diff options
Diffstat (limited to 'sc/source/filter/excel')
63 files changed, 67156 insertions, 0 deletions
diff --git a/sc/source/filter/excel/colrowst.cxx b/sc/source/filter/excel/colrowst.cxx new file mode 100644 index 000000000..e194b7309 --- /dev/null +++ b/sc/source/filter/excel/colrowst.cxx @@ -0,0 +1,359 @@ +/* -*- 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 <colrowst.hxx> + +#include <document.hxx> +#include <ftools.hxx> +#include <xltable.hxx> +#include <xistyle.hxx> +#include <excimp8.hxx> +#include <table.hxx> + +XclImpColRowSettings::XclImpColRowSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maColWidths(0, rRoot.GetDoc().GetSheetLimits().GetMaxColCount(), 0), + maColFlags(0, rRoot.GetDoc().GetSheetLimits().GetMaxColCount(), ExcColRowFlags::NONE), + maRowHeights(0, rRoot.GetDoc().GetSheetLimits().GetMaxRowCount(), 0), + maRowFlags(0, rRoot.GetDoc().GetSheetLimits().GetMaxRowCount(), ExcColRowFlags::NONE), + maHiddenRows(0, rRoot.GetDoc().GetSheetLimits().GetMaxRowCount(), false), + mnLastScRow( -1 ), + mnDefWidth( STD_COL_WIDTH ), + mnDefHeight( ScGlobal::nStdRowHeight ), + mnDefRowFlags( EXC_DEFROW_DEFAULTFLAGS ), + mbHasStdWidthRec( false ), + mbHasDefHeight( false ), + mbDirty( true ) +{ +} + +XclImpColRowSettings::~XclImpColRowSettings() +{ +} + +void XclImpColRowSettings::SetDefWidth( sal_uInt16 nDefWidth, bool bStdWidthRec ) +{ + if( bStdWidthRec ) + { + // STANDARDWIDTH record overrides DEFCOLWIDTH record + mnDefWidth = nDefWidth; + mbHasStdWidthRec = true; + } + else if( !mbHasStdWidthRec ) + { + // use DEFCOLWIDTH record only, if no STANDARDWIDTH record exists + mnDefWidth = nDefWidth; + } +} + +void XclImpColRowSettings::SetWidthRange( SCCOL nCol1, SCCOL nCol2, sal_uInt16 nWidth ) +{ + ScDocument& rDoc = GetDoc(); + nCol2 = ::std::min( nCol2, rDoc.MaxCol() ); + if (nCol2 == 256) + // In BIFF8, the column range is 0-255, and the use of 256 probably + // means the range should extend to the max column if the loading app + // support columns beyond 255. + nCol2 = rDoc.MaxCol(); + + nCol1 = ::std::min( nCol1, nCol2 ); + maColWidths.insert_back(nCol1, nCol2+1, nWidth); + + // We need to apply flag values individually since all flag values are aggregated for each column. + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + ApplyColFlag(nCol, ExcColRowFlags::Used); +} + +void XclImpColRowSettings::HideCol( SCCOL nCol ) +{ + if (!GetDoc().ValidCol(nCol)) + return; + + ApplyColFlag(nCol, ExcColRowFlags::Hidden); +} + +void XclImpColRowSettings::HideColRange( SCCOL nCol1, SCCOL nCol2 ) +{ + nCol2 = ::std::min( nCol2, GetDoc().MaxCol() ); + nCol1 = ::std::min( nCol1, nCol2 ); + + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + ApplyColFlag(nCol, ExcColRowFlags::Hidden); +} + +void XclImpColRowSettings::SetDefHeight( sal_uInt16 nDefHeight, sal_uInt16 nFlags ) +{ + mnDefHeight = nDefHeight; + mnDefRowFlags = nFlags; + if( mnDefHeight == 0 ) + { + mnDefHeight = ScGlobal::nStdRowHeight; + ::set_flag( mnDefRowFlags, EXC_DEFROW_HIDDEN ); + } + mbHasDefHeight = true; +} + +void XclImpColRowSettings::SetHeight( SCROW nScRow, sal_uInt16 nHeight ) +{ + if (!GetDoc().ValidRow(nScRow)) + return; + + sal_uInt16 nRawHeight = nHeight & EXC_ROW_HEIGHTMASK; + bool bDefHeight = ::get_flag( nHeight, EXC_ROW_FLAGDEFHEIGHT ) || (nRawHeight == 0); + maRowHeights.insert_back(nScRow, nScRow+1, nRawHeight); + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maRowFlags.search(nScRow, nFlagVal).second) + return; + + ::set_flag(nFlagVal, ExcColRowFlags::Used); + ::set_flag(nFlagVal, ExcColRowFlags::Default, bDefHeight); + + maRowFlags.insert_back(nScRow, nScRow+1, nFlagVal); + + if (nScRow > mnLastScRow) + mnLastScRow = nScRow; +} + +void XclImpColRowSettings::SetRowSettings( SCROW nScRow, sal_uInt16 nHeight, sal_uInt16 nFlags ) +{ + if (!GetDoc().ValidRow(nScRow)) + return; + + SetHeight(nScRow, nHeight); + + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maRowFlags.search(nScRow, nFlagVal).second) + return; + + if (::get_flag(nFlags, EXC_ROW_UNSYNCED)) + ::set_flag(nFlagVal, ExcColRowFlags::Man); + + maRowFlags.insert_back(nScRow, nScRow+1, nFlagVal); + + if (::get_flag(nFlags, EXC_ROW_HIDDEN)) + maHiddenRows.insert_back(nScRow, nScRow+1, true); +} + +void XclImpColRowSettings::SetManualRowHeight( SCROW nScRow ) +{ + if (!GetDoc().ValidRow(nScRow)) + return; + + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maRowFlags.search(nScRow, nFlagVal).second) + return; + + nFlagVal |= ExcColRowFlags::Man; + maRowFlags.insert_back(nScRow, nScRow+1, nFlagVal); +} + +void XclImpColRowSettings::SetDefaultXF( SCCOL nCol1, SCCOL nCol2, sal_uInt16 nXFIndex ) +{ + /* assign the default column formatting here to ensure that + explicit cell formatting is not overwritten. */ + OSL_ENSURE( (nCol1 <= nCol2) && GetDoc().ValidCol( nCol2 ), "XclImpColRowSettings::SetDefaultXF - invalid column index" ); + nCol2 = ::std::min( nCol2, GetDoc().MaxCol() ); + nCol1 = ::std::min( nCol1, nCol2 ); + XclImpXFRangeBuffer& rXFRangeBuffer = GetXFRangeBuffer(); + for( SCCOL nCol = nCol1; nCol <= nCol2; ++nCol ) + rXFRangeBuffer.SetColumnDefXF( nCol, nXFIndex ); +} + +void XclImpColRowSettings::Convert( SCTAB nScTab ) +{ + if( !mbDirty ) + return; + + ScDocument& rDoc = GetDoc(); + + // column widths ---------------------------------------------------------- + + maColWidths.build_tree(); + for (SCCOL nCol = 0; nCol <= rDoc.MaxCol(); ++nCol) + { + sal_uInt16 nWidth = mnDefWidth; + if (GetColFlag(nCol, ExcColRowFlags::Used)) + { + sal_uInt16 nTmp; + if (maColWidths.search_tree(nCol, nTmp).second) + nWidth = nTmp; + } + + /* Hidden columns: remember hidden state, but do not set hidden state + in document here. Needed for #i11776#, no HIDDEN flags in the + document, until filters and outlines are inserted. */ + if( nWidth == 0 ) + { + ApplyColFlag(nCol, ExcColRowFlags::Hidden); + nWidth = mnDefWidth; + } + rDoc.SetColWidthOnly( nCol, nScTab, nWidth ); + } + + // row heights ------------------------------------------------------------ + + // #i54252# set default row height + rDoc.SetRowHeightOnly( 0, rDoc.MaxRow(), nScTab, mnDefHeight ); + if( ::get_flag( mnDefRowFlags, EXC_DEFROW_UNSYNCED ) ) + // first access to row flags, do not ask for old flags + rDoc.SetRowFlags( 0, rDoc.MaxRow(), nScTab, CRFlags::ManualSize ); + + maRowHeights.build_tree(); + if (!maRowHeights.is_tree_valid()) + return; + + SCROW nPrevRow = -1; + ExcColRowFlags nPrevFlags = ExcColRowFlags::NONE; + for (const auto& [nRow, nFlags] : maRowFlags) + { + if (nPrevRow >= 0) + { + sal_uInt16 nHeight = 0; + + if (nPrevFlags & ExcColRowFlags::Used) + { + if (nPrevFlags & ExcColRowFlags::Default) + { + nHeight = mnDefHeight; + rDoc.SetRowHeightOnly(nPrevRow, nRow-1, nScTab, nHeight); + } + else + { + for (SCROW i = nPrevRow; i <= nRow - 1; ++i) + { + SCROW nLast; + if (!maRowHeights.search_tree(i, nHeight, nullptr, &nLast).second) + { + // search failed for some reason + return; + } + + if (nLast > nRow) + nLast = nRow; + + rDoc.SetRowHeightOnly(i, nLast-1, nScTab, nHeight); + i = nLast-1; + } + } + + if (nPrevFlags & ExcColRowFlags::Man) + rDoc.SetManualHeight(nPrevRow, nRow-1, nScTab, true); + } + else + { + nHeight = mnDefHeight; + rDoc.SetRowHeightOnly(nPrevRow, nRow-1, nScTab, nHeight); + } + } + + nPrevRow = nRow; + nPrevFlags = nFlags; + } + + mbDirty = false; +} + +void XclImpColRowSettings::ConvertHiddenFlags( SCTAB nScTab ) +{ + ScDocument& rDoc = GetDoc(); + + // hide the columns + for( SCCOL nCol : rDoc.GetColumnsRange(nScTab, 0, rDoc.MaxCol()) ) + if (GetColFlag(nCol, ExcColRowFlags::Hidden)) + rDoc.ShowCol( nCol, nScTab, false ); + + // #i38093# rows hidden by filter need extra flag + SCROW nFirstFilterScRow = SCROW_MAX; + SCROW nLastFilterScRow = SCROW_MAX; + if( GetBiff() == EXC_BIFF8 ) + { + const XclImpAutoFilterData* pFilter = GetFilterManager().GetByTab( nScTab ); + // #i70026# use IsFiltered() to set the CRFlags::Filtered flag for active filters only + if( pFilter && pFilter->IsActive() && pFilter->IsFiltered() ) + { + nFirstFilterScRow = pFilter->StartRow(); + nLastFilterScRow = pFilter->EndRow(); + } + } + + // In case the excel row limit is lower than calc's, use the visibility of + // the last row and extend it to calc's last row. + SCROW nLastXLRow = GetRoot().GetXclMaxPos().Row(); + if (nLastXLRow < rDoc.MaxRow()) + { + bool bHidden = false; + if (!maHiddenRows.search(nLastXLRow, bHidden).second) + return; + + maHiddenRows.insert_back(nLastXLRow, GetDoc().GetSheetLimits().GetMaxRowCount(), bHidden); + } + + SCROW nPrevRow = -1; + bool bPrevHidden = false; + for (const auto& [nRow, bHidden] : maHiddenRows) + { + if (nPrevRow >= 0) + { + if (bPrevHidden) + { + rDoc.SetRowHidden(nPrevRow, nRow-1, nScTab, true); + // #i38093# rows hidden by filter need extra flag + if (nFirstFilterScRow <= nPrevRow && nPrevRow <= nLastFilterScRow) + { + SCROW nLast = ::std::min(nRow-1, nLastFilterScRow); + rDoc.SetRowFiltered(nPrevRow, nLast, nScTab, true); + } + } + } + + nPrevRow = nRow; + bPrevHidden = bHidden; + } + + // #i47438# if default row format is hidden, hide remaining rows + if( ::get_flag( mnDefRowFlags, EXC_DEFROW_HIDDEN ) && (mnLastScRow < rDoc.MaxRow()) ) + rDoc.ShowRows( mnLastScRow + 1, rDoc.MaxRow(), nScTab, false ); +} + +void XclImpColRowSettings::ApplyColFlag(SCCOL nCol, ExcColRowFlags nNewVal) +{ + // Get the original flag value. + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + std::pair<ColRowFlagsType::const_iterator,bool> r = maColFlags.search(nCol, nFlagVal); + if (!r.second) + // Search failed. + return; + + ::set_flag(nFlagVal, nNewVal); + + // Re-insert the flag value. + maColFlags.insert(r.first, nCol, nCol+1, nFlagVal); +} + +bool XclImpColRowSettings::GetColFlag(SCCOL nCol, ExcColRowFlags nMask) const +{ + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maColFlags.search(nCol, nFlagVal).second) + return false; + // Search failed. + + return bool(nFlagVal & nMask); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excdoc.cxx b/sc/source/filter/excel/excdoc.cxx new file mode 100644 index 000000000..c01dde329 --- /dev/null +++ b/sc/source/filter/excel/excdoc.cxx @@ -0,0 +1,905 @@ +/* -*- 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 <sfx2/objsh.hxx> +#include <rtl/ustring.hxx> + +#include <document.hxx> +#include <scextopt.hxx> +#include <docoptio.hxx> +#include <tabprotection.hxx> +#include <postit.hxx> +#include <root.hxx> + +#include <excdoc.hxx> +#include <xeextlst.hxx> +#include <biffhelper.hxx> + +#include <xcl97rec.hxx> +#include <xetable.hxx> +#include <xelink.hxx> +#include <xepage.hxx> +#include <xeview.hxx> +#include <xecontent.hxx> +#include <xeescher.hxx> +#include <xepivot.hxx> +#include <export/SparklineExt.hxx> +#include <XclExpChangeTrack.hxx> +#include <xepivotxml.hxx> +#include <xedbdata.hxx> +#include <xlcontent.hxx> +#include <xlname.hxx> +#include <xllink.hxx> +#include <xltools.hxx> + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <o3tl/safeint.hxx> +#include <oox/token/tokens.hxx> +#include <oox/token/namespaces.hxx> +#include <memory> + +using namespace oox; + +static OUString lcl_GetVbaTabName( SCTAB n ) +{ + OUString aRet = "__VBA__" + OUString::number( static_cast<sal_uInt16>(n) ); + return aRet; +} + +static void lcl_AddBookviews( XclExpRecordList<>& aRecList, const ExcTable& self ) +{ + aRecList.AppendNewRecord( new XclExpXmlStartElementRecord( XML_bookViews ) ); + aRecList.AppendNewRecord( new XclExpWindow1( self.GetRoot() ) ); + aRecList.AppendNewRecord( new XclExpXmlEndElementRecord( XML_bookViews ) ); +} + +static void lcl_AddCalcPr( XclExpRecordList<>& aRecList, const ExcTable& self ) +{ + ScDocument& rDoc = self.GetDoc(); + + aRecList.AppendNewRecord( new XclExpXmlStartSingleElementRecord( XML_calcPr ) ); + // OOXTODO: calcCompleted, calcId, calcMode, calcOnSave, + // concurrentCalc, concurrentManualCount, + // forceFullCalc, fullCalcOnLoad, fullPrecision + aRecList.AppendNewRecord( new XclCalccount( rDoc ) ); + aRecList.AppendNewRecord( new XclRefmode( rDoc ) ); + aRecList.AppendNewRecord( new XclIteration( rDoc ) ); + aRecList.AppendNewRecord( new XclDelta( rDoc ) ); + aRecList.AppendNewRecord( new XclExpBoolRecord(oox::xls::BIFF_ID_SAVERECALC, true) ); + aRecList.AppendNewRecord( new XclExpXmlEndSingleElementRecord() ); // XML_calcPr +} + +static void lcl_AddWorkbookProtection( XclExpRecordList<>& aRecList, const ExcTable& self ) +{ + aRecList.AppendNewRecord( new XclExpXmlStartSingleElementRecord( XML_workbookProtection ) ); + + const ScDocProtection* pProtect = self.GetDoc().GetDocProtection(); + if (pProtect && pProtect->isProtected()) + { + aRecList.AppendNewRecord( new XclExpWindowProtection(pProtect->isOptionEnabled(ScDocProtection::WINDOWS)) ); + aRecList.AppendNewRecord( new XclExpProtection(pProtect->isOptionEnabled(ScDocProtection::STRUCTURE)) ); + aRecList.AppendNewRecord( new XclExpPassHash(pProtect->getPasswordHash(PASSHASH_XL)) ); + } + + aRecList.AppendNewRecord( new XclExpXmlEndSingleElementRecord() ); // XML_workbookProtection +} + +static void lcl_AddScenariosAndFilters( XclExpRecordList<>& aRecList, const XclExpRoot& rRoot, SCTAB nScTab ) +{ + // Scenarios + aRecList.AppendNewRecord( new ExcEScenarioManager( rRoot, nScTab ) ); + // filter + aRecList.AppendRecord( rRoot.GetFilterManager().CreateRecord( nScTab ) ); +} + +ExcTable::ExcTable( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnScTab( SCTAB_GLOBAL ), + nExcTab( EXC_NOTAB ), + mxNoteList( new XclExpNoteList ) +{ +} + +ExcTable::ExcTable( const XclExpRoot& rRoot, SCTAB nScTab ) : + XclExpRoot( rRoot ), + mnScTab( nScTab ), + nExcTab( rRoot.GetTabInfo().GetXclTab( nScTab ) ), + mxNoteList( new XclExpNoteList ) +{ +} + +ExcTable::~ExcTable() +{ +} + +void ExcTable::Add( XclExpRecordBase* pRec ) +{ + OSL_ENSURE( pRec, "-ExcTable::Add(): pRec is NULL!" ); + aRecList.AppendNewRecord( pRec ); +} + +void ExcTable::FillAsHeaderBinary( ExcBoundsheetList& rBoundsheetList ) +{ + InitializeGlobals(); + + RootData& rR = GetOldRoot(); + ScDocument& rDoc = GetDoc(); + XclExpTabInfo& rTabInfo = GetTabInfo(); + + if ( GetBiff() <= EXC_BIFF5 ) + Add( new ExcBofW ); + else + Add( new ExcBofW8 ); + + sal_uInt16 nExcTabCount = rTabInfo.GetXclTabCount(); + sal_uInt16 nCodenames = static_cast< sal_uInt16 >( GetExtDocOptions().GetCodeNameCount() ); + + SfxObjectShell* pShell = GetDocShell(); + sal_uInt16 nWriteProtHash = pShell ? pShell->GetModifyPasswordHash() : 0; + bool bRecommendReadOnly = pShell && pShell->IsLoadReadonly(); + + if( (nWriteProtHash > 0) || bRecommendReadOnly ) + Add( new XclExpEmptyRecord( EXC_ID_WRITEPROT ) ); + + // TODO: correct codepage for BIFF5? + sal_uInt16 nCodePage = XclTools::GetXclCodePage( (GetBiff() <= EXC_BIFF5) ? RTL_TEXTENCODING_MS_1252 : RTL_TEXTENCODING_UNICODE ); + + if( GetBiff() <= EXC_BIFF5 ) + { + Add( new XclExpEmptyRecord( EXC_ID_INTERFACEHDR ) ); + Add( new XclExpUInt16Record( EXC_ID_MMS, 0 ) ); + Add( new XclExpEmptyRecord( EXC_ID_TOOLBARHDR ) ); + Add( new XclExpEmptyRecord( EXC_ID_TOOLBAREND ) ); + Add( new XclExpEmptyRecord( EXC_ID_INTERFACEEND ) ); + Add( new ExcDummy_00 ); + } + else + { + if( IsDocumentEncrypted() ) + Add( new XclExpFileEncryption( GetRoot() ) ); + Add( new XclExpInterfaceHdr( nCodePage ) ); + Add( new XclExpUInt16Record( EXC_ID_MMS, 0 ) ); + Add( new XclExpInterfaceEnd ); + Add( new XclExpWriteAccess ); + } + + Add( new XclExpFileSharing( GetRoot(), nWriteProtHash, bRecommendReadOnly ) ); + Add( new XclExpUInt16Record( EXC_ID_CODEPAGE, nCodePage ) ); + + if( GetBiff() == EXC_BIFF8 ) + { + Add( new XclExpBoolRecord( EXC_ID_DSF, false ) ); + Add( new XclExpEmptyRecord( EXC_ID_XL9FILE ) ); + rR.pTabId = new XclExpChTrTabId( std::max( nExcTabCount, nCodenames ) ); + Add( rR.pTabId ); + if( HasVbaStorage() ) + { + Add( new XclObproj ); + const OUString& rCodeName = GetExtDocOptions().GetDocSettings().maGlobCodeName; + if( !rCodeName.isEmpty() ) + Add( new XclCodename( rCodeName ) ); + } + } + + Add( new XclExpUInt16Record( EXC_ID_FNGROUPCOUNT, 14 ) ); + + if ( GetBiff() <= EXC_BIFF5 ) + { + // global link table: EXTERNCOUNT, EXTERNSHEET, NAME + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_NAME ) ); + } + + // document protection options + lcl_AddWorkbookProtection( aRecList, *this ); + + if( GetBiff() == EXC_BIFF8 ) + { + Add( new XclExpProt4Rev ); + Add( new XclExpProt4RevPass ); + } + + lcl_AddBookviews( aRecList, *this ); + + Add( new XclExpXmlStartSingleElementRecord( XML_workbookPr ) ); + if ( GetBiff() == EXC_BIFF8 && GetOutput() != EXC_OUTPUT_BINARY ) + { + Add( new XclExpBoolRecord(0x0040, false, XML_backupFile ) ); // BACKUP + Add( new XclExpBoolRecord(0x008D, false, XML_showObjects ) ); // HIDEOBJ + } + + if ( GetBiff() == EXC_BIFF8 ) + { + Add( new XclExpBoolRecord(0x0040, false) ); // BACKUP + Add( new XclExpBoolRecord(0x008D, false) ); // HIDEOBJ + } + + if( GetBiff() <= EXC_BIFF5 ) + { + Add( new ExcDummy_040 ); + Add( new Exc1904( rDoc ) ); + Add( new ExcDummy_041 ); + } + else + { + // BIFF8 + Add( new Exc1904( rDoc ) ); + Add( new XclExpBoolRecord( 0x000E, !rDoc.GetDocOptions().IsCalcAsShown() ) ); + Add( new XclExpBoolRecord(0x01B7, false) ); // REFRESHALL + Add( new XclExpBoolRecord(0x00DA, false) ); // BOOKBOOL + } + + // Formatting: FONT, FORMAT, XF, STYLE, PALETTE + aRecList.AppendRecord( CreateRecord( EXC_ID_FONTLIST ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_FORMATLIST ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_XFLIST ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_PALETTE ) ); + + SCTAB nC; + SCTAB nScTabCount = rTabInfo.GetScTabCount(); + if( GetBiff() <= EXC_BIFF5 ) + { + // Bundlesheet + for( nC = 0 ; nC < nScTabCount ; nC++ ) + if( rTabInfo.IsExportTab( nC ) ) + { + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet( rR, nC ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + } + else + { + // Pivot Cache + GetPivotTableManager().CreatePivotTables(); + aRecList.AppendRecord( GetPivotTableManager().CreatePivotCachesRecord() ); + + // Change tracking + if( rDoc.GetChangeTrack() ) + { + rR.pUserBViewList = new XclExpUserBViewList( *rDoc.GetChangeTrack() ); + Add( rR.pUserBViewList ); + } + + // Natural Language Formulas Flag + aRecList.AppendNewRecord( new XclExpBoolRecord( EXC_ID_USESELFS, GetDoc().GetDocOptions().IsLookUpColRowNames() ) ); + + // Bundlesheet + for( nC = 0 ; nC < nScTabCount ; nC++ ) + if( rTabInfo.IsExportTab( nC ) ) + { + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( rR, nC ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + + OUString aTmpString; + for( SCTAB nAdd = 0; nC < static_cast<SCTAB>(nCodenames) ; nC++, nAdd++ ) + { + aTmpString = lcl_GetVbaTabName( nAdd ); + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( aTmpString ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + + // COUNTRY - in BIFF8 in workbook globals + Add( new XclExpCountry( GetRoot() ) ); + + // link table: SUPBOOK, XCT, CRN, EXTERNNAME, EXTERNSHEET, NAME + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_NAME ) ); + + Add( new XclExpRecalcId ); + + // MSODRAWINGGROUP per-document data + aRecList.AppendRecord( GetObjectManager().CreateDrawingGroup() ); + // Shared string table: SST, EXTSST + aRecList.AppendRecord( CreateRecord( EXC_ID_SST ) ); + + Add( new XclExpBookExt ); + } + + Add( new ExcEof ); +} + +void ExcTable::FillAsHeaderXml( ExcBoundsheetList& rBoundsheetList ) +{ + InitializeGlobals(); + + RootData& rR = GetOldRoot(); + ScDocument& rDoc = GetDoc(); + XclExpTabInfo& rTabInfo = GetTabInfo(); + + sal_uInt16 nExcTabCount = rTabInfo.GetXclTabCount(); + sal_uInt16 nCodenames = static_cast< sal_uInt16 >( GetExtDocOptions().GetCodeNameCount() ); + + rR.pTabId = new XclExpChTrTabId( std::max( nExcTabCount, nCodenames ) ); + Add( rR.pTabId ); + + Add( new XclExpXmlStartSingleElementRecord( XML_workbookPr ) ); + Add( new XclExpBoolRecord(0x0040, false, XML_backupFile ) ); // BACKUP + Add( new XclExpBoolRecord(0x008D, false, XML_showObjects ) ); // HIDEOBJ + + Add( new Exc1904( rDoc ) ); + // OOXTODO: The following /workbook/workbookPr attributes are mapped + // to various BIFF records that are not currently supported: + // + // XML_allowRefreshQuery: QSISTAG 802h: fEnableRefresh + // XML_autoCompressPictures: COMPRESSPICTURES 89Bh: fAutoCompressPictures + // XML_checkCompatibility: COMPAT12 88Ch: fNoCompatChk + // XML_codeName: "Calc" + // XML_defaultThemeVersion: ??? + // XML_filterPrivacy: BOOKEXT 863h: fFilterPrivacy + // XML_hidePivotFieldList: BOOKBOOL DAh: fHidePivotTableFList + // XML_promptedSolutions: BOOKEXT 863h: fBuggedUserAboutSolution + // XML_publishItems: NAMEPUBLISH 893h: fPublished + // XML_saveExternalLinkValues: BOOKBOOL DAh: fNoSavSupp + // XML_showBorderUnselectedTables: BOOKBOOL DAh: fHideBorderUnsels + // XML_showInkAnnotation: BOOKEXT 863h: fShowInkAnnotation + // XML_showPivotChart: PIVOTCHARTBITS 859h: fGXHide?? + // XML_updateLinks: BOOKBOOL DAh: grbitUpdateLinks + Add( new XclExpXmlEndSingleElementRecord() ); // XML_workbookPr + + // Formatting: FONT, FORMAT, XF, STYLE, PALETTE + aRecList.AppendNewRecord( new XclExpXmlStyleSheet( *this ) ); + + // Change tracking + if( rDoc.GetChangeTrack() ) + { + rR.pUserBViewList = new XclExpUserBViewList( *rDoc.GetChangeTrack() ); + Add( rR.pUserBViewList ); + } + + lcl_AddWorkbookProtection( aRecList, *this ); + lcl_AddBookviews( aRecList, *this ); + + // Bundlesheet + SCTAB nC; + SCTAB nScTabCount = rTabInfo.GetScTabCount(); + aRecList.AppendNewRecord( new XclExpXmlStartElementRecord( XML_sheets ) ); + for( nC = 0 ; nC < nScTabCount ; nC++ ) + if( rTabInfo.IsExportTab( nC ) ) + { + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( rR, nC ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + aRecList.AppendNewRecord( new XclExpXmlEndElementRecord( XML_sheets ) ); + + OUString aTmpString; + for( SCTAB nAdd = 0; nC < static_cast<SCTAB>(nCodenames) ; nC++, nAdd++ ) + { + aTmpString = lcl_GetVbaTabName( nAdd ); + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( aTmpString ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + + // link table: SUPBOOK, XCT, CRN, EXTERNNAME, EXTERNSHEET, NAME + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_NAME ) ); + + lcl_AddCalcPr( aRecList, *this ); + + // MSODRAWINGGROUP per-document data + aRecList.AppendRecord( GetObjectManager().CreateDrawingGroup() ); + // Shared string table: SST, EXTSST + aRecList.AppendRecord( CreateRecord( EXC_ID_SST ) ); +} + +void ExcTable::FillAsTableBinary( SCTAB nCodeNameIdx ) +{ + InitializeTable( mnScTab ); + + RootData& rR = GetOldRoot(); + XclBiff eBiff = GetBiff(); + ScDocument& rDoc = GetDoc(); + + OSL_ENSURE( (mnScTab >= 0) && (mnScTab <= MAXTAB), "-ExcTable::Table(): mnScTab - no ordinary table!" ); + OSL_ENSURE( nExcTab <= o3tl::make_unsigned(MAXTAB), "-ExcTable::Table(): nExcTab - no ordinary table!" ); + + // create a new OBJ list for this sheet (may be used by notes, autofilter, data validation) + if( eBiff == EXC_BIFF8 ) + GetObjectManager().StartSheet(); + + // cell table: DEFROWHEIGHT, DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + mxCellTable = new XclExpCellTable( GetRoot() ); + + //export cell notes + std::vector<sc::NoteEntry> aNotes; + rDoc.GetAllNoteEntries(aNotes); + for (const auto& rNote : aNotes) + { + if (rNote.maPos.Tab() != mnScTab) + continue; + + mxNoteList->AppendNewRecord(new XclExpNote(GetRoot(), rNote.maPos, rNote.mpNote, u"")); + } + + // WSBOOL needs data from page settings, create it here, add it later + rtl::Reference<XclExpPageSettings> xPageSett = new XclExpPageSettings( GetRoot() ); + bool bFitToPages = xPageSett->GetPageData().mbFitToPages; + + if( eBiff <= EXC_BIFF5 ) + { + Add( new ExcBof ); + Add( new ExcDummy_02a ); + } + else + { + Add( new ExcBof8 ); + lcl_AddCalcPr( aRecList, *this ); + } + + // GUTS (count & size of outline icons) + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_GUTS ) ); + // DEFROWHEIGHT, created by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID2_DEFROWHEIGHT ) ); + + // COUNTRY - in BIFF5/7 in every worksheet + if( eBiff <= EXC_BIFF5 ) + Add( new XclExpCountry( GetRoot() ) ); + + Add( new XclExpWsbool( bFitToPages ) ); + + // page settings (SETUP and various other records) + aRecList.AppendRecord( xPageSett ); + + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(mnScTab); + if (pTabProtect && pTabProtect->isProtected()) + { + Add( new XclExpProtection(true) ); + Add( new XclExpBoolRecord(oox::xls::BIFF_ID_SCENPROTECT, pTabProtect->isOptionEnabled(ScTableProtection::SCENARIOS)) ); + if (pTabProtect->isOptionEnabled(ScTableProtection::OBJECTS)) + Add( new XclExpBoolRecord(oox::xls::BIFF_ID_OBJECTPROTECT, true )); + Add( new XclExpPassHash(pTabProtect->getPasswordHash(PASSHASH_XL)) ); + } + + // local link table: EXTERNCOUNT, EXTERNSHEET + if( eBiff <= EXC_BIFF5 ) + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + + if ( eBiff == EXC_BIFF8 ) + lcl_AddScenariosAndFilters( aRecList, GetRoot(), mnScTab ); + + // cell table: DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + aRecList.AppendRecord( mxCellTable ); + + // MERGEDCELLS record, generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_MERGEDCELLS ) ); + // label ranges + if( eBiff == EXC_BIFF8 ) + Add( new XclExpLabelranges( GetRoot() ) ); + // data validation (DVAL and list of DV records), generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_DVAL ) ); + + if( eBiff == EXC_BIFF8 ) + { + // all MSODRAWING and OBJ stuff of this sheet goes here + aRecList.AppendRecord( GetObjectManager().ProcessDrawing( GetSdrPage( mnScTab ) ) ); + // pivot tables + aRecList.AppendRecord( GetPivotTableManager().CreatePivotTablesRecord( mnScTab ) ); + } + + // list of NOTE records, generated by the cell table + aRecList.AppendRecord( mxNoteList ); + + // sheet view settings: WINDOW2, SCL, PANE, SELECTION + aRecList.AppendNewRecord( new XclExpTabViewSettings( GetRoot(), mnScTab ) ); + + if( eBiff == EXC_BIFF8 ) + { + // sheet protection options + Add( new XclExpSheetProtectOptions( GetRoot(), mnScTab ) ); + + // enhanced protections if there are + if (pTabProtect) + { + const ::std::vector<ScEnhancedProtection>& rProts( pTabProtect->getEnhancedProtection()); + for (const auto& rProt : rProts) + { + Add( new XclExpSheetEnhancedProtection( GetRoot(), rProt)); + } + } + + // web queries + Add( new XclExpWebQueryBuffer( GetRoot() ) ); + + // conditional formats + Add( new XclExpCondFormatBuffer( GetRoot(), XclExtLstRef() ) ); + + if( HasVbaStorage() ) + if( nCodeNameIdx < GetExtDocOptions().GetCodeNameCount() ) + Add( new XclCodename( GetExtDocOptions().GetCodeName( nCodeNameIdx ) ) ); + } + + // list of HLINK records, generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_HLINK ) ); + + // change tracking + if( rR.pUserBViewList ) + { + XclExpUserBViewList::const_iterator iter; + for ( iter = rR.pUserBViewList->cbegin(); iter != rR.pUserBViewList->cend(); ++iter) + { + Add( new XclExpUsersViewBegin( (*iter).GetGUID(), nExcTab ) ); + Add( new XclExpUsersViewEnd ); + } + } + + // EOF + Add( new ExcEof ); +} + +void ExcTable::FillAsTableXml() +{ + InitializeTable( mnScTab ); + + ScDocument& rDoc = GetDoc(); + + OSL_ENSURE( (mnScTab >= 0) && (mnScTab <= MAXTAB), "-ExcTable::Table(): mnScTab - no ordinary table!" ); + OSL_ENSURE( nExcTab <= o3tl::make_unsigned(MAXTAB), "-ExcTable::Table(): nExcTab - no ordinary table!" ); + + // create a new OBJ list for this sheet (may be used by notes, autofilter, data validation) + GetObjectManager().StartSheet(); + + // cell table: DEFROWHEIGHT, DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + mxCellTable = new XclExpCellTable( GetRoot() ); + + //export cell notes + std::vector<sc::NoteEntry> aNotes; + rDoc.GetAllNoteEntries(aNotes); + for (const auto& rNote : aNotes) + { + if (rNote.maPos.Tab() != mnScTab) + continue; + + mxNoteList->AppendNewRecord(new XclExpNote(GetRoot(), rNote.maPos, rNote.mpNote, u"")); + } + + // WSBOOL needs data from page settings, create it here, add it later + rtl::Reference<XclExpPageSettings> xPageSett = new XclExpPageSettings( GetRoot() ); + XclExtLstRef xExtLst = new XclExtLst( GetRoot() ); + bool bFitToPages = xPageSett->GetPageData().mbFitToPages; + + Color aTabColor = GetRoot().GetDoc().GetTabBgColor(mnScTab); + Add(new XclExpXmlSheetPr(bFitToPages, mnScTab, aTabColor, &GetFilterManager())); + + // GUTS (count & size of outline icons) + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_GUTS ) ); + // DEFROWHEIGHT, created by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID2_DEFROWHEIGHT ) ); + + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID3_DIMENSIONS ) ); + + // sheet view settings: WINDOW2, SCL, PANE, SELECTION + aRecList.AppendNewRecord( new XclExpTabViewSettings( GetRoot(), mnScTab ) ); + + // cell table: DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + aRecList.AppendRecord( mxCellTable ); + + // list of NOTE records, generated by the cell table + // not in the worksheet file + if( mxNoteList != nullptr && !mxNoteList->IsEmpty() ) + aRecList.AppendNewRecord( new XclExpComments( mnScTab, *mxNoteList ) ); + + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(mnScTab); + if (pTabProtect && pTabProtect->isProtected()) + Add( new XclExpSheetProtection(true, mnScTab) ); + + lcl_AddScenariosAndFilters( aRecList, GetRoot(), mnScTab ); + + // MERGEDCELLS record, generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_MERGEDCELLS ) ); + + // conditional formats + Add( new XclExpCondFormatBuffer( GetRoot(), xExtLst ) ); + + Add(new xcl::exp::SparklineBuffer(GetRoot(), xExtLst)); + + // data validation (DVAL and list of DV records), generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_DVAL ) ); + + // list of HLINK records, generated by the cell table + XclExpRecordRef xHyperlinks = mxCellTable->CreateRecord( EXC_ID_HLINK ); + XclExpHyperlinkList* pHyperlinkList = dynamic_cast<XclExpHyperlinkList*>(xHyperlinks.get()); + if( pHyperlinkList != nullptr && !pHyperlinkList->IsEmpty() ) + { + aRecList.AppendNewRecord( new XclExpXmlStartElementRecord( XML_hyperlinks ) ); + aRecList.AppendRecord( xHyperlinks ); + aRecList.AppendNewRecord( new XclExpXmlEndElementRecord( XML_hyperlinks ) ); + } + + aRecList.AppendRecord( xPageSett ); + + // all MSODRAWING and OBJ stuff of this sheet goes here + aRecList.AppendRecord( GetObjectManager().ProcessDrawing( GetSdrPage( mnScTab ) ) ); + + XclExpImgData* pImgData = xPageSett->getGraphicExport(); + if (pImgData) + aRecList.AppendRecord(pImgData); + + // <tableParts> after <drawing> and before <extLst> + aRecList.AppendRecord( GetTablesManager().GetTablesBySheet( mnScTab)); + + aRecList.AppendRecord( xExtLst ); +} + +void ExcTable::FillAsEmptyTable( SCTAB nCodeNameIdx ) +{ + InitializeTable( mnScTab ); + + if( !(HasVbaStorage() && (nCodeNameIdx < GetExtDocOptions().GetCodeNameCount())) ) + return; + + if( GetBiff() <= EXC_BIFF5 ) + { + Add( new ExcBof ); + } + else + { + Add( new ExcBof8 ); + Add( new XclCodename( GetExtDocOptions().GetCodeName( nCodeNameIdx ) ) ); + } + // sheet view settings: WINDOW2, SCL, PANE, SELECTION + aRecList.AppendNewRecord( new XclExpTabViewSettings( GetRoot(), mnScTab ) ); + Add( new ExcEof ); +} + +void ExcTable::Write( XclExpStream& rStrm ) +{ + SetCurrScTab( mnScTab ); + if( mxCellTable ) + mxCellTable->Finalize(true); + aRecList.Save( rStrm ); +} + +void ExcTable::WriteXml( XclExpXmlStream& rStrm ) +{ + if (!GetTabInfo().IsExportTab(mnScTab)) + { + // header export. + SetCurrScTab(mnScTab); + if (mxCellTable) + mxCellTable->Finalize(false); + aRecList.SaveXml(rStrm); + + return; + } + + // worksheet export + OUString sSheetName = XclXmlUtils::GetStreamName( "xl/", "worksheets/sheet", mnScTab+1 ); + + sax_fastparser::FSHelperPtr pWorksheet = rStrm.GetStreamForPath( sSheetName ); + + rStrm.PushStream( pWorksheet ); + + pWorksheet->startElement( XML_worksheet, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_xmlns, XML_xdr), "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", // rStrm.getNamespaceURL(OOX_NS(xm)).toUtf8() -> "http://schemas.microsoft.com/office/excel/2006/main", + FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)).toUtf8(), + FSNS(XML_xmlns, XML_xr2), rStrm.getNamespaceURL(OOX_NS(xr2)).toUtf8(), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)).toUtf8()); + + SetCurrScTab( mnScTab ); + if (mxCellTable) + mxCellTable->Finalize(false); + aRecList.SaveXml( rStrm ); + + XclExpXmlPivotTables* pPT = GetXmlPivotTableManager().GetTablesBySheet(mnScTab); + if (pPT) + pPT->SaveXml(rStrm); + + rStrm.GetCurrentStream()->endElement( XML_worksheet ); + rStrm.PopStream(); +} + +ExcDocument::ExcDocument( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + aHeader( rRoot ) +{ +} + +ExcDocument::~ExcDocument() +{ + maTableList.RemoveAllRecords(); // for the following assertion! +} + +void ExcDocument::ReadDoc() +{ + InitializeConvert(); + + if (GetOutput() == EXC_OUTPUT_BINARY) + aHeader.FillAsHeaderBinary(maBoundsheetList); + else + { + aHeader.FillAsHeaderXml(maBoundsheetList); + GetXmlPivotTableManager().Initialize(); + GetTablesManager().Initialize(); // Move outside conditions if we wanted to support BIFF. + } + + SCTAB nScTab = 0, nScTabCount = GetTabInfo().GetScTabCount(); + SCTAB nCodeNameIdx = 0, nCodeNameCount = GetExtDocOptions().GetCodeNameCount(); + + for( ; nScTab < nScTabCount; ++nScTab ) + { + if( GetTabInfo().IsExportTab( nScTab ) ) + { + ExcTableList::RecordRefType xTab = new ExcTable( GetRoot(), nScTab ); + maTableList.AppendRecord( xTab ); + if (GetOutput() == EXC_OUTPUT_BINARY) + xTab->FillAsTableBinary(nCodeNameIdx); + else + xTab->FillAsTableXml(); + + ++nCodeNameIdx; + } + } + for( ; nCodeNameIdx < nCodeNameCount; ++nScTab, ++nCodeNameIdx ) + { + ExcTableList::RecordRefType xTab = new ExcTable( GetRoot(), nScTab ); + maTableList.AppendRecord( xTab ); + xTab->FillAsEmptyTable( nCodeNameIdx ); + } + + if ( GetBiff() == EXC_BIFF8 ) + { + // complete temporary Escher stream + GetObjectManager().EndDocument(); + + // change tracking + if ( GetDoc().GetChangeTrack() ) + m_xExpChangeTrack.reset(new XclExpChangeTrack( GetRoot() )); + } +} + +void ExcDocument::Write( SvStream& rSvStrm ) +{ + if( !maTableList.IsEmpty() ) + { + InitializeSave(); + + XclExpStream aXclStrm( rSvStrm, GetRoot() ); + + aHeader.Write( aXclStrm ); + + OSL_ENSURE( maTableList.GetSize() == maBoundsheetList.GetSize(), + "ExcDocument::Write - different number of sheets and BOUNDSHEET records" ); + + for( size_t nTab = 0, nTabCount = maTableList.GetSize(); nTab < nTabCount; ++nTab ) + { + // set current stream position in BOUNDSHEET record + ExcBoundsheetRef xBoundsheet = maBoundsheetList.GetRecord( nTab ); + if( xBoundsheet ) + xBoundsheet->SetStreamPos( aXclStrm.GetSvStreamPos() ); + // write the table + maTableList.GetRecord( nTab )->Write( aXclStrm ); + } + + // write the table stream positions into the BOUNDSHEET records + for( size_t nBSheet = 0, nBSheetCount = maBoundsheetList.GetSize(); nBSheet < nBSheetCount; ++nBSheet ) + maBoundsheetList.GetRecord( nBSheet )->UpdateStreamPos( aXclStrm ); + } + if( m_xExpChangeTrack ) + m_xExpChangeTrack->Write(); +} + +void ExcDocument::WriteXml( XclExpXmlStream& rStrm ) +{ + SfxObjectShell* pDocShell = GetDocShell(); + + using namespace ::com::sun::star; + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( pDocShell->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference<document::XDocumentProperties> xDocProps = xDPS->getDocumentProperties(); + + OUString sUserName = GetUserName(); + sal_uInt32 nWriteProtHash = pDocShell->GetModifyPasswordHash(); + bool bHasPasswordHash = nWriteProtHash && !sUserName.isEmpty(); + const uno::Sequence<beans::PropertyValue> aInfo = pDocShell->GetModifyPasswordInfo(); + OUString sAlgorithm, sSalt, sHash; + sal_Int32 nCount = 0; + for (const auto& prop : aInfo) + { + if (prop.Name == "algorithm-name") + prop.Value >>= sAlgorithm; + else if (prop.Name == "salt") + prop.Value >>= sSalt; + else if (prop.Name == "iteration-count") + prop.Value >>= nCount; + else if (prop.Name == "hash") + prop.Value >>= sHash; + } + bool bHasPasswordInfo + = sAlgorithm != "PBKDF2" && !sSalt.isEmpty() && !sHash.isEmpty() && !sUserName.isEmpty(); + rStrm.exportDocumentProperties(xDocProps, pDocShell->IsSecurityOptOpenReadOnly() + && !bHasPasswordHash && !bHasPasswordInfo); + rStrm.exportCustomFragments(); + + sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream(); + rWorkbook->startElement( XML_workbook, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8() ); + rWorkbook->singleElement( XML_fileVersion, + XML_appName, "Calc" + // OOXTODO: XML_codeName + // OOXTODO: XML_lastEdited + // OOXTODO: XML_lowestEdited + // OOXTODO: XML_rupBuild + ); + + if (bHasPasswordHash) + rWorkbook->singleElement(XML_fileSharing, + XML_userName, sUserName, + XML_reservationPassword, OString::number(nWriteProtHash, 16).getStr()); + else if (bHasPasswordInfo) + rWorkbook->singleElement(XML_fileSharing, + XML_userName, sUserName, + XML_algorithmName, sAlgorithm.toUtf8().getStr(), + XML_hashValue, sHash.toUtf8().getStr(), + XML_saltValue, sSalt.toUtf8().getStr(), + XML_spinCount, OString::number(nCount).getStr()); + + if( !maTableList.IsEmpty() ) + { + InitializeSave(); + + aHeader.WriteXml( rStrm ); + + for( size_t nTab = 0, nTabCount = maTableList.GetSize(); nTab < nTabCount; ++nTab ) + { + // write the table + maTableList.GetRecord( nTab )->WriteXml( rStrm ); + } + } + + if( m_xExpChangeTrack ) + m_xExpChangeTrack->WriteXml( rStrm ); + + XclExpXmlPivotCaches& rCaches = GetXmlPivotTableManager().GetCaches(); + if (rCaches.HasCaches()) + rCaches.SaveXml(rStrm); + + const ScCalcConfig& rCalcConfig = GetDoc().GetCalcConfig(); + formula::FormulaGrammar::AddressConvention eConv = rCalcConfig.meStringRefAddressSyntax; + + // don't save "unspecified" string ref syntax ... query formula grammar + // and save that instead + if( eConv == formula::FormulaGrammar::CONV_UNSPECIFIED) + { + eConv = GetDoc().GetAddressConvention(); + } + + // write if it has been read|imported or explicitly changed + // or if ref syntax isn't what would be native for our file format + // i.e. ExcelA1 in this case + if ( rCalcConfig.mbHasStringRefSyntax || + (eConv != formula::FormulaGrammar::CONV_XL_A1) ) + { + XclExtLstRef xExtLst = new XclExtLst( GetRoot() ); + xExtLst->AddRecord( new XclExpExtCalcPr( GetRoot(), eConv ) ); + xExtLst->SaveXml(rStrm); + } + + rWorkbook->endElement( XML_workbook ); + rWorkbook.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excel.cxx b/sc/source/filter/excel/excel.cxx new file mode 100644 index 000000000..edc60721a --- /dev/null +++ b/sc/source/filter/excel/excel.cxx @@ -0,0 +1,494 @@ +/* -*- 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 <sfx2/docfile.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/sfxsids.hrc> +#include <sot/storage.hxx> +#include <sot/exchange.hxx> +#include <filter/msfilter/classids.hxx> +#include <tools/globname.hxx> +#include <com/sun/star/packages/XPackageEncryption.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <unotools/streamwrap.hxx> +#include <unotools/defaultencoding.hxx> +#include <unotools/wincodepage.hxx> +#include <osl/diagnose.h> +#include <filter.hxx> +#include <document.hxx> +#include <xistream.hxx> +#include <xltools.hxx> +#include <docoptio.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/processfactory.hxx> + +#include <docsh.hxx> +#include <scerrors.hxx> +#include <imp_op.hxx> +#include <excimp8.hxx> +#include <exp_op.hxx> +#include <scdll.hxx> + +#include <memory> + +using namespace css; + +static void lcl_getListOfStreams(SotStorage * pStorage, comphelper::SequenceAsHashMap& aStreamsData, const OUString& sPrefix) +{ + SvStorageInfoList aElements; + pStorage->FillInfoList(&aElements); + for (const auto & aElement : aElements) + { + OUString sStreamFullName = sPrefix.getLength() ? sPrefix + "/" + aElement.GetName() : aElement.GetName(); + if (aElement.IsStorage()) + { + tools::SvRef<SotStorage> xSubStorage = pStorage->OpenSotStorage(aElement.GetName(), StreamMode::STD_READ | StreamMode::SHARE_DENYALL); + lcl_getListOfStreams(xSubStorage.get(), aStreamsData, sStreamFullName); + } + else + { + // Read stream + tools::SvRef<SotStorageStream> rStream = pStorage->OpenSotStream(aElement.GetName(), StreamMode::READ | StreamMode::SHARE_DENYALL); + if (rStream.is()) + { + sal_Int32 nStreamSize = rStream->GetSize(); + uno::Sequence< sal_Int8 > oData; + oData.realloc(nStreamSize); + sal_Int32 nReadBytes = rStream->ReadBytes(oData.getArray(), nStreamSize); + if (nStreamSize == nReadBytes) + aStreamsData[sStreamFullName] <<= oData; + } + } + } +} + +static tools::SvRef<SotStorage> lcl_DRMDecrypt(const SfxMedium& rMedium, const tools::SvRef<SotStorage>& rStorage, std::shared_ptr<SvStream>& rNewStorageStrm) +{ + tools::SvRef<SotStorage> aNewStorage; + + // We have DRM encrypted storage. We should try to decrypt it first, if we can + uno::Sequence< uno::Any > aArguments; + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference< packages::XPackageEncryption > xPackageEncryption( + xComponentContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.oox.crypto.DRMDataSpace", aArguments, xComponentContext), uno::UNO_QUERY); + + if (!xPackageEncryption.is()) + { + // We do not know how to decrypt this + return aNewStorage; + } + + comphelper::SequenceAsHashMap aStreamsData; + lcl_getListOfStreams(rStorage.get(), aStreamsData, ""); + + try { + uno::Sequence<beans::NamedValue> aStreams = aStreamsData.getAsConstNamedValueList(); + if (!xPackageEncryption->readEncryptionInfo(aStreams)) + { + // We failed with decryption + return aNewStorage; + } + + tools::SvRef<SotStorageStream> rContentStream = rStorage->OpenSotStream("\011DRMContent", StreamMode::READ | StreamMode::SHARE_DENYALL); + if (!rContentStream.is()) + { + return aNewStorage; + } + + rNewStorageStrm = std::make_shared<SvMemoryStream>(); + + uno::Reference<io::XInputStream > xInputStream(new utl::OSeekableInputStreamWrapper(rContentStream.get(), false)); + uno::Reference<io::XOutputStream > xDecryptedStream(new utl::OSeekableOutputStreamWrapper(*rNewStorageStrm)); + + if (!xPackageEncryption->decrypt(xInputStream, xDecryptedStream)) + { + // We failed with decryption + return aNewStorage; + } + + rNewStorageStrm->Seek(0); + + // Further reading is done from new document + aNewStorage = new SotStorage(*rNewStorageStrm); + + // Set the media descriptor data + uno::Sequence<beans::NamedValue> aEncryptionData = xPackageEncryption->createEncryptionData(""); + rMedium.GetItemSet()->Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, uno::Any(aEncryptionData))); + } + catch (const std::exception&) + { + return aNewStorage; + } + + return aNewStorage; +} + +ErrCode ScFormatFilterPluginImpl::ScImportExcel( SfxMedium& rMedium, ScDocument* pDocument, const EXCIMPFORMAT eFormat ) +{ + // check the passed Calc document + OSL_ENSURE( pDocument, "::ScImportExcel - no document" ); + if( !pDocument ) return SCERR_IMPORT_INTERNAL; // should not happen + + /* Import all BIFF versions regardless on eFormat, needed for import of + external cells (file type detection returns Excel4.0). */ + if( (eFormat != EIF_AUTO) && (eFormat != EIF_BIFF_LE4) && (eFormat != EIF_BIFF5) && (eFormat != EIF_BIFF8) ) + { + OSL_FAIL( "::ScImportExcel - wrong file format specification" ); + return SCERR_IMPORT_FORMAT; + } + + // check the input stream from medium + SvStream* pMedStrm = rMedium.GetInStream(); + OSL_ENSURE( pMedStrm, "::ScImportExcel - medium without input stream" ); + if( !pMedStrm ) return SCERR_IMPORT_OPEN; // should not happen + + SvStream* pBookStrm = nullptr; // The "Book"/"Workbook" stream containing main data. + XclBiff eBiff = EXC_BIFF_UNKNOWN; // The BIFF version of the main stream. + + // try to open an OLE storage + tools::SvRef<SotStorage> xRootStrg; + tools::SvRef<SotStorageStream> xStrgStrm; + std::shared_ptr<SvStream> aNewStorageStrm; + if( SotStorage::IsStorageFile( pMedStrm ) ) + { + xRootStrg = new SotStorage( pMedStrm, false ); + if( xRootStrg->GetError() ) + xRootStrg = nullptr; + } + + // try to open "Book" or "Workbook" stream in OLE storage + if( xRootStrg.is() ) + { + // Check if there is DRM encryption in storage + tools::SvRef<SotStorageStream> xDRMStrm = ScfTools::OpenStorageStreamRead(xRootStrg, "\011DRMContent"); + if (xDRMStrm.is()) + { + xRootStrg = lcl_DRMDecrypt(rMedium, xRootStrg, aNewStorageStrm); + } + + // try to open the "Book" stream + tools::SvRef<SotStorageStream> xBookStrm = ScfTools::OpenStorageStreamRead( xRootStrg, EXC_STREAM_BOOK ); + XclBiff eBookBiff = xBookStrm.is() ? XclImpStream::DetectBiffVersion( *xBookStrm ) : EXC_BIFF_UNKNOWN; + + // try to open the "Workbook" stream + tools::SvRef<SotStorageStream> xWorkbookStrm = ScfTools::OpenStorageStreamRead( xRootStrg, EXC_STREAM_WORKBOOK ); + XclBiff eWorkbookBiff = xWorkbookStrm.is() ? XclImpStream::DetectBiffVersion( *xWorkbookStrm ) : EXC_BIFF_UNKNOWN; + + // decide which stream to use + if( (eWorkbookBiff != EXC_BIFF_UNKNOWN) && ((eBookBiff == EXC_BIFF_UNKNOWN) || (eWorkbookBiff > eBookBiff)) ) + { + /* Only "Workbook" stream exists; or both streams exist, + and "Workbook" has higher BIFF version than "Book" stream. */ + xStrgStrm = xWorkbookStrm; + eBiff = eWorkbookBiff; + } + else if( eBookBiff != EXC_BIFF_UNKNOWN ) + { + /* Only "Book" stream exists; or both streams exist, + and "Book" has higher BIFF version than "Workbook" stream. */ + xStrgStrm = xBookStrm; + eBiff = eBookBiff; + } + + pBookStrm = xStrgStrm.get(); + } + + // no "Book" or "Workbook" stream found, try plain input stream from medium (even for BIFF5+) + if( !pBookStrm ) + { + eBiff = XclImpStream::DetectBiffVersion( *pMedStrm ); + if( eBiff != EXC_BIFF_UNKNOWN ) + pBookStrm = pMedStrm; + } + + // try to import the file + ErrCode eRet = SCERR_IMPORT_UNKNOWN_BIFF; + if( pBookStrm ) + { + pBookStrm->SetBufferSize( 0x8000 ); // still needed? + + XclImpRootData aImpData( + eBiff, rMedium, xRootStrg, *pDocument, + utl_getWinTextEncodingFromLangStr(utl_getLocaleForGlobalDefaultEncoding())); + std::unique_ptr< ImportExcel > xFilter; + switch( eBiff ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + xFilter.reset( new ImportExcel( aImpData, *pBookStrm ) ); + break; + case EXC_BIFF8: + xFilter.reset( new ImportExcel8( aImpData, *pBookStrm ) ); + break; + default: DBG_ERROR_BIFF(); + } + + eRet = xFilter ? xFilter->Read() : SCERR_IMPORT_INTERNAL; + } + + return eRet; +} + +static ErrCode lcl_ExportExcelBiff( SfxMedium& rMedium, ScDocument *pDocument, + SvStream* pMedStrm, bool bBiff8, rtl_TextEncoding eNach ) +{ + uno::Reference< packages::XPackageEncryption > xPackageEncryption; + uno::Sequence< beans::NamedValue > aEncryptionData; + const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem<SfxUnoAnyItem>(rMedium.GetItemSet(), SID_ENCRYPTIONDATA, false); + SvStream* pOriginalMediaStrm = pMedStrm; + std::shared_ptr<SvStream> pMediaStrm; + if (pEncryptionDataItem && (pEncryptionDataItem->GetValue() >>= aEncryptionData)) + { + ::comphelper::SequenceAsHashMap aHashData(aEncryptionData); + OUString sCryptoType = aHashData.getUnpackedValueOrDefault("CryptoType", OUString()); + + if (sCryptoType.getLength()) + { + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Sequence<uno::Any> aArguments{ + uno::Any(beans::NamedValue("Binary", uno::Any(true))) }; + xPackageEncryption.set( + xComponentContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.oox.crypto." + sCryptoType, aArguments, xComponentContext), uno::UNO_QUERY); + + if (xPackageEncryption.is()) + { + // We have an encryptor. Export document into memory stream and encrypt it later + pMediaStrm = std::make_shared<SvMemoryStream>(); + pMedStrm = pMediaStrm.get(); + + // Temp removal of EncryptionData to avoid password protection triggering + rMedium.GetItemSet()->ClearItem(SID_ENCRYPTIONDATA); + } + } + } + + // try to open an OLE storage + tools::SvRef<SotStorage> xRootStrg = new SotStorage( pMedStrm, false ); + if( xRootStrg->GetError() ) return SCERR_IMPORT_OPEN; + + // create BIFF dependent strings + OUString aStrmName, aClipName, aClassName; + if( bBiff8 ) + { + aStrmName = EXC_STREAM_WORKBOOK; + aClipName = "Biff8"; + aClassName = "Microsoft Excel 97-Tabelle"; + } + else + { + aStrmName = EXC_STREAM_BOOK; + aClipName = "Biff5"; + aClassName = "Microsoft Excel 5.0-Tabelle"; + } + + // open the "Book"/"Workbook" stream + tools::SvRef<SotStorageStream> xStrgStrm = ScfTools::OpenStorageStreamWrite( xRootStrg, aStrmName ); + if( !xStrgStrm.is() || xStrgStrm->GetError() ) return SCERR_IMPORT_OPEN; + + xStrgStrm->SetBufferSize( 0x8000 ); // still needed? + + ErrCode eRet = SCERR_IMPORT_UNKNOWN_BIFF; + XclExpRootData aExpData( bBiff8 ? EXC_BIFF8 : EXC_BIFF5, rMedium, xRootStrg, *pDocument, eNach ); + if ( bBiff8 ) + { + ExportBiff8 aFilter( aExpData, *xStrgStrm ); + eRet = aFilter.Write(); + } + else + { + ExportBiff5 aFilter( aExpData, *xStrgStrm ); + eRet = aFilter.Write(); + } + + if( eRet == SCWARN_IMPORT_RANGE_OVERFLOW ) + eRet = SCWARN_EXPORT_MAXROW; + + SvGlobalName aGlobName(MSO_EXCEL5_CLASSID); + SotClipboardFormatId nClip = SotExchange::RegisterFormatName( aClipName ); + xRootStrg->SetClass( aGlobName, nClip, aClassName ); + + xStrgStrm->Commit(); + xRootStrg->Commit(); + + if (xPackageEncryption.is()) + { + // Perform DRM encryption + pMedStrm->Seek(0); + + xPackageEncryption->setupEncryption(aEncryptionData); + + uno::Reference<io::XInputStream > xInputStream(new utl::OSeekableInputStreamWrapper(pMedStrm, false)); + uno::Sequence<beans::NamedValue> aStreams = xPackageEncryption->encrypt(xInputStream); + + tools::SvRef<SotStorage> xEncryptedRootStrg = new SotStorage(pOriginalMediaStrm, false); + for (const beans::NamedValue & aStreamData : std::as_const(aStreams)) + { + // To avoid long paths split and open substorages recursively + // Splitting paths manually, since comphelper::string::split is trimming special characters like \0x01, \0x09 + tools::SvRef<SotStorage> pStorage = xEncryptedRootStrg.get(); + OUString sFileName; + sal_Int32 idx = 0; + do + { + OUString sPathElem = aStreamData.Name.getToken(0, L'/', idx); + if (!sPathElem.isEmpty()) + { + if (idx < 0) + { + sFileName = sPathElem; + } + else + { + pStorage = pStorage->OpenSotStorage(sPathElem); + } + } + } while (pStorage && idx >= 0); + + if (!pStorage) + { + eRet = ERRCODE_IO_GENERAL; + break; + } + + tools::SvRef<SotStorageStream> pStream = pStorage->OpenSotStream(sFileName); + if (!pStream) + { + eRet = ERRCODE_IO_GENERAL; + break; + } + uno::Sequence<sal_Int8> aStreamContent; + aStreamData.Value >>= aStreamContent; + size_t nBytesWritten = pStream->WriteBytes(aStreamContent.getConstArray(), aStreamContent.getLength()); + if (nBytesWritten != static_cast<size_t>(aStreamContent.getLength())) + { + eRet = ERRCODE_IO_CANTWRITE; + break; + } + } + xEncryptedRootStrg->Commit(); + + // Restore encryption data + rMedium.GetItemSet()->Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, uno::Any(aEncryptionData))); + } + + return eRet; +} + +ErrCode ScFormatFilterPluginImpl::ScExportExcel5( SfxMedium& rMedium, ScDocument *pDocument, + ExportFormatExcel eFormat, rtl_TextEncoding eNach ) +{ + if( eFormat != ExpBiff5 && eFormat != ExpBiff8 ) + return SCERR_IMPORT_NI; + + // check the passed Calc document + OSL_ENSURE( pDocument, "::ScExportExcel5 - no document" ); + if( !pDocument ) return SCERR_IMPORT_INTERNAL; // should not happen + + // check the output stream from medium + SvStream* pMedStrm = rMedium.GetOutStream(); + OSL_ENSURE( pMedStrm, "::ScExportExcel5 - medium without output stream" ); + if( !pMedStrm ) return SCERR_IMPORT_OPEN; // should not happen + + ErrCode eRet = lcl_ExportExcelBiff(rMedium, pDocument, pMedStrm, eFormat == ExpBiff8, eNach); + return eRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportCalcRTF(SvStream &rStream) +{ + ScDLL::Init(); + ScDocument aDocument; + ScDocOptions aDocOpt = aDocument.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + aDocument.SetDocOptions(aDocOpt); + aDocument.MakeTable(0); + aDocument.EnableExecuteLink(false); + aDocument.SetInsertingFromOtherDoc(true); + ScRange aRange; + + bool bRet; + + try + { + bRet = ScFormatFilter::Get().ScImportRTF(rStream, OUString(), &aDocument, aRange) == ERRCODE_NONE; + } + catch (const std::range_error&) + { + return false; + } + + return bRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportXLS(SvStream& rStream) +{ + ScDLL::Init(); + SfxMedium aMedium; + css::uno::Reference<css::io::XInputStream> xStm(new utl::OInputStreamWrapper(rStream)); + aMedium.GetItemSet()->Put(SfxUnoAnyItem(SID_INPUTSTREAM, css::uno::Any(xStm))); + + ScDocShellRef xDocShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + + xDocShell->DoInitNew(); + + ScDocument& rDoc = xDocShell->GetDocument(); + + ScDocOptions aDocOpt = rDoc.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + rDoc.SetDocOptions(aDocOpt); + rDoc.MakeTable(0); + rDoc.EnableExecuteLink(false); + rDoc.SetInsertingFromOtherDoc(true); + rDoc.InitDrawLayer(xDocShell.get()); + bool bRet(false); + try + { + bRet = ScFormatFilter::Get().ScImportExcel(aMedium, &rDoc, EIF_AUTO) == ERRCODE_NONE; + } + catch (const css::ucb::ContentCreationException&) + { + } + catch (const std::out_of_range&) + { + } + xDocShell->DoClose(); + xDocShell.clear(); + return bRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportDIF(SvStream &rStream) +{ + ScDLL::Init(); + ScDocument aDocument; + ScDocOptions aDocOpt = aDocument.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + aDocument.SetDocOptions(aDocOpt); + aDocument.MakeTable(0); + aDocument.EnableExecuteLink(false); + aDocument.SetInsertingFromOtherDoc(true); + return ScFormatFilter::Get().ScImportDif(rStream, &aDocument, ScAddress(0, 0, 0), RTL_TEXTENCODING_IBM_850) == ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excform.cxx b/sc/source/filter/excel/excform.cxx new file mode 100644 index 000000000..d217c9622 --- /dev/null +++ b/sc/source/filter/excel/excform.cxx @@ -0,0 +1,1911 @@ +/* -*- 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 <excform.hxx> + +#include <formulacell.hxx> +#include <document.hxx> +#include <scmatrix.hxx> + +#include <formula/errorcodes.hxx> +#include <svl/sharedstringpool.hxx> +#include <sal/log.hxx> + +#include <imp_op.hxx> +#include <namebuff.hxx> +#include <root.hxx> +#include <xltracer.hxx> +#include <xihelper.hxx> +#include <xiname.hxx> +#include <xistyle.hxx> +#include <documentimport.hxx> + +using ::std::vector; + +const sal_uInt16 ExcelToSc::nRowMask = 0x3FFF; + +void ImportExcel::Formula25() +{ + XclAddress aXclPos; + sal_uInt16 nXF = 0, nFormLen; + double fCurVal; + bool bShrFmla; + + aIn >> aXclPos; + + if( GetBiff() == EXC_BIFF2 ) + {// BIFF2 + aIn.Ignore( 3 ); + + fCurVal = aIn.ReadDouble(); + aIn.Ignore( 1 ); + nFormLen = aIn.ReaduInt8(); + bShrFmla = false; + } + else + {// BIFF5 + sal_uInt8 nFlag0; + nXF = aIn.ReaduInt16(); + fCurVal = aIn.ReadDouble(); + nFlag0 = aIn.ReaduInt8(); + aIn.Ignore( 5 ); + + nFormLen = aIn.ReaduInt16(); + + bShrFmla = nFlag0 & 0x08; // shared or not shared + } + + Formula( aXclPos, nXF, nFormLen, fCurVal, bShrFmla ); +} + +void ImportExcel::Formula3() +{ + Formula4(); +} + +void ImportExcel::Formula4() +{ + XclAddress aXclPos; + + aIn >> aXclPos; + sal_uInt16 nXF = aIn.ReaduInt16(); + double fCurVal = aIn.ReadDouble(); + aIn.Ignore( 2 ); + sal_uInt16 nFormLen = aIn.ReaduInt16(); + + Formula( aXclPos, nXF, nFormLen, fCurVal, false ); +} + +void ImportExcel::Formula( + const XclAddress& rXclPos, sal_uInt16 nXF, sal_uInt16 nFormLen, double fCurVal, bool bShrFmla) +{ + if (!nFormLen) + return; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if (!GetAddressConverter().ConvertAddress(aScPos, rXclPos, GetCurrScTab(), true)) + // Conversion failed. + return; + + // Formula will be read next, length in nFormLen + std::unique_ptr<ScTokenArray> pResult; + + pFormConv->Reset( aScPos ); + ScDocumentImport& rDoc = GetDocImport(); + + if (bShrFmla) + { + // This is a shared formula. Get the token array from the shared formula pool. + SCCOL nSharedCol; + SCROW nSharedRow; + if (ExcelToSc::ReadSharedFormulaPosition(maStrm, nSharedCol, nSharedRow)) + { + ScAddress aRefPos(nSharedCol, nSharedRow, GetCurrScTab()); + const ScTokenArray* pSharedCode = pFormConv->GetSharedFormula(aRefPos); + if (pSharedCode) + { + ScFormulaCell* pCell; + pCell = new ScFormulaCell(rD, aScPos, pSharedCode->Clone()); + pCell->GetCode()->WrapReference(aScPos, EXC_MAXCOL8, EXC_MAXROW8); + rDoc.getDoc().EnsureTable(aScPos.Tab()); + rDoc.setFormulaCell(aScPos, pCell); + pCell->SetNeedNumberFormat(false); + if (std::isfinite(fCurVal)) + pCell->SetResultDouble(fCurVal); + + GetXFRangeBuffer().SetXF(aScPos, nXF); + SetLastFormula(aScPos.Col(), aScPos.Row(), fCurVal, nXF, pCell); + } + else + { + // Shared formula not found even though it's clearly a shared formula. + // The cell will be created in the following shared formula + // record. + SetLastFormula(aScPos.Col(), aScPos.Row(), fCurVal, nXF, nullptr); + } + return; + } + } + + ConvErr eErr = pFormConv->Convert( pResult, maStrm, nFormLen, true ); + + ScFormulaCell* pCell = nullptr; + + if (pResult) + { + pCell = new ScFormulaCell(rDoc.getDoc(), aScPos, std::move(pResult)); + pCell->GetCode()->WrapReference(aScPos, EXC_MAXCOL8, EXC_MAXROW8); + rDoc.getDoc().CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + rDoc.getDoc().EnsureTable(aScPos.Tab()); + rDoc.setFormulaCell(aScPos, pCell); + SetLastFormula(aScPos.Col(), aScPos.Row(), fCurVal, nXF, pCell); + } + else + { + pCell = rDoc.getDoc().GetFormulaCell(aScPos); + if (pCell) + pCell->AddRecalcMode( ScRecalcMode::ONLOAD_ONCE ); + } + + if (pCell) + { + pCell->SetNeedNumberFormat(false); + if( eErr != ConvErr::OK ) + ExcelToSc::SetError( *pCell, eErr ); + + if (std::isfinite(fCurVal)) + pCell->SetResultDouble(fCurVal); + } + + GetXFRangeBuffer().SetXF(aScPos, nXF); +} + +ExcelToSc::ExcelToSc( XclImpRoot& rRoot ) : + ExcelConverterBase(rRoot.GetDocImport().getDoc().GetSharedStringPool()), + XclImpRoot( rRoot ), + maFuncProv( rRoot ), + meBiff( rRoot.GetBiff() ) +{ +} + +ExcelToSc::~ExcelToSc() +{ +} + +std::unique_ptr<ScTokenArray> ExcelToSc::GetDummy() +{ + aPool.Store( "Dummy()" ); + aPool >> aStack; + return aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); +} + +// if bAllowArrays is false stream seeks to first byte after <nFormulaLen> +// otherwise it will seek to the first byte after the additional content (eg +// inline arrays) following <nFormulaLen> +ConvErr ExcelToSc::Convert( std::unique_ptr<ScTokenArray>& pResult, XclImpStream& aIn, std::size_t nFormulaLen, bool bAllowArrays, const FORMULA_TYPE eFT ) +{ + RootData& rR = GetOldRoot(); + sal_uInt8 nOp, nLen; + bool bError = false; + bool bArrayFormula = false; + TokenId nBuf0; + const bool bRangeName = eFT == FT_RangeName; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bConditional = eFT == FT_CondFormat; + const bool bRNorSF = bRangeName || bSharedFormula || bConditional; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + ExtensionTypeVec aExtensions; + + if( nFormulaLen == 0 ) + { + aPool.Store( "-/-" ); + aPool >> aStack; + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + return ConvErr::OK; + } + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + nOp = aIn.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + case 0x02: // Data Table [325 277] + { + sal_uInt16 nUINT16 = 3; + + if( meBiff != EXC_BIFF2 ) + nUINT16++; + + aIn.Ignore( nUINT16 ); + + bArrayFormula = true; + break; + } + case 0x03: // Addition [312 264] + aStack >> nBuf0; + aPool << aStack << ocAdd << nBuf0; + aPool >> aStack; + break; + case 0x04: // Subtraction [313 264] + // SECOND-TOP minus TOP + aStack >> nBuf0; + aPool << aStack << ocSub << nBuf0; + aPool >> aStack; + break; + case 0x05: // Multiplication [313 264] + aStack >> nBuf0; + aPool << aStack << ocMul << nBuf0; + aPool >> aStack; + break; + case 0x06: // Division [313 264] + // divide TOP by SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocDiv << nBuf0; + aPool >> aStack; + break; + case 0x07: // Exponentiation [313 265] + // raise SECOND-TOP to power of TOP + aStack >> nBuf0; + aPool << aStack << ocPow << nBuf0; + aPool >> aStack; + break; + case 0x08: // Concatenation [313 265] + // append TOP to SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocAmpersand << nBuf0; + aPool >> aStack; + break; + case 0x09: // Less Than [313 265] + // SECOND-TOP < TOP + aStack >> nBuf0; + aPool << aStack << ocLess << nBuf0; + aPool >> aStack; + break; + case 0x0A: // Less Than or Equal [313 265] + // SECOND-TOP <= TOP + aStack >> nBuf0; + aPool << aStack << ocLessEqual << nBuf0; + aPool >> aStack; + break; + case 0x0B: // Equal [313 265] + // SECOND-TOP == TOP + aStack >> nBuf0; + aPool << aStack << ocEqual << nBuf0; + aPool >> aStack; + break; + case 0x0C: // Greater Than or Equal [313 265] + // SECOND-TOP >= TOP + aStack >> nBuf0; + aPool << aStack << ocGreaterEqual << nBuf0; + aPool >> aStack; + break; + case 0x0D: // Greater Than [313 265] + // SECOND-TOP > TOP + aStack >> nBuf0; + aPool << aStack << ocGreater << nBuf0; + aPool >> aStack; + break; + case 0x0E: // Not Equal [313 265] + // SECOND-TOP != TOP + aStack >> nBuf0; + aPool << aStack << ocNotEqual << nBuf0; + aPool >> aStack; + break; + case 0x0F: // Intersection [314 265] + aStack >> nBuf0; + aPool << aStack << ocIntersect << nBuf0; + aPool >> aStack; + break; + case 0x10: // Union [314 265] + // ocSep instead of 'ocUnion' + aStack >> nBuf0; + aPool << aStack << ocSep << nBuf0; + // doesn't fit exactly, but is more Excel-like + aPool >> aStack; + break; + case 0x11: // Range [314 265] + aStack >> nBuf0; + aPool << aStack << ocRange << nBuf0; + aPool >> aStack; + break; + case 0x12: // Unary Plus [312 264] + aPool << ocAdd << aStack; + aPool >> aStack; + break; + case 0x13: // Unary Minus [312 264] + aPool << ocNegSub << aStack; + aPool >> aStack; + break; + case 0x14: // Percent Sign [312 264] + aPool << aStack << ocPercentSign; + aPool >> aStack; + break; + case 0x15: // Parenthesis [326 278] + aPool << ocOpen << aStack << ocClose; + aPool >> aStack; + break; + case 0x16: // Missing Argument [314 266] + aPool << ocMissing; + aPool >> aStack; + GetTracer().TraceFormulaMissingArg(); + break; + case 0x17: // String Constant [314 266] + { + nLen = aIn.ReaduInt8(); + OUString aString = aIn.ReadRawByteString( nLen ); + + aStack << aPool.Store( aString ); + break; + } + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + + if( meBiff == EXC_BIFF2 ) + { + nData = aIn.ReaduInt8(); + nFactor = 1; + } + else + { + nData = aIn.ReaduInt16(); + nFactor = 2; + } + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + ++nData; + aIn.Ignore(static_cast<std::size_t>(nData) * nFactor); + } + else if( nOpt & 0x10 ) // AttrSum + DoMulArgs( ocSum, 1 ); + } + break; + case 0x1A: // External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: aIn.Ignore( 7 ); break; + case EXC_BIFF3: + case EXC_BIFF4: aIn.Ignore( 10 ); break; + case EXC_BIFF5: + SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1A does not exist in Biff5!" ); + [[fallthrough]]; + default: + SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1B: // End External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: aIn.Ignore( 3 ); break; + case EXC_BIFF3: + case EXC_BIFF4: aIn.Ignore( 4 ); break; + case EXC_BIFF5: + SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1B does not exist in Biff5!" ); + [[fallthrough]]; + default: + SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1C: // Error Value [314 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + DefTokenId eOc; + switch( nByte ) + { + case EXC_ERR_NULL: + case EXC_ERR_DIV0: + case EXC_ERR_VALUE: + case EXC_ERR_REF: + case EXC_ERR_NAME: + case EXC_ERR_NUM: eOc = ocStop; break; + case EXC_ERR_NA: eOc = ocNotAvail; break; + default: eOc = ocNoName; + } + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + aPool >> aStack; + break; + } + case 0x1D: // Boolean [315 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + if( nByte == 0 ) + aPool << ocFalse << ocOpen << ocClose; + else + aPool << ocTrue << ocOpen << ocClose; + aPool >> aStack; + break; + } + case 0x1E: // Integer [315 266] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + aStack << aPool.Store( static_cast<double>(nUINT16) ); + break; + } + case 0x1F: // Number [315 266] + { + double fDouble = aIn.ReadDouble(); + aStack << aPool.Store( fDouble ); + break; + } + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + { + aIn.Ignore( (meBiff == EXC_BIFF2) ? 6 : 7 ); + if( bAllowArrays ) + { + aStack << aPool.StoreMatrix(); + aExtensions.push_back( EXTENSION_ARRAY ); + } + else + { + aPool << ocBad; + aPool >> aStack; + } + break; + } + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + { + sal_uInt16 nXclFunc; + if( meBiff <= EXC_BIFF3 ) + nXclFunc = aIn.ReaduInt8(); + else + nXclFunc = aIn.ReaduInt16(); + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, pFuncInfo->mnMaxParamCount ); + else + DoMulArgs( ocNoName, 0 ); + } + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + { + sal_uInt16 nXclFunc; + sal_uInt8 nParamCount; + nParamCount = aIn.ReaduInt8(); + nParamCount &= 0x7F; + if( meBiff <= EXC_BIFF3 ) + nXclFunc = aIn.ReaduInt8(); + else + nXclFunc = aIn.ReaduInt16(); + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, nParamCount ); + else + DoMulArgs( ocNoName, 0 ); + } + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + switch( meBiff ) + { + case EXC_BIFF2: aIn.Ignore( 5 ); break; + case EXC_BIFF3: + case EXC_BIFF4: aIn.Ignore( 8 ); break; + case EXC_BIFF5: aIn.Ignore( 12 ); break; + default: + OSL_FAIL( + "-ExcelToSc::Convert(): A little oblivious?" ); + } + const XclImpName* pName = GetNameManager().GetName( nUINT16 ); + if(pName && !pName->GetScRangeData()) + aStack << aPool.Store( ocMacro, pName->GetXclName() ); + else + aStack << aPool.StoreName(nUINT16, -1); + } + break; + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); + aSRD.SetAbsCol(static_cast<SCCOL>(nByte)); + aSRD.SetAbsRow(nUINT16 & 0x3FFF); + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRangeName ); + + switch ( nOp ) + { + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + + aStack << aPool.Store( aSRD ); + break; + } + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + ScSingleRefData& rSRef1 = aCRD.Ref1; + ScSingleRefData& rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName ); + rSRef2.SetFlag3D( bRangeName ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + // no information which part is deleted, set all + rSRef1.SetColDeleted( true ); + rSRef1.SetRowDeleted( true ); + rSRef2.SetColDeleted( true ); + rSRef2.SetRowDeleted( true ); + } + + aStack << aPool.Store( aCRD ); + } + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + aExtensions.push_back( EXTENSION_MEMAREA ); + [[fallthrough]]; + + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + aIn.Ignore( (meBiff == EXC_BIFF2) ? 4 : 6 ); + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); // >> Attribute, Row >> Col + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRNorSF ); + + aStack << aPool.Store( aSRD ); + break; + } + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8( ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + aStack << aPool.Store( aCRD ); + } + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + aIn.Ignore( (meBiff == EXC_BIFF2) ? 1 : 2 ); + break; + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + { + OUString aString = "COMM_EQU_FUNC"; + sal_uInt8 nByte = aIn.ReaduInt8(); + aString += OUString::number( nByte ); + nByte = aIn.ReaduInt8(); + aStack << aPool.Store( aString ); + DoMulArgs( ocPush, nByte + 1 ); + break; + } + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + { + sal_Int16 nINT16 = aIn.ReadInt16(); + aIn.Ignore( 8 ); + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + if( nINT16 >= 0 ) + { + aPool << ocBad; + aPool >> aStack; + } + else + aStack << aPool.StoreName( nUINT16, -1 ); + aIn.Ignore( 12 ); + break; + } + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + { + sal_uInt16 nTabFirst, nTabLast, nRow; + sal_Int16 nExtSheet; + sal_uInt8 nCol; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + { // from external + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a SingleRef + } + } + + if( nExtSheet <= 0 ) + { // in current Workbook + aSRD.SetAbsTab(nTabFirst); + aSRD.SetFlag3D(true); + + ExcRelToScRel( nRow, nCol, aSRD, bRangeName ); + + switch ( nOp ) + { + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + if ( !ValidTab(static_cast<SCTAB>(nTabFirst)) ) + aSRD.SetTabDeleted( true ); + + if( nTabLast != nTabFirst ) + { + aCRD.Ref1 = aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nTabLast); + aCRD.Ref2.SetTabDeleted( !ValidTab(static_cast<SCTAB>(nTabLast)) ); + aStack << aPool.Store( aCRD ); + } + else + aStack << aPool.Store( aSRD ); + } + } + + break; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + { + sal_uInt16 nTabFirst, nTabLast, nRowFirst, nRowLast; + sal_Int16 nExtSheet; + sal_uInt8 nColFirst, nColLast; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + // from external + { + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a CompleteRef + } + } + + if( nExtSheet <= 0 ) + {// in current Workbook + // first part of range + ScSingleRefData& rR1 = aCRD.Ref1; + ScSingleRefData& rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nTabFirst); + rR2.SetAbsTab(nTabLast); + rR1.SetFlag3D(true); + rR2.SetFlag3D( nTabFirst != nTabLast ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + // no information which part is deleted, set all + rR1.SetColDeleted( true ); + rR1.SetRowDeleted( true ); + rR2.SetColDeleted( true ); + rR2.SetRowDeleted( true ); + } + if ( !ValidTab(static_cast<SCTAB>(nTabFirst)) ) + rR1.SetTabDeleted( true ); + if ( !ValidTab(static_cast<SCTAB>(nTabLast)) ) + rR2.SetTabDeleted( true ); + + aStack << aPool.Store( aCRD ); + }//END in current Workbook + } + break; + default: bError = true; + } + bError |= !aIn.IsValid(); + } + + ConvErr eRet; + + if( bError ) + { + aPool << ocBad; + aPool >> aStack; + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Ni; + } + else if( aIn.GetRecPos() != nEndPos ) + { + aPool << ocBad; + aPool >> aStack; + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Count; + } + else if( bArrayFormula ) + { + pResult = nullptr; + eRet = ConvErr::OK; + } + else + { + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::OK; + } + + aIn.Seek( nEndPos ); + + if( eRet == ConvErr::OK ) + ReadExtensions( aExtensions, aIn ); + + return eRet; +} + +// stream seeks to first byte after <nFormulaLen> +ConvErr ExcelToSc::Convert( ScRangeListTabs& rRangeList, XclImpStream& aIn, std::size_t nFormulaLen, + SCTAB nTab, const FORMULA_TYPE eFT ) +{ + RootData& rR = GetOldRoot(); + sal_uInt8 nOp, nLen; + bool bError = false; + const bool bRangeName = eFT == FT_RangeName; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bRNorSF = bRangeName || bSharedFormula; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + aCRD.Ref1.SetAbsTab(aEingPos.Tab()); + aCRD.Ref2.SetAbsTab(aEingPos.Tab()); + + if( nFormulaLen == 0 ) + return ConvErr::OK; + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + nOp = aIn.ReaduInt8(); + std::size_t nIgnore = 0; + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + nIgnore = (meBiff == EXC_BIFF2) ? 3 : 4; + break; + case 0x02: // Data Table [325 277] + nIgnore = (meBiff == EXC_BIFF2) ? 3 : 4; + break; + case 0x03: // Addition [312 264] + case 0x04: // Subtraction [313 264] + case 0x05: // Multiplication [313 264] + case 0x06: // Division [313 264] + case 0x07: // Exponetiation [313 265] + case 0x08: // Concatenation [313 265] + case 0x09: // Less Than [313 265] + case 0x0A: // Less Than or Equal [313 265] + case 0x0B: // Equal [313 265] + case 0x0C: // Greater Than or Equal [313 265] + case 0x0D: // Greater Than [313 265] + case 0x0E: // Not Equal [313 265] + case 0x0F: // Intersection [314 265] + case 0x10: // Union [314 265] + case 0x11: // Range [314 265] + case 0x12: // Unary Plus [312 264] + case 0x13: // Unary Minus [312 264] + case 0x14: // Percent Sign [312 264] + case 0x15: // Parenthesis [326 278] + case 0x16: // Missing Argument [314 266] + break; + case 0x17: // String Constant [314 266] + nLen = aIn.ReaduInt8(); + nIgnore = nLen; + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + + if( meBiff == EXC_BIFF2 ) + { + nData = aIn.ReaduInt8(); + nFactor = 1; + } + else + { + nData = aIn.ReaduInt16(); + nFactor = 2; + } + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + ++nData; + aIn.Ignore(static_cast<std::size_t>(nData) * nFactor); + } + } + break; + case 0x1A: // External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: nIgnore = 7; break; + case EXC_BIFF3: + case EXC_BIFF4: nIgnore = 10; break; + case EXC_BIFF5: SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1A does not exist in Biff5!" ); + [[fallthrough]]; + default: SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1B: // End External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: nIgnore = 3; break; + case EXC_BIFF3: + case EXC_BIFF4: nIgnore = 4; break; + case EXC_BIFF5: SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1B does not exist in Biff5!" ); + [[fallthrough]]; + default: SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + nIgnore = 1; + break; + case 0x1E: // Integer [315 266] + nIgnore = 2; + break; + case 0x1F: // Number [315 266] + nIgnore = 8; + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + nIgnore = (meBiff == EXC_BIFF2) ? 6 : 7; + break; + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + nIgnore = (meBiff <= EXC_BIFF3) ? 1 : 2; + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + nIgnore = (meBiff <= EXC_BIFF3) ? 2 : 3; + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + switch( meBiff ) + { + case EXC_BIFF2: nIgnore = 7; break; + case EXC_BIFF3: + case EXC_BIFF4: nIgnore = 10; break; + case EXC_BIFF5: nIgnore = 14; break; + default: OSL_FAIL( "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); + aSRD.SetAbsCol(static_cast<SCCOL>(nByte)); + aSRD.SetAbsRow(nUINT16 & 0x3FFF); + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRangeName ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + break; + } + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + ScSingleRefData &rSRef1 = aCRD.Ref1; + ScSingleRefData &rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName ); + rSRef2.SetFlag3D( bRangeName ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + nIgnore = (meBiff == EXC_BIFF2) ? 4 : 6; + break; + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + nIgnore = 3; + break; + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + nIgnore = 6; + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); // >> Attribute, Row >> Col + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRNorSF ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + break; + } + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + nIgnore = (meBiff == EXC_BIFF2) ? 1 : 2; + break; + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + nIgnore = 2; + break; + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + nIgnore = 24; + break; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + { + sal_uInt16 nTabFirst, nTabLast, nRow; + sal_Int16 nExtSheet; + sal_uInt8 nCol; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + // from external + { + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a SingleRef + } + } + + if( nExtSheet <= 0 ) + {// in current Workbook + bool b3D = ( static_cast<SCTAB>(nTabFirst) != aEingPos.Tab() ) || bRangeName; + aSRD.SetAbsTab(nTabFirst); + aSRD.SetFlag3D( b3D ); + + ExcRelToScRel( nRow, nCol, aSRD, bRangeName ); + + if( nTabLast != nTabFirst ) + { + aCRD.Ref1 = aSRD; + aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(static_cast<SCTAB>(nTabLast)); + b3D = ( static_cast<SCTAB>(nTabLast) != aEingPos.Tab() ); + aCRD.Ref2.SetFlag3D( b3D ); + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + else + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + } + + break; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + { + sal_uInt16 nTabFirst, nTabLast, nRowFirst, nRowLast; + sal_Int16 nExtSheet; + sal_uInt8 nColFirst, nColLast; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + // from external + { + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a CompleteRef + } + } + + if( nExtSheet <= 0 ) + {// in current Workbook + // first part of range + ScSingleRefData &rR1 = aCRD.Ref1; + ScSingleRefData &rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nTabFirst); + rR2.SetAbsTab(nTabLast); + rR1.SetFlag3D( ( static_cast<SCTAB>(nTabFirst) != aEingPos.Tab() ) || bRangeName ); + rR2.SetFlag3D( ( static_cast<SCTAB>(nTabLast) != aEingPos.Tab() ) || bRangeName ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + }//END in current Workbook + } + break; + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + nIgnore = 17; + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + nIgnore = 20; + break; + default: bError = true; + } + bError |= !aIn.IsValid(); + + aIn.Ignore( nIgnore ); + } + + ConvErr eRet; + + if( bError ) + eRet = ConvErr::Ni; + else if( aIn.GetRecPos() != nEndPos ) + eRet = ConvErr::Count; + else + eRet = ConvErr::OK; + + aIn.Seek( nEndPos ); + return eRet; +} + +void ExcelToSc::ConvertExternName( std::unique_ptr<ScTokenArray>& /*rpArray*/, XclImpStream& /*rStrm*/, std::size_t /*nFormulaLen*/, + const OUString& /*rUrl*/, const vector<OUString>& /*rTabNames*/ ) +{ +} + +void ExcelToSc::GetAbsRefs( ScRangeList& rRangeList, XclImpStream& rStrm, std::size_t nLen ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF5 ); + if( GetBiff() != EXC_BIFF5 ) + return; + + sal_uInt8 nOp; + sal_uInt16 nRow1, nRow2; + sal_uInt8 nCol1, nCol2; + SCTAB nTab1, nTab2; + sal_uInt16 nTabFirst, nTabLast; + sal_Int16 nRefIdx; + + std::size_t nSeek; + std::size_t nEndPos = rStrm.GetRecPos() + nLen; + + while( rStrm.IsValid() && (rStrm.GetRecPos() < nEndPos) ) + { + nOp = rStrm.ReaduInt8(); + nSeek = 0; + + switch( nOp ) + { + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + nRow1 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + + nRow2 = nRow1; + nCol2 = nCol1; + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + // Area Reference Within a Shared Formula[ 274] + nRow1 = rStrm.ReaduInt16(); + nRow2 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + nCol2 = rStrm.ReaduInt8(); + + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + nRefIdx = rStrm.ReadInt16(); + rStrm.Ignore( 8 ); + nTabFirst = rStrm.ReaduInt16(); + nTabLast = rStrm.ReaduInt16(); + nRow1 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + + nRow2 = nRow1; + nCol2 = nCol1; + + goto _3d_common; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + nRefIdx = rStrm.ReadInt16(); + rStrm.Ignore( 8 ); + nTabFirst = rStrm.ReaduInt16(); + nTabLast = rStrm.ReaduInt16(); + nRow1 = rStrm.ReaduInt16(); + nRow2 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + nCol2 = rStrm.ReaduInt8(); + + _3d_common: + nTab1 = static_cast< SCTAB >( nTabFirst ); + nTab2 = static_cast< SCTAB >( nTabLast ); + + // skip references to deleted sheets + if( (nRefIdx >= 0) || !ValidTab( nTab1 ) || (nTab1 != nTab2) ) + break; + + goto _common; + _common: + // do not check abs/rel flags, linked controls have set them! + { + ScRange aScRange; + nRow1 &= 0x3FFF; + nRow2 &= 0x3FFF; + if( GetAddressConverter().ConvertRange( aScRange, XclRange( nCol1, nRow1, nCol2, nRow2 ), nTab1, nTab2, true ) ) + rRangeList.push_back( aScRange ); + } + break; + + case 0x03: // Addition [312 264] + case 0x04: // Subtraction [313 264] + case 0x05: // Multiplication [313 264] + case 0x06: // Division [313 264] + case 0x07: // Exponetiation [313 265] + case 0x08: // Concatenation [313 265] + case 0x09: // Less Than [313 265] + case 0x0A: // Less Than or Equal [313 265] + case 0x0B: // Equal [313 265] + case 0x0C: // Greater Than or Equal [313 265] + case 0x0D: // Greater Than [313 265] + case 0x0E: // Not Equal [313 265] + case 0x0F: // Intersection [314 265] + case 0x10: // Union [314 265] + case 0x11: // Range [314 265] + case 0x12: // Unary Plus [312 264] + case 0x13: // Unary Minus [312 264] + case 0x14: // Percent Sign [312 264] + case 0x15: // Parenthesis [326 278] + case 0x16: // Missing Argument [314 266] + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + nSeek = 1; + break; + case 0x1E: // Integer [315 266] + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + nSeek = 2; + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + nSeek = 3; + break; + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + case 0x02: // Data Table [325 277] + nSeek = 4; + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + nSeek = 6; + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + nSeek = 7; + break; + case 0x1F: // Number [315 266] + nSeek = 8; + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + nSeek = 14; + break; + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + nSeek = 17; + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + nSeek = 20; + break; + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + nSeek = 24; + break; + case 0x17: // String Constant [314 266] + nSeek = rStrm.ReaduInt8(); + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt8 nOpt; + sal_uInt16 nData; + nOpt = rStrm.ReaduInt8(); + nData = rStrm.ReaduInt16(); + if( nOpt & 0x04 ) + nSeek = nData * 2 + 2; + } + break; + } + + rStrm.Ignore( nSeek ); + } + rStrm.Seek( nEndPos ); +} + +void ExcelToSc::DoMulArgs( DefTokenId eId, sal_uInt8 nCnt ) +{ + TokenId eParam[ 256 ]; + sal_Int32 nPass; + + if( eId == ocCeil || eId == ocFloor ) + { + aStack << aPool.Store( 1.0 ); // default, because not present in Excel + nCnt++; + } + + for( nPass = 0; aStack.HasMoreTokens() && (nPass < nCnt); nPass++ ) + aStack >> eParam[ nPass ]; + // #i70925# reduce parameter count, if no more tokens available on token stack + if( nPass < nCnt ) + nCnt = static_cast< sal_uInt8 >( nPass ); + + if( nCnt > 0 && eId == ocExternal ) + { + TokenId n = eParam[ nCnt - 1 ]; +//##### ADJUST STUPIDITY FOR BASIC-FUNCS! + if( const OUString* pExt = aPool.GetExternal( n ) ) + { + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclMacroName( *pExt ) ) + aPool << pFuncInfo->meOpCode; + else + aPool << n; + nCnt--; + } + else + aPool << eId; + } + else + aPool << eId; + + aPool << ocOpen; + + if( nCnt > 0 ) + { + // attention: 0 = last parameter, nCnt-1 = first parameter + sal_Int16 nSkipEnd = -1; // skip all parameters <= nSkipEnd + + sal_Int16 nLast = nCnt - 1; + + // functions for which parameters have to be skipped + if( eId == ocPercentrank && nCnt == 3 ) + nSkipEnd = 0; // skip last parameter if necessary + + // Joost special cases + else if( eId == ocIf ) + { + sal_uInt16 nNullParam = 0; + for( nPass = 0 ; nPass < nCnt ; nPass++ ) + { + if( aPool.IsSingleOp( eParam[ nPass ], ocMissing ) ) + { + if( !nNullParam ) + nNullParam = static_cast<sal_uInt16>(aPool.Store( 0.0 )); + eParam[ nPass ] = nNullParam; + } + } + } + + // [Parameter{;Parameter}] + if( nLast > nSkipEnd ) + { + // nSkipEnd is either 0 or -1 => nLast >= 0 + aPool << eParam[ nLast ]; + for( nPass = nLast - 1 ; nPass > nSkipEnd ; nPass-- ) + { + // nPass > nSkipEnd => nPass >= 0 + aPool << ocSep << eParam[nPass]; + } + } + } + aPool << ocClose; + + aPool >> aStack; +} + +void ExcelToSc::ExcRelToScRel( sal_uInt16 nRow, sal_uInt8 nCol, ScSingleRefData &rSRD, const bool bName ) +{ + if( bName ) + { + // C O L + if( nRow & 0x4000 ) + rSRD.SetRelCol(nCol); + else + rSRD.SetAbsCol(nCol); + + // R O W + if( nRow & 0x8000 ) + {// rel Row + if( nRow & 0x2000 ) // Bit 13 set? + // Row negative + rSRD.SetRelRow(nRow | 0xC000); + else + // Row positive + rSRD.SetRelRow(nRow & nRowMask); + } + else + {// abs Row + rSRD.SetAbsRow(nRow & nRowMask); + } + + // T A B + // abs needed if rel in shared formula for ScCompiler UpdateNameReference + if ( rSRD.IsTabRel() && !rSRD.IsFlag3D() ) + rSRD.SetAbsTab(GetCurrScTab()); + } + else + { + bool bColRel = (nRow & 0x4000) > 0; + bool bRowRel = (nRow & 0x8000) > 0; + + if (bColRel) + rSRD.SetRelCol(nCol - aEingPos.Col()); + else + rSRD.SetAbsCol(nCol); + + rSRD.SetAbsRow(nRow & nRowMask); + if (bRowRel) + rSRD.SetRelRow(rSRD.Row() - aEingPos.Row()); + + // T A B + // #i10184# abs needed if rel in shared formula for ScCompiler UpdateNameReference + if ( rSRD.IsTabRel() && !rSRD.IsFlag3D() ) + rSRD.SetAbsTab(GetCurrScTab() + rSRD.Tab()); + } +} + +std::unique_ptr<ScTokenArray> ExcelToSc::GetBoolErr( XclBoolError eType ) +{ + FormulaError nError; + aPool.Reset(); + aStack.Reset(); + + DefTokenId eOc; + + switch( eType ) + { + case xlErrNull: eOc = ocStop; nError = FormulaError::NoCode; break; + case xlErrDiv0: eOc = ocStop; nError = FormulaError::DivisionByZero; break; + case xlErrValue: eOc = ocStop; nError = FormulaError::NoValue; break; + case xlErrRef: eOc = ocStop; nError = FormulaError::NoRef; break; + case xlErrName: eOc = ocStop; nError = FormulaError::NoName; break; + case xlErrNum: eOc = ocStop; nError = FormulaError::IllegalFPOperation; break; + case xlErrNA: eOc = ocNotAvail; nError = FormulaError::NotAvailable; break; + case xlErrTrue: eOc = ocTrue; nError = FormulaError::NONE; break; + case xlErrFalse: eOc = ocFalse; nError = FormulaError::NONE; break; + case xlErrUnknown: eOc = ocStop; nError = FormulaError::UnknownState; break; + default: + OSL_FAIL( "ExcelToSc::GetBoolErr - wrong enum!" ); + eOc = ocNoName; + nError = FormulaError::UnknownState; + } + + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + + aPool >> aStack; + + std::unique_ptr<ScTokenArray> pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + if( nError != FormulaError::NONE ) + pResult->SetCodeError( nError ); + + pResult->SetExclusiveRecalcModeNormal(); + + return pResult; +} + +bool ExcelToSc::ReadSharedFormulaPosition( XclImpStream& rStrm, SCCOL& rCol, SCROW& rRow ) +{ + rStrm.PushPosition(); + + sal_uInt8 nOp; + nOp = rStrm.ReaduInt8(); + + if (nOp != 0x01) // must be PtgExp token. + { + rStrm.PopPosition(); + return false; + } + + sal_uInt16 nRow, nCol; + nRow = rStrm.ReaduInt16(); + nCol = rStrm.ReaduInt16(); + rStrm.PopPosition(); + rCol = nCol; + rRow = nRow; + return true; +} + +const ScTokenArray* ExcelToSc::GetSharedFormula( const ScAddress& rRefPos ) const +{ + return GetOldRoot().pShrfmlaBuff->Find(rRefPos); +} + +void ExcelToSc::SetError( ScFormulaCell &rCell, const ConvErr eErr ) +{ + FormulaError nInd; + + switch( eErr ) + { + case ConvErr::Ni: nInd = FormulaError::UnknownToken; break; + case ConvErr::Count: nInd = FormulaError::CodeOverflow; break; + default: nInd = FormulaError::NoCode; // I had no better idea + } + + rCell.SetErrCode( nInd ); +} + +void ExcelToSc::SetComplCol( ScComplexRefData &rCRD ) +{ + ScSingleRefData &rSRD = rCRD.Ref2; + ScDocument& rDoc = GetDocImport().getDoc(); + if( rSRD.IsColRel() ) + rSRD.SetRelCol(rDoc.MaxCol() - aEingPos.Col()); + else + rSRD.SetAbsCol(rDoc.MaxCol()); +} + +void ExcelToSc::SetComplRow( ScComplexRefData &rCRD ) +{ + ScSingleRefData &rSRD = rCRD.Ref2; + ScDocument& rDoc = GetDocImport().getDoc(); + if( rSRD.IsRowRel() ) + rSRD.SetRelRow(rDoc.MaxRow() - aEingPos.Row()); + else + rSRD.SetAbsRow(rDoc.MaxRow()); +} + +void ExcelToSc::ReadExtensionArray( unsigned int n, XclImpStream& aIn ) +{ + sal_uInt8 nByte = aIn.ReaduInt8(); + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + + SCSIZE nC, nCols; + SCSIZE nR, nRows; + if( GetBiff() == EXC_BIFF8 ) + { + nCols = nByte + 1; + nRows = nUINT16 + 1; + } + else + { + nCols = nByte ? nByte : 256; + nRows = nUINT16; + } + + ScMatrix* pMatrix = aPool.GetMatrix( n ); + + if( nullptr != pMatrix ) + { + pMatrix->Resize(nCols, nRows); + pMatrix->GetDimensions( nC, nR); + if( nC != nCols || nR != nRows ) + { + OSL_FAIL( "ExcelToSc::ReadExtensionArray - matrix size mismatch" ); + pMatrix = nullptr; + } + } + else + { + OSL_FAIL( "ExcelToSc::ReadExtensionArray - missing matrix" ); + } + + //assuming worst case scenario of unknown types + const size_t nMinRecordSize = 1; + const size_t nMaxRows = aIn.GetRecLeft() / (nMinRecordSize * nCols); + if (nRows > nMaxRows) + { + SAL_WARN("sc", "Parsing error: " << nMaxRows << + " max possible rows, but " << nRows << " claimed, truncating"); + nRows = nMaxRows; + } + + svl::SharedStringPool& rPool = GetDoc().GetSharedStringPool(); + for( nR = 0 ; nR < nRows; nR++ ) + { + for( nC = 0 ; nC < nCols; nC++ ) + { + nByte = aIn.ReaduInt8(); + switch( nByte ) + { + case EXC_CACHEDVAL_EMPTY: + aIn.Ignore( 8 ); + if( nullptr != pMatrix ) + { + pMatrix->PutEmpty( nC, nR ); + } + break; + + case EXC_CACHEDVAL_DOUBLE: + { + double fDouble = aIn.ReadDouble(); + if( nullptr != pMatrix ) + { + pMatrix->PutDouble( fDouble, nC, nR ); + } + break; + } + case EXC_CACHEDVAL_STRING: + { + OUString aString; + if( GetBiff() == EXC_BIFF8 ) + { + nUINT16 = aIn.ReaduInt16(); + aString = aIn.ReadUniString( nUINT16 ); + } + else + { + nByte = aIn.ReaduInt8(); + aString = aIn.ReadRawByteString( nByte ); + } + if( nullptr != pMatrix ) + { + pMatrix->PutString(rPool.intern(aString), nC, nR); + } + break; + } + case EXC_CACHEDVAL_BOOL: + nByte = aIn.ReaduInt8(); + aIn.Ignore( 7 ); + if( nullptr != pMatrix ) + { + pMatrix->PutBoolean( nByte != 0, nC, nR ); + } + break; + + case EXC_CACHEDVAL_ERROR: + nByte = aIn.ReaduInt8(); + aIn.Ignore( 7 ); + if( nullptr != pMatrix ) + { + pMatrix->PutError( XclTools::GetScErrorCode( nByte ), nC, nR ); + } + break; + } + } + } +} + +void ExcelToSc::ReadExtensionNlr( XclImpStream& aIn ) +{ + sal_uInt32 nFlags; + nFlags = aIn.ReaduInt32(); + + sal_uInt32 nCount = nFlags & EXC_TOK_NLR_ADDMASK; + aIn.Ignore( nCount * 4 ); // Drop the cell positions +} + +void ExcelToSc::ReadExtensionMemArea( XclImpStream& aIn ) +{ + sal_uInt16 nCount = aIn.ReaduInt16(); + + aIn.Ignore( static_cast<std::size_t>(nCount) * ((GetBiff() == EXC_BIFF8) ? 8 : 6) ); // drop the ranges +} + +void ExcelToSc::ReadExtensions( const ExtensionTypeVec& rExtensions, + XclImpStream& aIn ) +{ + unsigned int nArray = 0; + + for(int eType : rExtensions) + { + switch( eType ) + { + case EXTENSION_ARRAY: + ReadExtensionArray( nArray++, aIn ); + break; + + case EXTENSION_NLR: + ReadExtensionNlr( aIn ); + break; + + case EXTENSION_MEMAREA: + ReadExtensionMemArea( aIn ); + break; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excform8.cxx b/sc/source/filter/excel/excform8.cxx new file mode 100644 index 000000000..62e184204 --- /dev/null +++ b/sc/source/filter/excel/excform8.cxx @@ -0,0 +1,1671 @@ +/* -*- 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 <excform.hxx> + +#include <document.hxx> +#include <documentimport.hxx> +#include <xltracer.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xilink.hxx> +#include <xiname.hxx> + +#include <externalrefmgr.hxx> + +#include <cstring> + +#include <o3tl/safeint.hxx> + +using ::std::vector; + +namespace { + +/** + * Extract a file path from OLE link path. An OLE link path is expected to + * be in the following format: + * + * Excel.Sheet.8 \3 [file path] + */ +bool extractFilePath(const OUString& rUrl, OUString& rPath) +{ + const char* prefix = "Excel.Sheet.8\3"; + size_t nPrefixLen = ::std::strlen(prefix); + + sal_Int32 n = rUrl.getLength(); + if (n <= static_cast<sal_Int32>(nPrefixLen)) + // needs to have the specified prefix. + return false; + + OUStringBuffer aBuf; + const sal_Unicode* p = rUrl.getStr(); + for (size_t i = 0; i < o3tl::make_unsigned(n); ++i, ++p) + { + if (i < nPrefixLen) + { + sal_Unicode pc = static_cast<sal_Unicode>(*prefix++); + if (pc != *p) + return false; + + continue; + } + aBuf.append(*p); + } + + rPath = aBuf.makeStringAndClear(); + return true; +} + +} + +ExcelToSc8::ExternalTabInfo::ExternalTabInfo() : + mnFileId(0), mbExternal(false) +{ +} + +ExcelToSc8::ExcelToSc8( XclImpRoot& rRoot ) : + ExcelToSc( rRoot ), + rLinkMan( rRoot.GetLinkManager() ) +{ +} + +ExcelToSc8::~ExcelToSc8() +{ +} + +bool ExcelToSc8::GetExternalFileIdFromXti( sal_uInt16 nIxti, sal_uInt16& rFileId ) const +{ + const OUString* pFileUrl = rLinkMan.GetSupbookUrl(nIxti); + if (!pFileUrl || pFileUrl->isEmpty() || !GetDocShell()) + return false; + + OUString aFileUrl = ScGlobal::GetAbsDocName(*pFileUrl, GetDocShell()); + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + rFileId = pRefMgr->getExternalFileId(aFileUrl); + + return true; +} + +bool ExcelToSc8::Read3DTabReference( sal_uInt16 nIxti, SCTAB& rFirstTab, SCTAB& rLastTab, ExternalTabInfo& rExtInfo ) +{ + rFirstTab = rLastTab = 0; + rExtInfo.mbExternal = !rLinkMan.IsSelfRef(nIxti); + bool bSuccess = rLinkMan.GetScTabRange(rFirstTab, rLastTab, nIxti); + if (!bSuccess) + return false; + + if (!rExtInfo.mbExternal) + // This is internal reference. Stop here. + return true; + + rExtInfo.maTabName = rLinkMan.GetSupbookTabName(nIxti, rFirstTab); + return GetExternalFileIdFromXti(nIxti, rExtInfo.mnFileId); +} + +bool ExcelToSc8::HandleOleLink(sal_uInt16 nXtiIndex, const XclImpExtName& rExtName, ExternalTabInfo& rExtInfo) +{ + const OUString* pUrl = rLinkMan.GetSupbookUrl(nXtiIndex); + if (!pUrl) + return false; + + OUString aPath; + if (!extractFilePath(*pUrl, aPath)) + // file path extraction failed. + return false; + + OUString aFileUrl = ScGlobal::GetAbsDocName(aPath, GetDocShell()); + return rExtName.CreateOleData(GetDoc(), aFileUrl, rExtInfo.mnFileId, rExtInfo.maTabName, rExtInfo.maRange); +} + +// if bAllowArrays is false stream seeks to first byte after <nFormulaLen> +// otherwise it will seek to the first byte past additional content after <nFormulaLen> +ConvErr ExcelToSc8::Convert( std::unique_ptr<ScTokenArray>& rpTokArray, XclImpStream& aIn, std::size_t nFormulaLen, bool bAllowArrays, const FORMULA_TYPE eFT ) +{ + bool bError = false; + bool bArrayFormula = false; + TokenId nBuf0; + const bool bCondFormat = eFT == FT_CondFormat; + const bool bRangeName = eFT == FT_RangeName; + const bool bRangeNameOrCond = bRangeName || bCondFormat; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bRNorSF = bRangeNameOrCond || bSharedFormula; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + ExtensionTypeVec aExtensions; + + if( nFormulaLen == 0 ) + { + aPool.Store( "-/-" ); + aPool >> aStack; + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + return ConvErr::OK; + } + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + sal_uInt8 nOp = aIn.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + case 0x02: // Data Table [325 277] + aIn.Ignore( 4 ); + + bArrayFormula = true; + break; + case 0x03: // Addition [312 264] + aStack >> nBuf0; + aPool << aStack << ocAdd << nBuf0; + aPool >> aStack; + break; + case 0x04: // Subtraction [313 264] + // SECOND-TOP minus TOP + aStack >> nBuf0; + aPool << aStack << ocSub << nBuf0; + aPool >> aStack; + break; + case 0x05: // Multiplication [313 264] + aStack >> nBuf0; + aPool << aStack << ocMul << nBuf0; + aPool >> aStack; + break; + case 0x06: // Division [313 264] + // divide TOP by SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocDiv << nBuf0; + aPool >> aStack; + break; + case 0x07: // Exponentiation [313 265] + // raise SECOND-TOP to power of TOP + aStack >> nBuf0; + aPool << aStack << ocPow << nBuf0; + aPool >> aStack; + break; + case 0x08: // Concatenation [313 265] + // append TOP to SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocAmpersand << nBuf0; + aPool >> aStack; + break; + case 0x09: // Less Than [313 265] + // SECOND-TOP < TOP + aStack >> nBuf0; + aPool << aStack << ocLess << nBuf0; + aPool >> aStack; + break; + case 0x0A: // Less Than or Equal [313 265] + // SECOND-TOP <= TOP + aStack >> nBuf0; + aPool << aStack << ocLessEqual << nBuf0; + aPool >> aStack; + break; + case 0x0B: // Equal [313 265] + // SECOND-TOP == TOP + aStack >> nBuf0; + aPool << aStack << ocEqual << nBuf0; + aPool >> aStack; + break; + case 0x0C: // Greater Than or Equal [313 265] + // SECOND-TOP >= TOP + aStack >> nBuf0; + aPool << aStack << ocGreaterEqual << nBuf0; + aPool >> aStack; + break; + case 0x0D: // Greater Than [313 265] + // SECOND-TOP > TOP + aStack >> nBuf0; + aPool << aStack << ocGreater << nBuf0; + aPool >> aStack; + break; + case 0x0E: // Not Equal [313 265] + // SECOND-TOP != TOP + aStack >> nBuf0; + aPool << aStack << ocNotEqual << nBuf0; + aPool >> aStack; + break; + case 0x0F: // Intersection [314 265] + aStack >> nBuf0; + aPool << aStack << ocIntersect << nBuf0; + aPool >> aStack; + break; + case 0x10: // Union [314 265] + // ocSep instead of 'ocUnion' + aStack >> nBuf0; + aPool << aStack << ocSep << nBuf0; + // doesn't fit exactly, but is more Excel-like + aPool >> aStack; + break; + case 0x11: // Range [314 265] + aStack >> nBuf0; + aPool << aStack << ocRange << nBuf0; + aPool >> aStack; + break; + case 0x12: // Unary Plus [312 264] + aPool << ocAdd << aStack; + aPool >> aStack; + break; + case 0x13: // Unary Minus [312 264] + aPool << ocNegSub << aStack; + aPool >> aStack; + break; + case 0x14: // Percent Sign [312 264] + aPool << aStack << ocPercentSign; + aPool >> aStack; + break; + case 0x15: // Parenthesis [326 278] + aPool << ocOpen << aStack << ocClose; + aPool >> aStack; + break; + case 0x16: // Missing Argument [314 266] + aPool << ocMissing; + aPool >> aStack; + GetTracer().TraceFormulaMissingArg(); + break; + case 0x17: // String Constant [314 266] + { + sal_uInt8 nLen = aIn.ReaduInt8(); // Why? + OUString aString = aIn.ReadUniString( nLen ); // reads Grbit even if nLen==0 + + aStack << aPool.Store( aString ); + break; + } + case 0x18: // natural language formula + { + sal_uInt8 nEptg; + sal_uInt16 nCol, nRow; + nEptg = aIn.ReaduInt8(); + switch( nEptg ) + { // name size ext type + case 0x01: // Lel 4 - err + aIn.Ignore( 4 ); + aPool << ocBad; + aPool >> aStack; + break; + case 0x02: // Rw 4 - ref + case 0x03: // Col 4 - ref + case 0x06: // RwV 4 - val + case 0x07: // ColV 4 - val + { + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + ScAddress aAddr(static_cast<SCCOL>(nCol & 0xFF), static_cast<SCROW>(nRow), aEingPos.Tab()); + aSRD.InitAddress(aAddr); + + if( nEptg == 0x02 || nEptg == 0x06 ) + aSRD.SetRowRel(true); + else + aSRD.SetColRel(true); + + aSRD.SetAddress(GetDocImport().getDoc().GetSheetLimits(), aAddr, aEingPos); + + aStack << aPool.StoreNlf( aSRD ); + + break; + } + case 0x0A: // Radical 13 - ref + { + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + aIn.Ignore( 9 ); + ScAddress aAddr(static_cast<SCCOL>(nCol & 0xFF), static_cast<SCROW>(nRow), aEingPos.Tab()); + aSRD.InitAddress(aAddr); + aSRD.SetColRel(true); + aSRD.SetAddress(GetDocImport().getDoc().GetSheetLimits(), aAddr, aEingPos); + + aStack << aPool.StoreNlf( aSRD ); + + break; + } + case 0x0B: // RadicalS 13 x ref + aIn.Ignore( 13 ); + aExtensions.push_back( EXTENSION_NLR ); + aPool << ocBad; + aPool >> aStack; + break; + case 0x0C: // RwS 4 x ref + case 0x0D: // ColS 4 x ref + case 0x0E: // RwSV 4 x val + case 0x0F: // ColSV 4 x val + aIn.Ignore( 4 ); + aExtensions.push_back( EXTENSION_NLR ); + aPool << ocBad; + aPool >> aStack; + break; + case 0x10: // RadicalLel 4 - err + case 0x1D: // SxName 4 - val + aIn.Ignore( 4 ); + aPool << ocBad; + aPool >> aStack; + break; + default: + aPool << ocBad; + aPool >> aStack; + } + } + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + nData = aIn.ReaduInt16(); + nFactor = 2; + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + nData++; + aIn.Ignore(static_cast<std::size_t>(nData) * nFactor); + } + else if( nOpt & 0x10 ) // AttrSum + DoMulArgs( ocSum, 1 ); + break; + } + case 0x1C: // Error Value [314 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + + DefTokenId eOc; + switch( nByte ) + { + case EXC_ERR_NULL: + case EXC_ERR_DIV0: + case EXC_ERR_VALUE: + case EXC_ERR_REF: + case EXC_ERR_NAME: + case EXC_ERR_NUM: eOc = ocStop; break; + case EXC_ERR_NA: eOc = ocNotAvail; break; + default: eOc = ocNoName; + } + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + aPool >> aStack; + + break; + } + case 0x1D: // Boolean [315 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + if( nByte == 0 ) + aPool << ocFalse << ocOpen << ocClose; + else + aPool << ocTrue << ocOpen << ocClose; + aPool >> aStack; + break; + } + case 0x1E: // Integer [315 266] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + aStack << aPool.Store( static_cast<double>(nUINT16) ); + break; + } + case 0x1F: // Number [315 266] + { + double fDouble = aIn.ReadDouble(); + aStack << aPool.Store( fDouble ); + break; + } + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + { + aIn.Ignore( 7 ); + if( bAllowArrays ) + { + aStack << aPool.StoreMatrix(); + aExtensions.push_back( EXTENSION_ARRAY ); + } + else + { + aPool << ocBad; + aPool >> aStack; + } + break; + } + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + { + sal_uInt16 nXclFunc; + nXclFunc = aIn.ReaduInt16(); + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, pFuncInfo->mnMaxParamCount ); + else + DoMulArgs( ocNoName, 0 ); + break; + } + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + { + sal_uInt16 nXclFunc; + sal_uInt8 nParamCount; + nParamCount = aIn.ReaduInt8(); + nXclFunc = aIn.ReaduInt16(); + nParamCount &= 0x7F; + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, nParamCount ); + else + DoMulArgs( ocNoName, 0 ); + break; + } + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + aIn.Ignore( 2 ); + const XclImpName* pName = GetNameManager().GetName( nUINT16 ); + if (pName) + { + if (pName->IsMacro()) + // user-defined macro name. + aStack << aPool.Store(ocMacro, pName->GetXclName()); + else + aStack << aPool.StoreName(nUINT16, pName->IsGlobal() ? -1 : pName->GetScTab()); + } + break; + } + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + { + sal_uInt16 nCol, nRow; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRangeNameOrCond ); + + switch ( nOp ) + { + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + + aStack << aPool.Store( aSRD ); + break; + } + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + ScSingleRefData &rSRef1 = aCRD.Ref1; + ScSingleRefData &rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName ); + rSRef2.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRangeNameOrCond ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRangeNameOrCond ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + // no information which part is deleted, set all + rSRef1.SetColDeleted( true ); + rSRef1.SetRowDeleted( true ); + rSRef2.SetColDeleted( true ); + rSRef2.SetRowDeleted( true ); + } + + aStack << aPool.Store( aCRD ); + break; + } + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + aExtensions.push_back( EXTENSION_MEMAREA ); + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nRow, nCol; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRNorSF ); + + aStack << aPool.Store( aSRD ); + break; + } + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + bool bColRel = aCRD.Ref1.IsColRel() || aCRD.Ref2.IsColRel(); + bool bRowRel = aCRD.Ref1.IsRowRel() || aCRD.Ref2.IsRowRel(); + + if( !bColRel && IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( !bRowRel && IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + aStack << aPool.Store( aCRD ); + break; + } + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + { + OUString aString = "COMM_EQU_FUNC"; + sal_uInt8 nByte = aIn.ReaduInt8(); + aString += OUString::number( nByte ); + nByte = aIn.ReaduInt8(); + aStack << aPool.Store( aString ); + DoMulArgs( ocPush, nByte + 1 ); + break; + } + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + { + sal_uInt16 nXtiIndex, nNameIdx; + nXtiIndex = aIn.ReaduInt16(); + nNameIdx = aIn.ReaduInt16(); + aIn.Ignore( 2 ); + + if( rLinkMan.IsSelfRef( nXtiIndex ) ) + { + // internal defined name with explicit sheet, i.e.: =Sheet1!AnyName + const XclImpName* pName = GetNameManager().GetName( nNameIdx ); + if (pName) + { + if (pName->GetScRangeData()) + aStack << aPool.StoreName( nNameIdx, pName->IsGlobal() ? -1 : pName->GetScTab()); + else + aStack << aPool.Store(ocMacro, pName->GetXclName()); + } + } + else if( const XclImpExtName* pExtName = rLinkMan.GetExternName( nXtiIndex, nNameIdx ) ) + { + switch( pExtName->GetType() ) + { + case xlExtName: + { + /* FIXME: enable this code for #i4385# once + * external name reference can be stored in ODF, + * which remains to be done for #i3740#. Until then + * create a #NAME? token. */ +#if 1 + sal_uInt16 nFileId; + if (!GetExternalFileIdFromXti(nXtiIndex, nFileId) || !pExtName->HasFormulaTokens()) + { + aStack << aPool.Store(ocNoName, pExtName->GetName()); + break; + } + + aStack << aPool.StoreExtName(nFileId, pExtName->GetName()); + pExtName->CreateExtNameData(GetDoc(), nFileId); +#else + aStack << aPool.Store( ocNoName, pExtName->GetName() ); +#endif + } + break; + + case xlExtAddIn: + { + aStack << aPool.Store( ocExternal, pExtName->GetName() ); + } + break; + + case xlExtDDE: + { + OUString aApplic, aTopic; + if( rLinkMan.GetLinkData( aApplic, aTopic, nXtiIndex ) ) + { + TokenId nPar1 = aPool.Store( aApplic ); + TokenId nPar2 = aPool.Store( aTopic ); + nBuf0 = aPool.Store( pExtName->GetName() ); + aPool << ocDde << ocOpen << nPar1 << ocSep << nPar2 << ocSep + << nBuf0 << ocClose; + aPool >> aStack; + pExtName->CreateDdeData( GetDoc(), aApplic, aTopic ); + GetDoc().SetLinkFormulaNeedingCheck(true); + } + } + break; + + case xlExtEuroConvert: + { + aStack << aPool.Store( ocEuroConvert, OUString() ); + } + break; + case xlExtOLE: + { + ExternalTabInfo aExtInfo; + if (HandleOleLink(nXtiIndex, *pExtName, aExtInfo)) + { + if (aExtInfo.maRange.aStart == aExtInfo.maRange.aEnd) + { + // single cell + aSRD.InitAddress(aExtInfo.maRange.aStart); + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aSRD); + } + else + { + // range + aCRD.InitRange(aExtInfo.maRange); + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aCRD); + } + } + else + aStack << aPool.Store(ocNoName, pExtName->GetName()); + } + break; + default: + { + aPool << ocBad; + aPool >> aStack; + } + } + } + else + { + aPool << ocBad; + aPool >> aStack; + } + break; + } + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + { + sal_uInt16 nIxti, nRw, nGrbitCol; + SCTAB nTabFirst, nTabLast; + + nIxti = aIn.ReaduInt16(); + nRw = aIn.ReaduInt16(); + nGrbitCol = aIn.ReaduInt16(); + + ExternalTabInfo aExtInfo; + if (!Read3DTabReference(nIxti, nTabFirst, nTabLast, aExtInfo)) + { + aPool << ocBad; + aPool >> aStack; + break; + } + + aSRD.SetAbsTab(nTabFirst); + aSRD.SetFlag3D(true); + + ExcRelToScRel8( nRw, nGrbitCol, aSRD, bRangeNameOrCond ); + + switch ( nOp ) + { + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + + if (aExtInfo.mbExternal) + { + // nTabFirst and nTabLast are the indices of the referenced + // sheets in the SUPBOOK record, hence do not represent + // the actual indices of the original sheets since the + // SUPBOOK record only stores referenced sheets and skips + // the ones that are not referenced. + + if (nTabLast != nTabFirst) + { + aCRD.Ref1 = aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nTabLast); + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aCRD); + } + else + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aSRD); + } + else + { + if ( !ValidTab(nTabFirst)) + aSRD.SetTabDeleted( true ); + + if( nTabLast != nTabFirst ) + { + aCRD.Ref1 = aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nTabLast); + aCRD.Ref2.SetTabDeleted( !ValidTab(nTabLast) ); + aStack << aPool.Store( aCRD ); + } + else + aStack << aPool.Store( aSRD ); + } + break; + } + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + { + sal_uInt16 nIxti, nRw1, nGrbitCol1, nRw2, nGrbitCol2; + SCTAB nTabFirst, nTabLast; + nIxti = aIn.ReaduInt16(); + nRw1 = aIn.ReaduInt16(); + nRw2 = aIn.ReaduInt16(); + nGrbitCol1 = aIn.ReaduInt16(); + nGrbitCol2 = aIn.ReaduInt16(); + + ExternalTabInfo aExtInfo; + if (!Read3DTabReference(nIxti, nTabFirst, nTabLast, aExtInfo)) + { + aPool << ocBad; + aPool >> aStack; + break; + } + ScSingleRefData &rR1 = aCRD.Ref1; + ScSingleRefData &rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nTabFirst); + rR2.SetAbsTab(nTabLast); + rR1.SetFlag3D(true); + rR2.SetFlag3D( nTabFirst != nTabLast ); + + ExcRelToScRel8( nRw1, nGrbitCol1, aCRD.Ref1, bRangeNameOrCond ); + ExcRelToScRel8( nRw2, nGrbitCol2, aCRD.Ref2, bRangeNameOrCond ); + + if( IsComplColRange( nGrbitCol1, nGrbitCol2 ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRw1, nRw2 ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + // no information which part is deleted, set all + rR1.SetColDeleted( true ); + rR1.SetRowDeleted( true ); + rR2.SetColDeleted( true ); + rR2.SetRowDeleted( true ); + } + + if (aExtInfo.mbExternal) + { + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aCRD); + } + else + { + if ( !ValidTab(nTabFirst) ) + rR1.SetTabDeleted( true ); + if ( !ValidTab(nTabLast) ) + rR2.SetTabDeleted( true ); + + aStack << aPool.Store( aCRD ); + } + break; + } + default: + bError = true; + } + bError |= !aIn.IsValid(); + } + + ConvErr eRet; + + if( bError ) + { + aPool << ocBad; + aPool >> aStack; + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Ni; + } + else if( aIn.GetRecPos() != nEndPos ) + { + aPool << ocBad; + aPool >> aStack; + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Count; + } + else if( bArrayFormula ) + { + rpTokArray = nullptr; + eRet = ConvErr::OK; + } + else + { + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::OK; + } + + aIn.Seek( nEndPos ); + + if( eRet == ConvErr::OK) + ReadExtensions( aExtensions, aIn ); + + return eRet; +} + +// stream seeks to first byte after <nFormulaLen> +ConvErr ExcelToSc8::Convert( ScRangeListTabs& rRangeList, XclImpStream& aIn, std::size_t nFormulaLen, + SCTAB nTab, const FORMULA_TYPE eFT ) +{ + sal_uInt8 nOp, nLen; + bool bError = false; + const bool bCondFormat = eFT == FT_CondFormat; + const bool bRangeName = eFT == FT_RangeName || bCondFormat; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bRNorSF = bRangeName || bSharedFormula; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + + if( nFormulaLen == 0 ) + return ConvErr::OK; + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + nOp = aIn.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + aIn.Ignore( 4 ); + break; + case 0x02: // Data Table [325 277] + aIn.Ignore( 4 ); + break; + case 0x03: // Addition [312 264] + case 0x04: // Subtraction [313 264] + case 0x05: // Multiplication [313 264] + case 0x06: // Division [313 264] + case 0x07: // Exponetiation [313 265] + case 0x08: // Concatenation [313 265] + case 0x09: // Less Than [313 265] + case 0x0A: // Less Than or Equal [313 265] + case 0x0B: // Equal [313 265] + case 0x0C: // Greater Than or Equal [313 265] + case 0x0D: // Greater Than [313 265] + case 0x0E: // Not Equal [313 265] + case 0x0F: // Intersection [314 265] + case 0x10: // Union [314 265] + case 0x11: // Range [314 265] + case 0x12: // Unary Plus [312 264] + case 0x13: // Unary Minus [312 264] + case 0x14: // Percent Sign [312 264] + case 0x15: // Parenthesis [326 278] + case 0x16: // Missing Argument [314 266] + break; + case 0x17: // String Constant [314 266] + nLen = aIn.ReaduInt8(); // Why? + + aIn.IgnoreUniString( nLen ); // reads Grbit even if nLen==0 + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + nData = aIn.ReaduInt16(); + nFactor = 2; + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + ++nData; + aIn.Ignore(static_cast<std::size_t>(nData) * nFactor); + } + } + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + aIn.Ignore( 1 ); + break; + case 0x1E: // Integer [315 266] + aIn.Ignore( 2 ); + break; + case 0x1F: // Number [315 266] + aIn.Ignore( 8 ); + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + aIn.Ignore( 7 ); + break; + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + aIn.Ignore( 2 ); + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + aIn.Ignore( 3 ); + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + aIn.Ignore( 4 ); + break; + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + { + sal_uInt16 nCol, nRow; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName && !bCondFormat ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRangeName ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + ScSingleRefData &rSRef1 = aCRD.Ref1; + ScSingleRefData &rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName && !bCondFormat ); + rSRef2.SetFlag3D( bRangeName && !bCondFormat ); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + aIn.Ignore( 3 ); + break; + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + aIn.Ignore( 6 ); + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nRow, nCol; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRNorSF ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16( ); + nColLast = aIn.ReaduInt16(); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + aIn.Ignore( 2 ); + break; + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + aIn.Ignore( 24 ); + break; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + { + sal_uInt16 nIxti, nRw, nGrbitCol; + + nIxti = aIn.ReaduInt16(); + nRw = aIn.ReaduInt16(); + nGrbitCol = aIn.ReaduInt16(); + + SCTAB nFirstScTab, nLastScTab; + if( rLinkMan.GetScTabRange( nFirstScTab, nLastScTab, nIxti ) ) + { + aSRD.SetAbsTab(nFirstScTab); + aSRD.SetFlag3D(true); + + ExcRelToScRel8( nRw, nGrbitCol, aSRD, bRangeName ); + + if( nFirstScTab != nLastScTab ) + { + aCRD.Ref1 = aSRD; + aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nLastScTab); + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + else + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + } + break; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + { + sal_uInt16 nIxti, nRw1, nGrbitCol1, nRw2, nGrbitCol2; + + nIxti = aIn.ReaduInt16(); + nRw1 = aIn.ReaduInt16(); + nRw2 = aIn.ReaduInt16(); + nGrbitCol1 = aIn.ReaduInt16(); + nGrbitCol2 = aIn.ReaduInt16(); + + SCTAB nFirstScTab, nLastScTab; + if( rLinkMan.GetScTabRange( nFirstScTab, nLastScTab, nIxti ) ) + { + ScSingleRefData &rR1 = aCRD.Ref1; + ScSingleRefData &rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nFirstScTab); + rR2.SetAbsTab(nLastScTab); + rR1.SetFlag3D(true); + rR2.SetFlag3D( nFirstScTab != nLastScTab ); + + ExcRelToScRel8( nRw1, nGrbitCol1, aCRD.Ref1, bRangeName ); + ExcRelToScRel8( nRw2, nGrbitCol2, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nGrbitCol1, nGrbitCol2 ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRw1, nRw2 ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + } + break; + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + aIn.Ignore( 6 ); + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + aIn.Ignore( 10 ); + break; + default: + bError = true; + } + bError |= !aIn.IsValid(); + } + + ConvErr eRet; + + if( bError ) + eRet = ConvErr::Ni; + else if( aIn.GetRecPos() != nEndPos ) + eRet = ConvErr::Count; + else + eRet = ConvErr::OK; + + aIn.Seek( nEndPos ); + return eRet; +} + +void ExcelToSc8::ConvertExternName( std::unique_ptr<ScTokenArray>& rpArray, XclImpStream& rStrm, std::size_t nFormulaLen, + const OUString& rUrl, const vector<OUString>& rTabNames ) +{ + if( !GetDocShell() ) + return; + + OUString aFileUrl = ScGlobal::GetAbsDocName(rUrl, GetDocShell()); + + sal_uInt8 nOp, nByte; + bool bError = false; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + + if (nFormulaLen == 0) + { + aPool.Store("-/-"); + aPool >> aStack; + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + return; + } + + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFileUrl); + sal_uInt16 nTabCount = static_cast< sal_uInt16 >( rTabNames.size() ); + + std::size_t nEndPos = rStrm.GetRecPos() + nFormulaLen; + + while( (rStrm.GetRecPos() < nEndPos) && !bError ) + { + nOp = rStrm.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) + { + case 0x1C: // Error Value + { + nByte = rStrm.ReaduInt8(); + DefTokenId eOc; + switch( nByte ) + { + case EXC_ERR_NULL: + case EXC_ERR_DIV0: + case EXC_ERR_VALUE: + case EXC_ERR_REF: + case EXC_ERR_NAME: + case EXC_ERR_NUM: eOc = ocStop; break; + case EXC_ERR_NA: eOc = ocNotAvail; break; + default: eOc = ocNoName; + } + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + aPool >> aStack; + } + break; + case 0x3A: + { + // cell reference in external range name + sal_uInt16 nExtTab1, nExtTab2, nRow, nGrbitCol; + nExtTab1 = rStrm.ReaduInt16(); + nExtTab2 = rStrm.ReaduInt16(); + nRow = rStrm.ReaduInt16(); + nGrbitCol = rStrm.ReaduInt16(); + if (nExtTab1 >= nTabCount || nExtTab2 >= nTabCount) + { + bError = true; + break; + } + + aSRD.SetAbsTab(nExtTab1); + aSRD.SetFlag3D(true); + ExcRelToScRel8(nRow, nGrbitCol, aSRD, true); + aCRD.Ref1 = aCRD.Ref2 = aSRD; + OUString aTabName = rTabNames[nExtTab1]; + + if (nExtTab1 == nExtTab2) + { + // single cell reference + aStack << aPool.StoreExtRef(nFileId, aTabName, aSRD); + } + else + { + // area reference + aCRD.Ref2.SetAbsTab(nExtTab2); + aStack << aPool.StoreExtRef(nFileId, aTabName, aCRD); + } + } + break; + case 0x3B: + { + // area reference + sal_uInt16 nExtTab1, nExtTab2, nRow1, nRow2, nGrbitCol1, nGrbitCol2; + nExtTab1 = rStrm.ReaduInt16(); + nExtTab2 = rStrm.ReaduInt16(); + nRow1 = rStrm.ReaduInt16(); + nRow2 = rStrm.ReaduInt16(); + nGrbitCol1 = rStrm.ReaduInt16(); + nGrbitCol2 = rStrm.ReaduInt16(); + + if (nExtTab1 >= nTabCount || nExtTab2 >= nTabCount) + { + bError = true; + break; + } + + ScSingleRefData& rR1 = aCRD.Ref1; + ScSingleRefData& rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nExtTab1); + rR1.SetFlag3D(true); + ExcRelToScRel8(nRow1, nGrbitCol1, rR1, true); + + rR2.SetAbsTab(nExtTab2); + rR2.SetFlag3D(true); + ExcRelToScRel8(nRow2, nGrbitCol2, rR2, true); + + OUString aTabName = rTabNames[nExtTab1]; + aStack << aPool.StoreExtRef(nFileId, aTabName, aCRD); + } + break; + default: + bError = true; + } + bError |= !rStrm.IsValid(); + } + + if( bError ) + { + aPool << ocBad; + aPool >> aStack; + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + } + else if( rStrm.GetRecPos() != nEndPos ) + { + aPool << ocBad; + aPool >> aStack; + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + } + else + { + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + } + + rStrm.Seek(nEndPos); +} + +void ExcelToSc8::ExcRelToScRel8( sal_uInt16 nRow, sal_uInt16 nC, ScSingleRefData &rSRD, const bool bName ) +{ + const bool bColRel = ( nC & 0x4000 ) != 0; + const bool bRowRel = ( nC & 0x8000 ) != 0; + const sal_uInt8 nCol = static_cast<sal_uInt8>(nC); + + if( bName ) + { + // C O L + if( bColRel ) + { + SCCOL nRelCol = static_cast<sal_Int8>(nC); + sal_Int16 nDiff = aEingPos.Col() + nRelCol; + if ( nDiff < 0) + { + // relative column references wrap around + nRelCol = static_cast<sal_Int16>(256 + static_cast<int>(nRelCol)); + } + rSRD.SetRelCol(nRelCol); + } + else + rSRD.SetAbsCol(static_cast<SCCOL>(nCol)); + + // R O W + if( bRowRel ) + { + SCROW nRelRow = static_cast<sal_Int16>(nRow); + sal_Int32 nDiff = aEingPos.Row() + nRelRow; + if (nDiff < 0) + { + // relative row references wrap around + nRelRow = 65536 + nRelRow; + } + rSRD.SetRelRow(nRelRow); + } + else + rSRD.SetAbsRow(std::min( static_cast<SCROW>(nRow), GetDoc().MaxRow())); + } + else + { + // C O L + if ( bColRel ) + rSRD.SetRelCol(static_cast<SCCOL>(nCol) - aEingPos.Col()); + else + rSRD.SetAbsCol(nCol); + + // R O W + if ( bRowRel ) + rSRD.SetRelRow(static_cast<SCROW>(nRow) - aEingPos.Row()); + else + rSRD.SetAbsRow(nRow); + } +} + +// stream seeks to first byte after <nLen> +void ExcelToSc8::GetAbsRefs( ScRangeList& r, XclImpStream& aIn, std::size_t nLen ) +{ + sal_uInt8 nOp; + sal_uInt16 nRow1, nRow2, nCol1, nCol2; + SCTAB nTab1, nTab2; + sal_uInt16 nIxti; + + std::size_t nSeek; + + std::size_t nEndPos = aIn.GetRecPos() + nLen; + + while( aIn.IsValid() && (aIn.GetRecPos() < nEndPos) ) + { + nOp = aIn.ReaduInt8(); + nSeek = 0; + + switch( nOp ) + { + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + nRow1 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + + nRow2 = nRow1; + nCol2 = nCol1; + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + // Area Reference Within a Shared Formula[ 274] + nRow1 = aIn.ReaduInt16(); + nRow2 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + nCol2 = aIn.ReaduInt16(); + + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + nIxti = aIn.ReaduInt16(); + nRow1 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + + nRow2 = nRow1; + nCol2 = nCol1; + + goto _3d_common; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + nIxti = aIn.ReaduInt16(); + nRow1 = aIn.ReaduInt16(); + nRow2 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + nCol2 = aIn.ReaduInt16(); + + _3d_common: + // skip references to deleted sheets + if( !rLinkMan.GetScTabRange( nTab1, nTab2, nIxti ) || !ValidTab( nTab1 ) || !ValidTab( nTab2 ) ) + break; + + goto _common; + _common: + // do not check abs/rel flags, linked controls have set them! + { + ScRange aScRange; + nCol1 &= 0x3FFF; + nCol2 &= 0x3FFF; + if( GetAddressConverter().ConvertRange( aScRange, XclRange( nCol1, nRow1, nCol2, nRow2 ), nTab1, nTab2, true ) ) + r.push_back( aScRange ); + } + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + nSeek = 1; + break; + case 0x1E: // Integer [315 266] + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + nSeek = 2; + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + nSeek = 3; + break; + case 0x01: // Array Formula [325 ] + case 0x02: // Data Table [325 277] + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + nSeek = 4; + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + nSeek = 6; + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + nSeek = 7; + break; + case 0x1F: // Number [315 266] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + nSeek = 8; + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + nSeek = 10; + break; + case 0x17: // String Constant [314 266] + { + sal_uInt8 nStrLen; + nStrLen = aIn.ReaduInt8(); + aIn.IgnoreUniString( nStrLen ); // reads Grbit even if nLen==0 + nSeek = 0; + } + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData; + sal_uInt8 nOpt; + nOpt = aIn.ReaduInt8(); + nData = aIn.ReaduInt16(); + if( nOpt & 0x04 ) + {// nFactor -> skip bytes or words AttrChoose + nData++; + nSeek = nData * 2; + } + } + break; + } + + aIn.Ignore( nSeek ); + } + aIn.Seek( nEndPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excimp8.cxx b/sc/source/filter/excel/excimp8.cxx new file mode 100644 index 000000000..d5db209a1 --- /dev/null +++ b/sc/source/filter/excel/excimp8.cxx @@ -0,0 +1,817 @@ +/* -*- 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 <config_features.h> + +#include <excimp8.hxx> + +#include <scitems.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <unotools/fltrcfg.hxx> + +#include <sfx2/docfile.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docinf.hxx> +#include <sot/storage.hxx> +#include <svl/sharedstringpool.hxx> + +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <unotools/localedatawrapper.hxx> + +#include <document.hxx> +#include <attrib.hxx> +#include <dbdata.hxx> +#include <globalnames.hxx> +#include <docoptio.hxx> +#include <xihelper.hxx> +#include <xicontent.hxx> +#include <xilink.hxx> +#include <xiescher.hxx> +#include <xistyle.hxx> +#include <excdefs.hxx> + +#include <excform.hxx> +#include <queryentry.hxx> + +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <cppuhelper/implbase.hxx> +#include "xltoolbar.hxx" +#include <oox/ole/vbaproject.hxx> +#include <oox/ole/olestorage.hxx> + +using namespace com::sun::star; +using namespace ::comphelper; + +//OleNameOverrideContainer + +namespace { + +class OleNameOverrideContainer : public ::cppu::WeakImplHelper< container::XNameContainer > +{ +private: + typedef std::unordered_map< OUString, uno::Reference< container::XIndexContainer > > NamedIndexToOleName; + NamedIndexToOleName IdToOleNameHash; + ::osl::Mutex m_aMutex; +public: + // XElementAccess + virtual uno::Type SAL_CALL getElementType( ) override { return cppu::UnoType<container::XIndexContainer>::get(); } + virtual sal_Bool SAL_CALL hasElements( ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + return ( !IdToOleNameHash.empty() ); + } + // XNameAccess + virtual uno::Any SAL_CALL getByName( const OUString& aName ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !hasByName(aName) ) + throw container::NoSuchElementException(); + return uno::Any( IdToOleNameHash[ aName ] ); + } + virtual uno::Sequence< OUString > SAL_CALL getElementNames( ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + return comphelper::mapKeysToSequence( IdToOleNameHash); + } + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + return ( IdToOleNameHash.find( aName ) != IdToOleNameHash.end() ); + } + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const uno::Any& aElement ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( hasByName( aName ) ) + throw container::ElementExistException(); + uno::Reference< container::XIndexContainer > xElement; + if ( ! ( aElement >>= xElement ) ) + throw lang::IllegalArgumentException(); + IdToOleNameHash[ aName ] = xElement; + } + virtual void SAL_CALL removeByName( const OUString& aName ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( IdToOleNameHash.erase( aName ) == 0 ) + throw container::NoSuchElementException(); + } + virtual void SAL_CALL replaceByName( const OUString& aName, const uno::Any& aElement ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !hasByName( aName ) ) + throw container::NoSuchElementException(); + uno::Reference< container::XIndexContainer > xElement; + if ( ! ( aElement >>= xElement ) ) + throw lang::IllegalArgumentException(); + IdToOleNameHash[ aName ] = xElement; + } +}; + +/** Future Record Type header. + @return whether read rt matches nRecordID + */ +bool readFrtHeader( XclImpStream& rStrm, sal_uInt16 nRecordID ) +{ + sal_uInt16 nRt = rStrm.ReaduInt16(); + rStrm.Ignore(10); // grbitFrt (2 bytes) and reserved (8 bytes) + return nRt == nRecordID; +} + +} + +ImportExcel8::ImportExcel8( XclImpRootData& rImpData, SvStream& rStrm ) : + ImportExcel( rImpData, rStrm ) +{ + // replace BIFF2-BIFF5 formula importer with BIFF8 formula importer + pFormConv.reset(new ExcelToSc8( GetRoot() )); + pExcRoot->pFmlaConverter = pFormConv.get(); +} + +ImportExcel8::~ImportExcel8() +{ +} + +void ImportExcel8::Calccount() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetIterCount( aIn.ReaduInt16() ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Precision() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetCalcAsShown( aIn.ReaduInt16() == 0 ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Delta() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetIterEps( aIn.ReadDouble() ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Iteration() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetIter( aIn.ReaduInt16() == 1 ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Boundsheet() +{ + sal_uInt8 nLen; + sal_uInt16 nGrbit; + + aIn.DisableDecryption(); + maSheetOffsets.push_back( aIn.ReaduInt32() ); + aIn.EnableDecryption(); + nGrbit = aIn.ReaduInt16(); + nLen = aIn.ReaduInt8(); + + OUString aName( aIn.ReadUniString( nLen ) ); + GetTabInfo().AppendXclTabName( aName, nBdshtTab ); + + SCTAB nScTab = nBdshtTab; + if( nScTab > 0 ) + { + OSL_ENSURE( !rD.HasTable( nScTab ), "ImportExcel8::Boundsheet - sheet exists already" ); + rD.MakeTable( nScTab ); + } + + if( ( nGrbit & 0x0001 ) || ( nGrbit & 0x0002 ) ) + rD.SetVisible( nScTab, false ); + + if( !rD.RenameTab( nScTab, aName ) ) + { + rD.CreateValidTabName( aName ); + rD.RenameTab( nScTab, aName ); + } + + nBdshtTab++; +} + +void ImportExcel8::Scenman() +{ + sal_uInt16 nLastDispl; + + aIn.Ignore( 4 ); + nLastDispl = aIn.ReaduInt16(); + + maScenList.nLastScenario = nLastDispl; +} + +void ImportExcel8::Scenario() +{ + maScenList.aEntries.push_back( std::make_unique<ExcScenario>( aIn, *pExcRoot ) ); +} + +void ImportExcel8::Labelsst() +{ + XclAddress aXclPos; + sal_uInt16 nXF; + sal_uInt32 nSst; + + aIn >> aXclPos; + nXF = aIn.ReaduInt16(); + nSst = aIn.ReaduInt32( ); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + GetXFRangeBuffer().SetXF( aScPos, nXF ); + const XclImpString* pXclStr = GetSst().GetString(nSst); + if (pXclStr) + XclImpStringHelper::SetToDocument(GetDocImport(), aScPos, *this, *pXclStr, nXF); + } +} + +void ImportExcel8::FeatHdr() +{ + if (!readFrtHeader( aIn, 0x0867)) + return; + + // Feature type (isf) can be EXC_ISFPROTECTION, EXC_ISFFEC2 or + // EXC_ISFFACTOID. + sal_uInt16 nFeatureType = aIn.ReaduInt16(); + if (nFeatureType != EXC_ISFPROTECTION) + // We currently only support import of enhanced protection data. + return; + + aIn.Ignore(1); // always 1 + + GetSheetProtectBuffer().ReadOptions( aIn, GetCurrScTab() ); +} + +void ImportExcel8::Feat() +{ + if (!readFrtHeader( aIn, 0x0868)) + return; + + // Feature type (isf) can be EXC_ISFPROTECTION, EXC_ISFFEC2 or + // EXC_ISFFACTOID. + sal_uInt16 nFeatureType = aIn.ReaduInt16(); + if (nFeatureType != EXC_ISFPROTECTION) + // We currently only support import of enhanced protection data. + return; + + aIn.Ignore(5); // reserved1 (1 byte) and reserved2 (4 bytes) + + sal_uInt16 nCref = aIn.ReaduInt16(); // number of ref elements + aIn.Ignore(4); // size if EXC_ISFFEC2, else 0 and to be ignored + aIn.Ignore(2); // reserved3 (2 bytes) + + ScEnhancedProtection aProt; + if (nCref) + { + XclRangeList aRefs; + aRefs.Read( aIn, true, nCref); + if (!aRefs.empty()) + { + aProt.maRangeList = new ScRangeList; + GetAddressConverter().ConvertRangeList( *aProt.maRangeList, aRefs, GetCurrScTab(), false); + } + } + + // FeatProtection structure follows in record. + + aProt.mnAreserved = aIn.ReaduInt32(); + aProt.mnPasswordVerifier = aIn.ReaduInt32(); + aProt.maTitle = aIn.ReadUniString(); + if ((aProt.mnAreserved & 0x00000001) == 0x00000001) + { + sal_uInt32 nCbSD = aIn.ReaduInt32(); + // TODO: could here be some sanity check applied to not allocate 4GB? + aProt.maSecurityDescriptor.resize( nCbSD); + std::size_t nRead = aIn.Read(aProt.maSecurityDescriptor.data(), nCbSD); + if (nRead < nCbSD) + aProt.maSecurityDescriptor.resize( nRead); + } + + GetSheetProtectBuffer().AppendEnhancedProtection( aProt, GetCurrScTab() ); +} + +void ImportExcel8::ReadBasic() +{ + SfxObjectShell* pShell = GetDocShell(); + tools::SvRef<SotStorage> xRootStrg = GetRootStorage(); + const SvtFilterOptions& rFilterOpt = SvtFilterOptions::Get(); + if( !pShell || !xRootStrg.is() ) + return; + + try + { + // #FIXME need to get rid of this, we can also do this from within oox + // via the "ooo.vba.VBAGlobals" service + if( ( rFilterOpt.IsLoadExcelBasicCode() || + rFilterOpt.IsLoadExcelBasicStorage() ) && + rFilterOpt.IsLoadExcelBasicExecutable() ) + { + // see if we have the XCB stream + tools::SvRef<SotStorageStream> xXCB = xRootStrg->OpenSotStream( "XCB", StreamMode::STD_READ ); + if ( xXCB.is()|| ERRCODE_NONE == xXCB->GetError() ) + { + ScCTBWrapper wrapper; + if ( wrapper.Read( *xXCB ) ) + { +#ifdef DEBUG_SC_EXCEL + wrapper.Print( stderr ); +#endif + wrapper.ImportCustomToolBar( *pShell ); + } + } + } + try + { + uno::Reference< uno::XComponentContext > aCtx( ::comphelper::getProcessComponentContext() ); + SfxMedium& rMedium = GetMedium(); + uno::Reference< io::XInputStream > xIn = rMedium.GetInputStream(); + oox::ole::OleStorage root( aCtx, xIn, false ); + oox::StorageRef vbaStg = root.openSubStorage( "_VBA_PROJECT_CUR", false ); + if ( vbaStg ) + { + oox::ole::VbaProject aVbaPrj( aCtx, pShell->GetModel(), u"Calc" ); + // collect names of embedded form controls, as specified in the VBA project + uno::Reference< container::XNameContainer > xOleNameOverrideSink( new OleNameOverrideContainer ); + aVbaPrj.setOleOverridesSink( xOleNameOverrideSink ); + aVbaPrj.importVbaProject( *vbaStg ); + GetObjectManager().SetOleNameOverrideInfo( xOleNameOverrideSink ); + } + } + catch( uno::Exception& ) + { + } + } + catch( uno::Exception& ) + { + } +} + +void ImportExcel8::EndSheet() +{ + ImportExcel::EndSheet(); + GetCondFormatManager().Apply(); + GetValidationManager().Apply(); +} + +void ImportExcel8::PostDocLoad() +{ +#if HAVE_FEATURE_SCRIPTING + // reading basic has been delayed until sheet objects (codenames etc.) are read + if( HasBasic() ) + ReadBasic(); +#endif + // #i11776# filtered ranges before outlines and hidden rows + if( pExcRoot->pAutoFilterBuffer ) + pExcRoot->pAutoFilterBuffer->Apply(); + + GetWebQueryBuffer().Apply(); //TODO: test if extant + GetSheetProtectBuffer().Apply(); + GetDocProtectBuffer().Apply(); + + ImportExcel::PostDocLoad(); + + // check scenarios; Attention: This increases the table count of the document!! + if( !rD.IsClipboard() && !maScenList.aEntries.empty() ) + { + rD.UpdateChartListenerCollection(); // references in charts must be updated + + maScenList.Apply( GetRoot() ); + } + + // read doc info (no docshell while pasting from clipboard) + SfxObjectShell* pShell = GetDocShell(); + if(!pShell) + return; + + // BIFF5+ without storage is possible + tools::SvRef<SotStorage> xRootStrg = GetRootStorage(); + if( xRootStrg.is() ) try + { + uno::Reference< document::XDocumentPropertiesSupplier > xDPS( pShell->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< document::XDocumentProperties > xDocProps( xDPS->getDocumentProperties(), uno::UNO_SET_THROW ); + sfx2::LoadOlePropertySet( xDocProps, xRootStrg.get() ); + } + catch( uno::Exception& ) + { + } + + // #i45843# Pivot tables are now handled outside of PostDocLoad, so they are available + // when formula cells are calculated, for the GETPIVOTDATA function. +} + +// autofilter + +void ImportExcel8::FilterMode() +{ + // The FilterMode record exists: if either the AutoFilter + // record exists or an Advanced Filter is saved and stored + // in the sheet. Thus if the FilterMode records only exists + // then the latter is true... + if( !pExcRoot->pAutoFilterBuffer ) return; + + XclImpAutoFilterData* pData = pExcRoot->pAutoFilterBuffer->GetByTab( GetCurrScTab() ); + if( pData ) + pData->SetAutoOrAdvanced(); +} + +void ImportExcel8::AutoFilterInfo() +{ + if( !pExcRoot->pAutoFilterBuffer ) return; + + XclImpAutoFilterData* pData = pExcRoot->pAutoFilterBuffer->GetByTab( GetCurrScTab() ); + if( pData ) + { + pData->SetAdvancedRange( nullptr ); + pData->Activate(); + } +} + +void ImportExcel8::AutoFilter() +{ + if( !pExcRoot->pAutoFilterBuffer ) return; + + XclImpAutoFilterData* pData = pExcRoot->pAutoFilterBuffer->GetByTab( GetCurrScTab() ); + if( pData ) + pData->ReadAutoFilter(aIn, GetDoc().GetSharedStringPool()); +} + +XclImpAutoFilterData::XclImpAutoFilterData( RootData* pRoot, const ScRange& rRange ) : + ExcRoot( pRoot ), + pCurrDBData(nullptr), + bActive( false ), + bCriteria( false ), + bAutoOrAdvanced(false) +{ + aParam.nCol1 = rRange.aStart.Col(); + aParam.nRow1 = rRange.aStart.Row(); + aParam.nTab = rRange.aStart.Tab(); + aParam.nCol2 = rRange.aEnd.Col(); + aParam.nRow2 = rRange.aEnd.Row(); + + aParam.bInplace = true; + +} + +namespace { + +OUString CreateFromDouble( double fVal ) +{ + return rtl::math::doubleToUString(fVal, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + ScGlobal::getLocaleData().getNumDecimalSep()[0], true); +} + +} + +void XclImpAutoFilterData::SetCellAttribs() +{ + ScDocument& rDoc = pExcRoot->pIR->GetDoc(); + for ( SCCOL nCol = StartCol(); nCol <= EndCol(); nCol++ ) + { + ScMF nFlag = rDoc.GetAttr( nCol, StartRow(), Tab(), ATTR_MERGE_FLAG )->GetValue(); + rDoc.ApplyAttr( nCol, StartRow(), Tab(), ScMergeFlagAttr( nFlag | ScMF::Auto) ); + } +} + +void XclImpAutoFilterData::InsertQueryParam() +{ + if (!pCurrDBData) + return; + + ScRange aAdvRange; + bool bHasAdv = pCurrDBData->GetAdvancedQuerySource( aAdvRange ); + if( bHasAdv ) + pExcRoot->pIR->GetDoc().CreateQueryParam(aAdvRange, aParam); + + pCurrDBData->SetQueryParam( aParam ); + if( bHasAdv ) + pCurrDBData->SetAdvancedQuerySource( &aAdvRange ); + else + { + pCurrDBData->SetAutoFilter( true ); + SetCellAttribs(); + } +} + +static void ExcelQueryToOooQuery( OUString& aStr, ScQueryEntry& rEntry ) +{ + if (rEntry.eOp != SC_EQUAL && rEntry.eOp != SC_NOT_EQUAL) + return; + + sal_Int32 nLen = aStr.getLength(); + sal_Unicode nStart = aStr[0]; + sal_Unicode nEnd = aStr[ nLen-1 ]; + if( nLen > 2 && nStart == '*' && nEnd == '*' ) + { + aStr = aStr.copy( 1, nLen-2 ); + rEntry.eOp = ( rEntry.eOp == SC_EQUAL ) ? SC_CONTAINS : SC_DOES_NOT_CONTAIN; + } + else if( nLen > 1 && nStart == '*' && nEnd != '*' ) + { + aStr = aStr.copy( 1 ); + rEntry.eOp = ( rEntry.eOp == SC_EQUAL ) ? SC_ENDS_WITH : SC_DOES_NOT_END_WITH; + } + else if( nLen > 1 && nStart != '*' && nEnd == '*' ) + { + aStr = aStr.copy( 0, nLen-1 ); + rEntry.eOp = ( rEntry.eOp == SC_EQUAL ) ? SC_BEGINS_WITH : SC_DOES_NOT_BEGIN_WITH; + } + else if( nLen == 2 && nStart == '*' && nEnd == '*' ) + { + aStr = aStr.copy( 1 ); + } +} + +void XclImpAutoFilterData::ReadAutoFilter( + XclImpStream& rStrm, svl::SharedStringPool& rPool ) +{ + sal_uInt16 nCol, nFlags; + nCol = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + + ScQueryConnect eConn = ::get_flagvalue( nFlags, EXC_AFFLAG_ANDORMASK, SC_OR, SC_AND ); + bool bSimple1 = ::get_flag(nFlags, EXC_AFFLAG_SIMPLE1); + bool bSimple2 = ::get_flag(nFlags, EXC_AFFLAG_SIMPLE2); + bool bTop10 = ::get_flag(nFlags, EXC_AFFLAG_TOP10); + bool bTopOfTop10 = ::get_flag(nFlags, EXC_AFFLAG_TOP10TOP); + bool bPercent = ::get_flag(nFlags, EXC_AFFLAG_TOP10PERC); + sal_uInt16 nCntOfTop10 = nFlags >> 7; + + if( bTop10 ) + { + ScQueryEntry& aEntry = aParam.AppendEntry(); + ScQueryEntry::Item& rItem = aEntry.GetQueryItem(); + aEntry.bDoQuery = true; + aEntry.nField = static_cast<SCCOLROW>(StartCol() + static_cast<SCCOL>(nCol)); + aEntry.eOp = bTopOfTop10 ? + (bPercent ? SC_TOPPERC : SC_TOPVAL) : (bPercent ? SC_BOTPERC : SC_BOTVAL); + aEntry.eConnect = SC_AND; + + rItem.meType = ScQueryEntry::ByString; + rItem.maString = rPool.intern(OUString::number(nCntOfTop10)); + + rStrm.Ignore(20); + return; + } + + sal_uInt8 nType, nOper, nBoolErr, nVal; + sal_Int32 nRK; + double fVal; + + sal_uInt8 nStrLen[2] = { 0, 0 }; + ScQueryEntry aEntries[2]; + + for (size_t nE = 0; nE < 2; ++nE) + { + ScQueryEntry& rEntry = aEntries[nE]; + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + bool bIgnore = false; + + nType = rStrm.ReaduInt8(); + nOper = rStrm.ReaduInt8(); + switch( nOper ) + { + case EXC_AFOPER_LESS: + rEntry.eOp = SC_LESS; + break; + case EXC_AFOPER_EQUAL: + rEntry.eOp = SC_EQUAL; + break; + case EXC_AFOPER_LESSEQUAL: + rEntry.eOp = SC_LESS_EQUAL; + break; + case EXC_AFOPER_GREATER: + rEntry.eOp = SC_GREATER; + break; + case EXC_AFOPER_NOTEQUAL: + rEntry.eOp = SC_NOT_EQUAL; + break; + case EXC_AFOPER_GREATEREQUAL: + rEntry.eOp = SC_GREATER_EQUAL; + break; + default: + rEntry.eOp = SC_EQUAL; + } + + switch( nType ) + { + case EXC_AFTYPE_RK: + nRK = rStrm.ReadInt32(); + rStrm.Ignore( 4 ); + rItem.maString = rPool.intern( + CreateFromDouble(XclTools::GetDoubleFromRK(nRK))); + break; + case EXC_AFTYPE_DOUBLE: + fVal = rStrm.ReadDouble(); + rItem.maString = rPool.intern(CreateFromDouble(fVal)); + break; + case EXC_AFTYPE_STRING: + rStrm.Ignore( 4 ); + nStrLen[ nE ] = rStrm.ReaduInt8(); + rStrm.Ignore( 3 ); + rItem.maString = svl::SharedString(); + break; + case EXC_AFTYPE_BOOLERR: + nBoolErr = rStrm.ReaduInt8(); + nVal = rStrm.ReaduInt8(); + rStrm.Ignore( 6 ); + rItem.maString = rPool.intern(OUString::number(nVal)); + bIgnore = (nBoolErr != 0); + break; + case EXC_AFTYPE_EMPTY: + rEntry.SetQueryByEmpty(); + break; + case EXC_AFTYPE_NOTEMPTY: + rEntry.SetQueryByNonEmpty(); + break; + default: + rStrm.Ignore( 8 ); + bIgnore = true; + } + + if (!bIgnore) + { + rEntry.bDoQuery = true; + rItem.meType = ScQueryEntry::ByString; + rEntry.nField = static_cast<SCCOLROW>(StartCol() + static_cast<SCCOL>(nCol)); + rEntry.eConnect = nE ? eConn : SC_AND; + } + } + + if (eConn == SC_AND) + { + for (size_t nE = 0; nE < 2; ++nE) + { + if (nStrLen[nE] && aEntries[nE].bDoQuery) + { + OUString aStr = rStrm.ReadUniString(nStrLen[nE]); + ExcelQueryToOooQuery(aStr, aEntries[nE]); + aEntries[nE].GetQueryItem().maString = rPool.intern(aStr); + aParam.AppendEntry() = aEntries[nE]; + } + } + } + else + { + assert( eConn == SC_OR && "eConn should be SC_AND or SC_OR"); + // Import only when both conditions are for simple equality, else + // import only the 1st condition due to conflict with the ordering of + // conditions. #i39464#. + // + // Example: Let A1 be a condition of column A, and B1 and B2 + // conditions of column B, connected with OR. Excel performs 'A1 AND + // (B1 OR B2)' in this case, but Calc would do '(A1 AND B1) OR B2' + // instead. + + if (bSimple1 && bSimple2 && nStrLen[0] && nStrLen[1]) + { + // Two simple OR'ed equal conditions. We can import this correctly. + ScQueryEntry& rEntry = aParam.AppendEntry(); + rEntry.bDoQuery = true; + rEntry.eOp = SC_EQUAL; + rEntry.eConnect = SC_AND; + ScQueryEntry::QueryItemsType aItems; + aItems.reserve(2); + ScQueryEntry::Item aItem1, aItem2; + aItem1.maString = rPool.intern(rStrm.ReadUniString(nStrLen[0])); + aItem1.meType = ScQueryEntry::ByString; + aItem2.maString = rPool.intern(rStrm.ReadUniString(nStrLen[1])); + aItem2.meType = ScQueryEntry::ByString; + aItems.push_back(aItem1); + aItems.push_back(aItem2); + rEntry.GetQueryItems().swap(aItems); + } + else if (nStrLen[0] && aEntries[0].bDoQuery) + { + // Due to conflict, we can import only the first condition. + OUString aStr = rStrm.ReadUniString(nStrLen[0]); + ExcelQueryToOooQuery(aStr, aEntries[0]); + aEntries[0].GetQueryItem().maString = rPool.intern(aStr); + aParam.AppendEntry() = aEntries[0]; + } + } +} + +void XclImpAutoFilterData::SetAdvancedRange( const ScRange* pRange ) +{ + if (pRange) + { + aCriteriaRange = *pRange; + bCriteria = true; + } + else + bCriteria = false; +} + +void XclImpAutoFilterData::SetExtractPos( const ScAddress& rAddr ) +{ + aParam.nDestCol = rAddr.Col(); + aParam.nDestRow = rAddr.Row(); + aParam.nDestTab = rAddr.Tab(); + aParam.bInplace = false; + aParam.bDestPers = true; +} + +void XclImpAutoFilterData::Apply() +{ + // Create the ScDBData() object if the AutoFilter is activated + // or if we need to create the Advanced Filter. + if( bActive || bCriteria) + { + ScDocument& rDoc = pExcRoot->pIR->GetDoc(); + pCurrDBData = new ScDBData(STR_DB_LOCAL_NONAME, Tab(), + StartCol(),StartRow(), EndCol(),EndRow() ); + if(bCriteria) + { + EnableRemoveFilter(); + + pCurrDBData->SetQueryParam( aParam ); + pCurrDBData->SetAdvancedQuerySource(&aCriteriaRange); + } + else + pCurrDBData->SetAdvancedQuerySource(nullptr); + rDoc.SetAnonymousDBData(Tab(), std::unique_ptr<ScDBData>(pCurrDBData)); + } + + if( bActive ) + { + InsertQueryParam(); + } +} + +void XclImpAutoFilterData::EnableRemoveFilter() +{ + // only if this is a saved Advanced filter + if( !bActive && bAutoOrAdvanced ) + { + ScQueryEntry& aEntry = aParam.AppendEntry(); + aEntry.bDoQuery = true; + } + + // TBD: force the automatic activation of the + // "Remove Filter" by setting a virtual mouse click + // inside the advanced range +} + +void XclImpAutoFilterBuffer::Insert( RootData* pRoot, const ScRange& rRange) +{ + if( !GetByTab( rRange.aStart.Tab() ) ) + maFilters.push_back( std::make_shared<XclImpAutoFilterData>( pRoot, rRange )); +} + +void XclImpAutoFilterBuffer::AddAdvancedRange( const ScRange& rRange ) +{ + XclImpAutoFilterData* pData = GetByTab( rRange.aStart.Tab() ); + if( pData ) + pData->SetAdvancedRange( &rRange ); +} + +void XclImpAutoFilterBuffer::AddExtractPos( const ScRange& rRange ) +{ + XclImpAutoFilterData* pData = GetByTab( rRange.aStart.Tab() ); + if( pData ) + pData->SetExtractPos( rRange.aStart ); +} + +void XclImpAutoFilterBuffer::Apply() +{ + for( const auto& rFilterPtr : maFilters ) + rFilterPtr->Apply(); +} + +XclImpAutoFilterData* XclImpAutoFilterBuffer::GetByTab( SCTAB nTab ) +{ + for( const auto& rFilterPtr : maFilters ) + { + if( rFilterPtr->Tab() == nTab ) + return rFilterPtr.get(); + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excrecds.cxx b/sc/source/filter/excel/excrecds.cxx new file mode 100644 index 000000000..b175445bc --- /dev/null +++ b/sc/source/filter/excel/excrecds.cxx @@ -0,0 +1,1177 @@ +/* -*- 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 <excrecds.hxx> + +#include <map> +#include <filter/msfilter/countryid.hxx> + +#include <svl/numformat.hxx> +#include <sal/log.hxx> +#include <sax/fastattribs.hxx> + +#include <string.h> + +#include <global.hxx> +#include <document.hxx> +#include <dbdata.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/tokens.hxx> +#include <queryentry.hxx> +#include <queryparam.hxx> +#include <sortparam.hxx> +#include <userlist.hxx> +#include <root.hxx> + +#include <xeescher.hxx> +#include <xelink.hxx> +#include <xename.hxx> +#include <xlname.hxx> +#include <xestyle.hxx> + +#include <xcl97rec.hxx> +#include <tabprotection.hxx> + +using namespace ::oox; + +using ::com::sun::star::uno::Sequence; + +//--------------------------------------------------------- class ExcDummy_00 - +const sal_uInt8 ExcDummy_00::pMyData[] = { + 0x5c, 0x00, 0x20, 0x00, 0x04, 'C', 'a', 'l', 'c', // WRITEACCESS + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 +}; +const std::size_t ExcDummy_00::nMyLen = sizeof( ExcDummy_00::pMyData ); + +//-------------------------------------------------------- class ExcDummy_04x - +const sal_uInt8 ExcDummy_040::pMyData[] = { + 0x40, 0x00, 0x02, 0x00, 0x00, 0x00, // BACKUP + 0x8d, 0x00, 0x02, 0x00, 0x00, 0x00, // HIDEOBJ +}; +const std::size_t ExcDummy_040::nMyLen = sizeof( ExcDummy_040::pMyData ); + +const sal_uInt8 ExcDummy_041::pMyData[] = { + 0x0e, 0x00, 0x02, 0x00, 0x01, 0x00, // PRECISION + 0xda, 0x00, 0x02, 0x00, 0x00, 0x00 // BOOKBOOL +}; +const std::size_t ExcDummy_041::nMyLen = sizeof( ExcDummy_041::pMyData ); + +//-------------------------------------------------------- class ExcDummy_02a - +const sal_uInt8 ExcDummy_02a::pMyData[] = { + 0x0d, 0x00, 0x02, 0x00, 0x01, 0x00, // CALCMODE + 0x0c, 0x00, 0x02, 0x00, 0x64, 0x00, // CALCCOUNT + 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, // REFMODE + 0x11, 0x00, 0x02, 0x00, 0x00, 0x00, // ITERATION + 0x10, 0x00, 0x08, 0x00, 0xfc, 0xa9, 0xf1, 0xd2, 0x4d, // DELTA + 0x62, 0x50, 0x3f, + 0x5f, 0x00, 0x02, 0x00, 0x01, 0x00 // SAVERECALC +}; +const std::size_t ExcDummy_02a::nMyLen = sizeof( ExcDummy_02a::pMyData ); + +//----------------------------------------------------------- class ExcRecord - + +void ExcRecord::Save( XclExpStream& rStrm ) +{ + SetRecHeader( GetNum(), GetLen() ); + XclExpRecord::Save( rStrm ); +} + +void ExcRecord::SaveCont( XclExpStream& /*rStrm*/ ) +{ +} + +void ExcRecord::WriteBody( XclExpStream& rStrm ) +{ + SaveCont( rStrm ); +} + +void ExcRecord::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ +} + +//--------------------------------------------------------- class ExcEmptyRec - + +void ExcEmptyRec::Save( XclExpStream& /*rStrm*/ ) +{ +} + +sal_uInt16 ExcEmptyRec::GetNum() const +{ + return 0; +} + +std::size_t ExcEmptyRec::GetLen() const +{ + return 0; +} + +//--------------------------------------------------------- class ExcDummyRec - + +void ExcDummyRec::Save( XclExpStream& rStrm ) +{ + rStrm.Write( GetData(), GetLen() ); // raw write mode +} + +sal_uInt16 ExcDummyRec::GetNum() const +{ + return 0x0000; +} + +//------------------------------------------------------- class ExcBoolRecord - + +void ExcBoolRecord::SaveCont( XclExpStream& rStrm ) +{ + rStrm << static_cast<sal_uInt16>(bVal ? 0x0001 : 0x0000); +} + +std::size_t ExcBoolRecord::GetLen() const +{ + return 2; +} + +//--------------------------------------------------------- class ExcBof_Base - + +ExcBof_Base::ExcBof_Base() + : nDocType(0) + , nVers(0) + , nRupBuild(0x096C) // copied from Excel + , nRupYear(0x07C9) // copied from Excel +{ +} + +//-------------------------------------------------------------- class ExcBof - + +ExcBof::ExcBof() +{ + nDocType = 0x0010; + nVers = 0x0500; +} + +void ExcBof::SaveCont( XclExpStream& rStrm ) +{ + rStrm << nVers << nDocType << nRupBuild << nRupYear; +} + +sal_uInt16 ExcBof::GetNum() const +{ + return 0x0809; +} + +std::size_t ExcBof::GetLen() const +{ + return 8; +} + +//------------------------------------------------------------- class ExcBofW - + +ExcBofW::ExcBofW() +{ + nDocType = 0x0005; + nVers = 0x0500; +} + +void ExcBofW::SaveCont( XclExpStream& rStrm ) +{ + rStrm << nVers << nDocType << nRupBuild << nRupYear; +} + +sal_uInt16 ExcBofW::GetNum() const +{ + return 0x0809; +} + +std::size_t ExcBofW::GetLen() const +{ + return 8; +} + +//-------------------------------------------------------------- class ExcEof - + +sal_uInt16 ExcEof::GetNum() const +{ + return 0x000A; +} + +std::size_t ExcEof::GetLen() const +{ + return 0; +} + +//--------------------------------------------------------- class ExcDummy_00 - + +std::size_t ExcDummy_00::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_00::GetData() const +{ + return pMyData; +} + +//-------------------------------------------------------- class ExcDummy_04x - + +std::size_t ExcDummy_040::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_040::GetData() const +{ + return pMyData; +} + +std::size_t ExcDummy_041::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_041::GetData() const +{ + return pMyData; +} + +//------------------------------------------------------------- class Exc1904 - + +Exc1904::Exc1904( const ScDocument& rDoc ) +{ + const Date& rDate = rDoc.GetFormatTable()->GetNullDate(); + bVal = (rDate == Date( 1, 1, 1904 )); + bDateCompatibility = (rDate != Date( 30, 12, 1899 )); +} + +sal_uInt16 Exc1904::GetNum() const +{ + return 0x0022; +} + +void Exc1904::SaveXml( XclExpXmlStream& rStrm ) +{ + bool bISOIEC = ( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 ); + + if( bISOIEC ) + { + rStrm.WriteAttributes(XML_dateCompatibility, ToPsz(bDateCompatibility)); + } + + if( !bISOIEC || bDateCompatibility ) + { + rStrm.WriteAttributes(XML_date1904, ToPsz(bVal)); + } +} + +//------------------------------------------------------ class ExcBundlesheet - + +ExcBundlesheetBase::ExcBundlesheetBase( const RootData& rRootData, SCTAB nTabNum ) : + m_nStrPos( STREAM_SEEK_TO_END ), + m_nOwnPos( STREAM_SEEK_TO_END ), + nGrbit( rRootData.pER->GetTabInfo().IsVisibleTab( nTabNum ) ? 0x0000 : 0x0001 ), + nTab( nTabNum ) +{ +} + +ExcBundlesheetBase::ExcBundlesheetBase() : + m_nStrPos( STREAM_SEEK_TO_END ), + m_nOwnPos( STREAM_SEEK_TO_END ), + nGrbit( 0x0000 ), + nTab( SCTAB_GLOBAL ) +{ +} + +void ExcBundlesheetBase::UpdateStreamPos( XclExpStream& rStrm ) +{ + rStrm.SetSvStreamPos( m_nOwnPos ); + rStrm.DisableEncryption(); + rStrm << static_cast<sal_uInt32>(m_nStrPos); + rStrm.EnableEncryption(); +} + +sal_uInt16 ExcBundlesheetBase::GetNum() const +{ + return 0x0085; +} + +ExcBundlesheet::ExcBundlesheet( const RootData& rRootData, SCTAB _nTab ) : + ExcBundlesheetBase( rRootData, _nTab ) +{ + OUString sTabName = rRootData.pER->GetTabInfo().GetScTabName( _nTab ); + OSL_ENSURE( sTabName.getLength() < 256, "ExcBundlesheet::ExcBundlesheet - table name too long" ); + aName = OUStringToOString(sTabName, rRootData.pER->GetTextEncoding()); +} + +void ExcBundlesheet::SaveCont( XclExpStream& rStrm ) +{ + m_nOwnPos = rStrm.GetSvStreamPos(); + rStrm << sal_uInt32(0x00000000) // dummy (stream position of the sheet) + << nGrbit; + rStrm.WriteByteString(aName); // 8 bit length, max 255 chars +} + +std::size_t ExcBundlesheet::GetLen() const +{ + return 7 + std::min( aName.getLength(), sal_Int32(255) ); +} + +//--------------------------------------------------------- class ExcDummy_02 - + +std::size_t ExcDummy_02a::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_02a::GetData() const +{ + return pMyData; +} +//--------------------------------------------------------- class ExcDummy_02 - + +XclExpCountry::XclExpCountry( const XclExpRoot& rRoot ) : + XclExpRecord( EXC_ID_COUNTRY, 4 ) +{ + /* #i31530# set document country as UI country too - + needed for correct behaviour of number formats. */ + mnUICountry = mnDocCountry = static_cast< sal_uInt16 >( + ::msfilter::ConvertLanguageToCountry( rRoot.GetDocLanguage() ) ); +} + +void XclExpCountry::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnUICountry << mnDocCountry; +} + +// XclExpWsbool =============================================================== + +XclExpWsbool::XclExpWsbool( bool bFitToPages ) + : XclExpUInt16Record( EXC_ID_WSBOOL, EXC_WSBOOL_DEFAULTFLAGS ) +{ + if( bFitToPages ) + SetValue( GetValue() | EXC_WSBOOL_FITTOPAGE ); +} + +XclExpXmlSheetPr::XclExpXmlSheetPr( bool bFitToPages, SCTAB nScTab, const Color& rTabColor, XclExpFilterManager* pManager ) : + mnScTab(nScTab), mpManager(pManager), mbFitToPage(bFitToPages), maTabColor(rTabColor) {} + +void XclExpXmlSheetPr::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_sheetPr, + // OOXTODO: XML_syncHorizontal, + // OOXTODO: XML_syncVertical, + // OOXTODO: XML_syncRef, + // OOXTODO: XML_transitionEvaluation, + // OOXTODO: XML_transitionEntry, + // OOXTODO: XML_published, + // OOXTODO: XML_codeName, + XML_filterMode, mpManager ? ToPsz(mpManager->HasFilterMode(mnScTab)) : nullptr + // OOXTODO: XML_enableFormatConditionsCalculation + ); + + // Note : the order of child elements is significant. Don't change the order. + + // OOXTODO: XML_outlinePr + + if (maTabColor != COL_AUTO) + rWorksheet->singleElement(XML_tabColor, XML_rgb, XclXmlUtils::ToOString(maTabColor)); + + rWorksheet->singleElement(XML_pageSetUpPr, + // OOXTODO: XML_autoPageBreaks, + XML_fitToPage, ToPsz(mbFitToPage)); + + rWorksheet->endElement( XML_sheetPr ); +} + +// XclExpWindowProtection =============================================================== + +XclExpWindowProtection::XclExpWindowProtection(bool bValue) : + XclExpBoolRecord(EXC_ID_WINDOWPROTECT, bValue) +{ +} + +void XclExpWindowProtection::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.WriteAttributes(XML_lockWindows, ToPsz(GetBool())); +} + +// XclExpDocProtection =============================================================== + +XclExpProtection::XclExpProtection(bool bValue) : + XclExpBoolRecord(EXC_ID_PROTECT, bValue) +{ +} + +XclExpSheetProtection::XclExpSheetProtection(bool bValue, SCTAB nTab ) : + XclExpProtection( bValue), + mnTab(nTab) +{ +} + +void XclExpSheetProtection::SaveXml( XclExpXmlStream& rStrm ) +{ + ScDocument& rDoc = rStrm.GetRoot().GetDoc(); + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(mnTab); + if ( !pTabProtect ) + return; + + const ScOoxPasswordHash& rPH = pTabProtect->getPasswordHash(); + // Do not write any hash attributes if there is no password. + ScOoxPasswordHash aPH; + if (rPH.hasPassword()) + aPH = rPH; + + Sequence<sal_Int8> aHash = pTabProtect->getPasswordHash(PASSHASH_XL); + OString sHash; + if (aHash.getLength() >= 2) + { + sHash = OString::number( + ( static_cast<sal_uInt8>(aHash[0]) << 8 + | static_cast<sal_uInt8>(aHash[1]) ), + 16 ); + } + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->singleElement( XML_sheetProtection, + XML_algorithmName, aPH.maAlgorithmName.isEmpty() ? nullptr : aPH.maAlgorithmName.toUtf8().getStr(), + XML_hashValue, aPH.maHashValue.isEmpty() ? nullptr : aPH.maHashValue.toUtf8().getStr(), + XML_saltValue, aPH.maSaltValue.isEmpty() ? nullptr : aPH.maSaltValue.toUtf8().getStr(), + XML_spinCount, aPH.mnSpinCount ? OString::number( aPH.mnSpinCount).getStr() : nullptr, + XML_sheet, ToPsz( true ), + XML_password, sHash.isEmpty()? nullptr : sHash.getStr(), + XML_objects, pTabProtect->isOptionEnabled( ScTableProtection::OBJECTS ) ? nullptr : ToPsz( true ), + XML_scenarios, pTabProtect->isOptionEnabled( ScTableProtection::SCENARIOS ) ? nullptr : ToPsz( true ), + XML_formatCells, pTabProtect->isOptionEnabled( ScTableProtection::FORMAT_CELLS ) ? ToPsz( false ) : nullptr, + XML_formatColumns, pTabProtect->isOptionEnabled( ScTableProtection::FORMAT_COLUMNS ) ? ToPsz( false ) : nullptr, + XML_formatRows, pTabProtect->isOptionEnabled( ScTableProtection::FORMAT_ROWS ) ? ToPsz( false ) : nullptr, + XML_insertColumns, pTabProtect->isOptionEnabled( ScTableProtection::INSERT_COLUMNS ) ? ToPsz( false ) : nullptr, + XML_insertRows, pTabProtect->isOptionEnabled( ScTableProtection::INSERT_ROWS ) ? ToPsz( false ) : nullptr, + XML_insertHyperlinks, pTabProtect->isOptionEnabled( ScTableProtection::INSERT_HYPERLINKS ) ? ToPsz( false ) : nullptr, + XML_deleteColumns, pTabProtect->isOptionEnabled( ScTableProtection::DELETE_COLUMNS ) ? ToPsz( false ) : nullptr, + XML_deleteRows, pTabProtect->isOptionEnabled( ScTableProtection::DELETE_ROWS ) ? ToPsz( false ) : nullptr, + XML_selectLockedCells, pTabProtect->isOptionEnabled( ScTableProtection::SELECT_LOCKED_CELLS ) ? nullptr : ToPsz( true ), + XML_sort, pTabProtect->isOptionEnabled( ScTableProtection::SORT ) ? ToPsz( false ) : nullptr, + XML_autoFilter, pTabProtect->isOptionEnabled( ScTableProtection::AUTOFILTER ) ? ToPsz( false ) : nullptr, + XML_pivotTables, pTabProtect->isOptionEnabled( ScTableProtection::PIVOT_TABLES ) ? ToPsz( false ) : nullptr, + XML_selectUnlockedCells, pTabProtect->isOptionEnabled( ScTableProtection::SELECT_UNLOCKED_CELLS ) ? nullptr : ToPsz( true ) ); + + const ::std::vector<ScEnhancedProtection>& rProts( pTabProtect->getEnhancedProtection()); + if (rProts.empty()) + return; + + rWorksheet->startElement(XML_protectedRanges); + for (const auto& rProt : rProts) + { + SAL_WARN_IF( rProt.maSecurityDescriptorXML.isEmpty() && !rProt.maSecurityDescriptor.empty(), + "sc.filter", "XclExpSheetProtection::SaveXml: losing BIFF security descriptor"); + rWorksheet->singleElement( XML_protectedRange, + XML_name, rProt.maTitle.isEmpty() ? nullptr : rProt.maTitle.toUtf8().getStr(), + XML_securityDescriptor, rProt.maSecurityDescriptorXML.isEmpty() ? nullptr : rProt.maSecurityDescriptorXML.toUtf8().getStr(), + /* XXX 'password' is not part of OOXML, but Excel2013 + * writes it if loaded from BIFF, in which case + * 'algorithmName', 'hashValue', 'saltValue' and + * 'spinCount' are absent; so do we if it was present. */ + XML_password, rProt.mnPasswordVerifier ? OString::number( rProt.mnPasswordVerifier, 16).getStr() : nullptr, + XML_algorithmName, rProt.maPasswordHash.maAlgorithmName.isEmpty() ? nullptr : rProt.maPasswordHash.maAlgorithmName.toUtf8().getStr(), + XML_hashValue, rProt.maPasswordHash.maHashValue.isEmpty() ? nullptr : rProt.maPasswordHash.maHashValue.toUtf8().getStr(), + XML_saltValue, rProt.maPasswordHash.maSaltValue.isEmpty() ? nullptr : rProt.maPasswordHash.maSaltValue.toUtf8().getStr(), + XML_spinCount, rProt.maPasswordHash.mnSpinCount ? OString::number( rProt.maPasswordHash.mnSpinCount).getStr() : nullptr, + XML_sqref, rProt.maRangeList.is() ? XclXmlUtils::ToOString( rStrm.GetRoot().GetDoc(), *rProt.maRangeList).getStr() : nullptr); + } + rWorksheet->endElement( XML_protectedRanges); +} + +XclExpPassHash::XclExpPassHash(const Sequence<sal_Int8>& aHash) : + XclExpRecord(EXC_ID_PASSWORD, 2), + mnHash(0x0000) +{ + if (aHash.getLength() >= 2) + { + mnHash = ((aHash[0] << 8) & 0xFFFF); + mnHash |= (aHash[1] & 0xFF); + } +} + +XclExpPassHash::~XclExpPassHash() +{ +} + +void XclExpPassHash::WriteBody(XclExpStream& rStrm) +{ + rStrm << mnHash; +} + +XclExpFiltermode::XclExpFiltermode() : + XclExpEmptyRecord( EXC_ID_FILTERMODE ) +{ +} + +XclExpAutofilterinfo::XclExpAutofilterinfo( const ScAddress& rStartPos, SCCOL nScCol ) : + XclExpUInt16Record( EXC_ID_AUTOFILTERINFO, static_cast< sal_uInt16 >( nScCol ) ), + maStartPos( rStartPos ) +{ +} + +ExcFilterCondition::ExcFilterCondition() : + nType( EXC_AFTYPE_NOTUSED ), + nOper( EXC_AFOPER_EQUAL ) +{ +} + +ExcFilterCondition::~ExcFilterCondition() +{ +} + +std::size_t ExcFilterCondition::GetTextBytes() const +{ + return pText ? (1 + pText->GetBufferSize()) : 0; +} + +void ExcFilterCondition::SetCondition( sal_uInt8 nTp, sal_uInt8 nOp, const OUString* pT ) +{ + nType = nTp; + nOper = nOp; + pText.reset( pT ? new XclExpString( *pT, XclStrFlags::EightBitLength ) : nullptr); +} + +void ExcFilterCondition::Save( XclExpStream& rStrm ) +{ + rStrm << nType << nOper; + if (nType == EXC_AFTYPE_STRING) + { + OSL_ENSURE(pText, "ExcFilterCondition::Save() -- pText is NULL!"); + rStrm << sal_uInt32(0) << static_cast<sal_uInt8>(pText->Len()) << sal_uInt16(0) << sal_uInt8(0); + } + else + rStrm << sal_uInt32(0) << sal_uInt32(0); +} + +static const char* lcl_GetOperator( sal_uInt8 nOper ) +{ + switch( nOper ) + { + case EXC_AFOPER_EQUAL: return "equal"; + case EXC_AFOPER_GREATER: return "greaterThan"; + case EXC_AFOPER_GREATEREQUAL: return "greaterThanOrEqual"; + case EXC_AFOPER_LESS: return "lessThan"; + case EXC_AFOPER_LESSEQUAL: return "lessThanOrEqual"; + case EXC_AFOPER_NOTEQUAL: return "notEqual"; + case EXC_AFOPER_NONE: + default: return "**none**"; + } +} + +static OString lcl_GetValue( sal_uInt8 nType, const XclExpString* pStr ) +{ + if (nType == EXC_AFTYPE_STRING) + return XclXmlUtils::ToOString(*pStr); + else + return OString(); +} + +void ExcFilterCondition::SaveXml( XclExpXmlStream& rStrm ) +{ + if( IsEmpty() ) + return; + + rStrm.GetCurrentStream()->singleElement( XML_customFilter, + XML_operator, lcl_GetOperator( nOper ), + XML_val, lcl_GetValue(nType, pText.get()) ); +} + +void ExcFilterCondition::SaveText( XclExpStream& rStrm ) +{ + if( nType == EXC_AFTYPE_STRING ) + { + OSL_ENSURE( pText, "ExcFilterCondition::SaveText() -- pText is NULL!" ); + pText->WriteFlagField( rStrm ); + pText->WriteBuffer( rStrm ); + } +} + +XclExpAutofilter::XclExpAutofilter( const XclExpRoot& rRoot, sal_uInt16 nC ) : + XclExpRecord( EXC_ID_AUTOFILTER, 24 ), + XclExpRoot( rRoot ), + meType(FilterCondition), + nCol( nC ), + nFlags( 0 ), + bHasBlankValue( false ) +{ +} + +bool XclExpAutofilter::AddCondition( ScQueryConnect eConn, sal_uInt8 nType, sal_uInt8 nOp, + const OUString* pText, bool bSimple ) +{ + if( !aCond[ 1 ].IsEmpty() ) + return false; + + sal_uInt16 nInd = aCond[ 0 ].IsEmpty() ? 0 : 1; + + if( nInd == 1 ) + nFlags |= (eConn == SC_OR) ? EXC_AFFLAG_OR : EXC_AFFLAG_AND; + if( bSimple ) + nFlags |= (nInd == 0) ? EXC_AFFLAG_SIMPLE1 : EXC_AFFLAG_SIMPLE2; + + aCond[ nInd ].SetCondition( nType, nOp, pText ); + + AddRecSize( aCond[ nInd ].GetTextBytes() ); + + return true; +} + +bool XclExpAutofilter::HasCondition() const +{ + return !aCond[0].IsEmpty(); +} + +bool XclExpAutofilter::AddEntry( const ScQueryEntry& rEntry ) +{ + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + + if (rItems.empty()) + { + if (GetOutput() != EXC_OUTPUT_BINARY) + { + // tdf#123353 XLSX export + meType = BlankValue; + return false; + } + // XLS export + return true; + } + + if (GetOutput() != EXC_OUTPUT_BINARY && rItems.size() > 1) + { + AddMultiValueEntry(rEntry); + return false; + } + + bool bConflict = false; + OUString sText; + const ScQueryEntry::Item& rItem = rItems[0]; + if (!rItem.maString.isEmpty()) + { + sText = rItem.maString.getString(); + switch( rEntry.eOp ) + { + case SC_CONTAINS: + case SC_DOES_NOT_CONTAIN: + { + sText = "*" + sText + "*"; + } + break; + case SC_BEGINS_WITH: + case SC_DOES_NOT_BEGIN_WITH: + sText += "*"; + break; + case SC_ENDS_WITH: + case SC_DOES_NOT_END_WITH: + sText = "*" + sText; + break; + default: + { + //nothing + } + } + } + + // empty/nonempty fields + if (rEntry.IsQueryByEmpty()) + { + bConflict = !AddCondition(rEntry.eConnect, EXC_AFTYPE_EMPTY, EXC_AFOPER_NONE, nullptr, true); + bHasBlankValue = true; + } + else if(rEntry.IsQueryByNonEmpty()) + bConflict = !AddCondition( rEntry.eConnect, EXC_AFTYPE_NOTEMPTY, EXC_AFOPER_NONE, nullptr, true ); + else if (rEntry.IsQueryByTextColor() || rEntry.IsQueryByBackgroundColor()) + { + AddColorEntry(rEntry); + } + // other conditions + else + { + // top10 flags + sal_uInt16 nNewFlags = 0x0000; + switch( rEntry.eOp ) + { + case SC_TOPVAL: + nNewFlags = (EXC_AFFLAG_TOP10 | EXC_AFFLAG_TOP10TOP); + break; + case SC_BOTVAL: + nNewFlags = EXC_AFFLAG_TOP10; + break; + case SC_TOPPERC: + nNewFlags = (EXC_AFFLAG_TOP10 | EXC_AFFLAG_TOP10TOP | EXC_AFFLAG_TOP10PERC); + break; + case SC_BOTPERC: + nNewFlags = (EXC_AFFLAG_TOP10 | EXC_AFFLAG_TOP10PERC); + break; + default:; + } + bool bNewTop10 = ::get_flag( nNewFlags, EXC_AFFLAG_TOP10 ); + + bConflict = HasTop10() && bNewTop10; + if( !bConflict ) + { + if( bNewTop10 ) + { + sal_uInt32 nIndex = 0; + double fVal = 0.0; + if (GetFormatter().IsNumberFormat(sText, nIndex, fVal)) + { + if (fVal < 0) fVal = 0; + if (fVal >= 501) fVal = 500; + } + nFlags |= (nNewFlags | static_cast<sal_uInt16>(fVal) << 7); + } + // normal condition + else + { + if (GetOutput() != EXC_OUTPUT_BINARY && rEntry.eOp == SC_EQUAL) + { + AddMultiValueEntry(rEntry); + return false; + } + + sal_uInt8 nOper = EXC_AFOPER_NONE; + + switch( rEntry.eOp ) + { + case SC_EQUAL: nOper = EXC_AFOPER_EQUAL; break; + case SC_LESS: nOper = EXC_AFOPER_LESS; break; + case SC_GREATER: nOper = EXC_AFOPER_GREATER; break; + case SC_LESS_EQUAL: nOper = EXC_AFOPER_LESSEQUAL; break; + case SC_GREATER_EQUAL: nOper = EXC_AFOPER_GREATEREQUAL; break; + case SC_NOT_EQUAL: nOper = EXC_AFOPER_NOTEQUAL; break; + case SC_CONTAINS: + case SC_BEGINS_WITH: + case SC_ENDS_WITH: + nOper = EXC_AFOPER_EQUAL; break; + case SC_DOES_NOT_CONTAIN: + case SC_DOES_NOT_BEGIN_WITH: + case SC_DOES_NOT_END_WITH: + nOper = EXC_AFOPER_NOTEQUAL; break; + default:; + } + bConflict = !AddCondition( rEntry.eConnect, EXC_AFTYPE_STRING, nOper, &sText); + } + } + } + return bConflict; +} + +void XclExpAutofilter::AddMultiValueEntry( const ScQueryEntry& rEntry ) +{ + meType = MultiValue; + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + for (const auto& rItem : rItems) + { + if( rItem.maString.isEmpty() ) + bHasBlankValue = true; + else + maMultiValues.push_back(std::make_pair(rItem.maString.getString(), rItem.meType == ScQueryEntry::ByDate)); + } +} + +void XclExpAutofilter::AddColorEntry(const ScQueryEntry& rEntry) +{ + meType = ColorValue; + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + for (const auto& rItem : rItems) + { + maColorValues.push_back( + std::make_pair(rItem.maColor, rItem.meType == ScQueryEntry::ByBackgroundColor)); + // Ensure that selected color(s) will be added to dxf: selection can be not in list + // of already added to dfx colors taken from filter range + if (GetDxfs().GetDxfByColor(rItem.maColor) == -1) + GetDxfs().AddColor(rItem.maColor); + } +} + +void XclExpAutofilter::WriteBody( XclExpStream& rStrm ) +{ + rStrm << nCol << nFlags; + aCond[ 0 ].Save( rStrm ); + aCond[ 1 ].Save( rStrm ); + aCond[ 0 ].SaveText( rStrm ); + aCond[ 1 ].SaveText( rStrm ); +} + +void XclExpAutofilter::SaveXml( XclExpXmlStream& rStrm ) +{ + if (meType == FilterCondition && !HasCondition() && !HasTop10()) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_filterColumn, + XML_colId, OString::number(nCol) + // OOXTODO: XML_hiddenButton, AutoFilter12 fHideArrow? + // OOXTODO: XML_showButton + ); + + switch (meType) + { + case FilterCondition: + { + if( HasTop10() ) + { + rWorksheet->singleElement( XML_top10, + XML_top, ToPsz( get_flag( nFlags, EXC_AFFLAG_TOP10TOP ) ), + XML_percent, ToPsz( get_flag( nFlags, EXC_AFFLAG_TOP10PERC ) ), + XML_val, OString::number(nFlags >> 7) + // OOXTODO: XML_filterVal + ); + } + else + { + rWorksheet->startElement(XML_customFilters, XML_and, + ToPsz((nFlags & EXC_AFFLAG_ANDORMASK) == EXC_AFFLAG_AND)); + aCond[0].SaveXml(rStrm); + aCond[1].SaveXml(rStrm); + rWorksheet->endElement(XML_customFilters); + } + // OOXTODO: XML_dynamicFilter, XML_extLst, XML_filters, XML_iconFilter + } + break; + case ColorValue: + { + if (!maColorValues.empty()) + { + Color color = maColorValues[0].first; + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + + if (maColorValues[0].second) // is background color + { + pAttrList->add(XML_cellColor, OString::number(1)); + } + else + { + pAttrList->add(XML_cellColor, OString::number(0)); + } + pAttrList->add(XML_dxfId, OString::number(GetDxfs().GetDxfByColor(color))); + rWorksheet->singleElement(XML_colorFilter, pAttrList); + } + } + break; + case BlankValue: + { + rWorksheet->singleElement(XML_filters, XML_blank, "1"); + } + break; + case MultiValue: + { + if( bHasBlankValue ) + rWorksheet->startElement(XML_filters, XML_blank, "1"); + else + rWorksheet->startElement(XML_filters); + + for (const auto& rMultiValue : maMultiValues) + { + OString aStr = OUStringToOString(rMultiValue.first, RTL_TEXTENCODING_UTF8); + if( !rMultiValue.second ) + { + const char* pz = aStr.getStr(); + rWorksheet->singleElement(XML_filter, XML_val, pz); + } + else + { + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + sal_Int32 aDateGroup[3] = { XML_year, XML_month, XML_day }; + sal_Int32 idx = 0; + for (size_t i = 0; idx >= 0 && i < 3; i++) + { + OString kw = aStr.getToken(0, '-', idx); + kw = kw.trim(); + if (!kw.isEmpty()) + { + pAttrList->add(aDateGroup[i], kw); + } + } + // TODO: date filter can only handle YYYY-MM-DD date formats, so XML_dateTimeGrouping value + // will be "day" as default, until date filter cannot handle HH:MM:SS. + pAttrList->add(XML_dateTimeGrouping, "day"); + rWorksheet->singleElement(XML_dateGroupItem, pAttrList); + } + } + rWorksheet->endElement(XML_filters); + } + break; + } + rWorksheet->endElement( XML_filterColumn ); +} + +ExcAutoFilterRecs::ExcAutoFilterRecs( const XclExpRoot& rRoot, SCTAB nTab, const ScDBData* pDefinedData ) : + XclExpRoot( rRoot ), + mbAutoFilter (false) +{ + XclExpNameManager& rNameMgr = GetNameManager(); + + bool bFound = false; + bool bAdvanced = false; + const ScDBData* pData = (pDefinedData ? pDefinedData : rRoot.GetDoc().GetAnonymousDBData(nTab)); + ScRange aAdvRange; + if (pData) + { + bAdvanced = pData->GetAdvancedQuerySource( aAdvRange ); + bFound = (pData->HasQueryParam() || pData->HasAutoFilter() || bAdvanced); + } + if( !bFound ) + return; + + ScQueryParam aParam; + pData->GetQueryParam( aParam ); + + ScRange aRange( aParam.nCol1, aParam.nRow1, aParam.nTab, + aParam.nCol2, aParam.nRow2, aParam.nTab ); + SCCOL nColCnt = aParam.nCol2 - aParam.nCol1 + 1; + + maRef = aRange; + + // #i2394# built-in defined names must be sorted by containing sheet name + if (!pDefinedData) + rNameMgr.InsertBuiltInName( EXC_BUILTIN_FILTERDATABASE, aRange ); + + // advanced filter + if( bAdvanced ) + { + // filter criteria, excel allows only same table + if( !pDefinedData && aAdvRange.aStart.Tab() == nTab ) + rNameMgr.InsertBuiltInName( EXC_BUILTIN_CRITERIA, aAdvRange ); + + // filter destination range, excel allows only same table + if( !aParam.bInplace ) + { + ScRange aDestRange( aParam.nDestCol, aParam.nDestRow, aParam.nDestTab ); + aDestRange.aEnd.IncCol( nColCnt - 1 ); + if( !pDefinedData && aDestRange.aStart.Tab() == nTab ) + rNameMgr.InsertBuiltInName( EXC_BUILTIN_EXTRACT, aDestRange ); + } + + m_pFilterMode = new XclExpFiltermode; + } + // AutoFilter + else + { + bool bConflict = false; + bool bContLoop = true; + bool bHasOr = false; + SCCOLROW nFirstField = aParam.GetEntry( 0 ).nField; + + // create AUTOFILTER records for filtered columns + for( SCSIZE nEntry = 0; !bConflict && bContLoop && (nEntry < aParam.GetEntryCount()); nEntry++ ) + { + const ScQueryEntry& rEntry = aParam.GetEntry( nEntry ); + + bContLoop = rEntry.bDoQuery; + if( bContLoop ) + { + XclExpAutofilter* pFilter = GetByCol( static_cast<SCCOL>(rEntry.nField) - aRange.aStart.Col() ); + + if( nEntry > 0 ) + bHasOr |= (rEntry.eConnect == SC_OR); + + bConflict = (nEntry > 1) && bHasOr; + if( !bConflict ) + bConflict = (nEntry == 1) && (rEntry.eConnect == SC_OR) && + (nFirstField != rEntry.nField); + if( !bConflict ) + bConflict = pFilter->AddEntry( rEntry ); + } + } + + // additional tests for conflicts + for( size_t nPos = 0, nSize = maFilterList.GetSize(); !bConflict && (nPos < nSize); ++nPos ) + { + XclExpAutofilterRef xFilter = maFilterList.GetRecord( nPos ); + bConflict = xFilter->HasCondition() && xFilter->HasTop10(); + } + + if( bConflict ) + maFilterList.RemoveAllRecords(); + + if( !maFilterList.IsEmpty() ) + m_pFilterMode = new XclExpFiltermode; + m_pFilterInfo = new XclExpAutofilterinfo( aRange.aStart, nColCnt ); + + if (maFilterList.IsEmpty () && !bConflict) + mbAutoFilter = true; + + // get sort criteria + { + ScSortParam aSortParam; + pData->GetSortParam( aSortParam ); + + ScUserList* pList = ScGlobal::GetUserList(); + if (aSortParam.bUserDef && pList && pList->size() > aSortParam.nUserIndex) + { + // get sorted area without headers + maSortRef = ScRange( + aParam.nCol1, aParam.nRow1 + (aSortParam.bHasHeader? 1 : 0), aParam.nTab, + aParam.nCol2, aParam.nRow2, aParam.nTab ); + + // get sorted columns with custom lists + const ScUserListData& rData = (*pList)[aSortParam.nUserIndex]; + + // get column index and sorting direction + SCCOLROW nField = 0; + bool bSortAscending=true; + for (const auto & rKey : aSortParam.maKeyState) + { + if (rKey.bDoSort) + { + nField = rKey.nField; + bSortAscending = rKey.bAscending; + break; + } + } + + // remember sort criteria + const ScRange aSortedColumn( + nField, aParam.nRow1 + (aSortParam.bHasHeader? 1 : 0), aParam.nTab, + nField, aParam.nRow2, aParam.nTab ); + const OUString aItemList = rData.GetString(); + + maSortCustomList.emplace_back(aSortedColumn, aItemList, !bSortAscending); + } + } + } +} + +ExcAutoFilterRecs::~ExcAutoFilterRecs() +{ +} + +XclExpAutofilter* ExcAutoFilterRecs::GetByCol( SCCOL nCol ) +{ + XclExpAutofilterRef xFilter; + for( size_t nPos = 0, nSize = maFilterList.GetSize(); nPos < nSize; ++nPos ) + { + xFilter = maFilterList.GetRecord( nPos ); + if( xFilter->GetCol() == static_cast<sal_uInt16>(nCol) ) + return xFilter.get(); + } + xFilter = new XclExpAutofilter( GetRoot(), static_cast<sal_uInt16>(nCol) ); + maFilterList.AppendRecord( xFilter ); + return xFilter.get(); +} + +bool ExcAutoFilterRecs::IsFiltered( SCCOL nCol ) +{ + for( size_t nPos = 0, nSize = maFilterList.GetSize(); nPos < nSize; ++nPos ) + if( maFilterList.GetRecord( nPos )->GetCol() == static_cast<sal_uInt16>(nCol) ) + return true; + return false; +} + +void ExcAutoFilterRecs::AddObjRecs() +{ + if( m_pFilterInfo ) + { + ScAddress aAddr( m_pFilterInfo->GetStartPos() ); + for( SCCOL nObj = 0, nCount = m_pFilterInfo->GetColCount(); nObj < nCount; nObj++ ) + { + std::unique_ptr<XclObj> pObjRec(new XclObjDropDown( GetObjectManager(), aAddr, IsFiltered( nObj ) )); + GetObjectManager().AddObj( std::move(pObjRec) ); + aAddr.IncCol(); + } + } +} + +void ExcAutoFilterRecs::Save( XclExpStream& rStrm ) +{ + if( m_pFilterMode ) + m_pFilterMode->Save( rStrm ); + if( m_pFilterInfo ) + m_pFilterInfo->Save( rStrm ); + maFilterList.Save( rStrm ); +} + +void ExcAutoFilterRecs::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maFilterList.IsEmpty() && !mbAutoFilter ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_autoFilter, XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maRef)); + // OOXTODO: XML_extLst, XML_sortState + if( !maFilterList.IsEmpty() ) + maFilterList.SaveXml( rStrm ); + + if (!maSortCustomList.empty()) + { + rWorksheet->startElement(XML_sortState, XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maSortRef)); + + for (const auto & rSortCriteria : maSortCustomList) + { + if (std::get<2>(rSortCriteria)) + rWorksheet->singleElement(XML_sortCondition, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), + std::get<0>(rSortCriteria)), + XML_descending, "1", + XML_customList, std::get<1>(rSortCriteria).toUtf8().getStr()); + else + rWorksheet->singleElement(XML_sortCondition, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), + std::get<0>(rSortCriteria)), + XML_customList, std::get<1>(rSortCriteria).toUtf8().getStr()); + } + + rWorksheet->endElement(XML_sortState); + } + + rWorksheet->endElement( XML_autoFilter ); +} + +bool ExcAutoFilterRecs::HasFilterMode() const +{ + return m_pFilterMode != nullptr; +} + +XclExpFilterManager::XclExpFilterManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpFilterManager::InitTabFilter( SCTAB nScTab ) +{ + maFilterMap[ nScTab ] = new ExcAutoFilterRecs( GetRoot(), nScTab, nullptr ); +} + +XclExpRecordRef XclExpFilterManager::CreateRecord( SCTAB nScTab ) +{ + XclExpTabFilterRef xRec; + XclExpTabFilterMap::iterator aIt = maFilterMap.find( nScTab ); + if( aIt != maFilterMap.end() ) + { + xRec = aIt->second; + xRec->AddObjRecs(); + } + return xRec; +} + +bool XclExpFilterManager::HasFilterMode( SCTAB nScTab ) +{ + XclExpTabFilterMap::iterator aIt = maFilterMap.find( nScTab ); + if( aIt != maFilterMap.end() ) + { + return aIt->second->HasFilterMode(); + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/exctools.cxx b/sc/source/filter/excel/exctools.cxx new file mode 100644 index 000000000..6e9d91777 --- /dev/null +++ b/sc/source/filter/excel/exctools.cxx @@ -0,0 +1,245 @@ +/* -*- 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 <osl/diagnose.h> +#include <document.hxx> +#include <attrib.hxx> +#include <scextopt.hxx> +#include <olinetab.hxx> + +#include <root.hxx> +#include <excimp8.hxx> +#include <namebuff.hxx> +#include <otlnbuff.hxx> +#include <formel.hxx> +#include <xilink.hxx> + +#include <memory> +#include <vector> + +RootData::RootData() +{ + eDateiTyp = BiffX; + pFmlaConverter = nullptr; + + pTabId = nullptr; + pUserBViewList = nullptr; + + pIR = nullptr; + pER = nullptr; + pColRowBuff = nullptr; +} + +RootData::~RootData() +{ + pExtSheetBuff.reset(); + pShrfmlaBuff.reset(); + pExtNameBuff.reset(); + pAutoFilterBuffer.reset(); +} + +XclImpOutlineBuffer::XclImpOutlineBuffer( SCSIZE nNewSize ) : + maLevels(0, nNewSize, 0), + mpOutlineArray(nullptr), + mnEndPos(nNewSize), + mnMaxLevel(0), + mbButtonAfter(true) +{ +} + +XclImpOutlineBuffer::~XclImpOutlineBuffer() +{ +} + +void XclImpOutlineBuffer::SetLevel( SCSIZE nIndex, sal_uInt8 nVal, bool bCollapsed ) +{ + maLevels.insert_back(nIndex, nIndex+1, nVal); + if (nVal > mnMaxLevel) + mnMaxLevel = nVal; + if (bCollapsed) + maCollapsedPosSet.insert(nIndex); +} + +void XclImpOutlineBuffer::SetOutlineArray( ScOutlineArray* pOArray ) +{ + mpOutlineArray = pOArray; +} + +void XclImpOutlineBuffer::MakeScOutline() +{ + if (!mpOutlineArray) + return; + + ::std::vector<SCSIZE> aOutlineStack; + aOutlineStack.reserve(mnMaxLevel); + for (const auto& [nPos, nLevel] : maLevels) + { + if (nPos >= mnEndPos) + { + // Don't go beyond the max allowed position. + OSL_ENSURE(aOutlineStack.empty(), "XclImpOutlineBuffer::MakeScOutline: outline stack not empty but expected to be."); + break; + } + sal_uInt8 nCurLevel = static_cast<sal_uInt8>(aOutlineStack.size()); + if (nLevel > nCurLevel) + { + for (sal_uInt8 i = 0; i < nLevel - nCurLevel; ++i) + aOutlineStack.push_back(nPos); + } + else + { + OSL_ENSURE(nLevel <= nCurLevel, "XclImpOutlineBuffer::MakeScOutline: unexpected level!"); + for (sal_uInt8 i = 0; i < nCurLevel - nLevel; ++i) + { + if (aOutlineStack.empty()) + { + // Something is wrong. + return; + } + SCSIZE nFirstPos = aOutlineStack.back(); + aOutlineStack.pop_back(); + bool bCollapsed = false; + if (mbButtonAfter) + bCollapsed = maCollapsedPosSet.count(nPos) > 0; + else if (nFirstPos > 0) + bCollapsed = maCollapsedPosSet.count(nFirstPos-1) > 0; + + bool bDummy; + mpOutlineArray->Insert(nFirstPos, nPos-1, bDummy, bCollapsed); + } + } + } +} + +void XclImpOutlineBuffer::SetLevelRange( SCSIZE nF, SCSIZE nL, sal_uInt8 nVal, bool bCollapsed ) +{ + if (nF > nL) + // invalid range + return; + + maLevels.insert_back(nF, nL+1, nVal); + + if (bCollapsed) + maCollapsedPosSet.insert(nF); +} + +void XclImpOutlineBuffer::SetButtonMode( bool bRightOrUnder ) +{ + mbButtonAfter = bRightOrUnder; +} + +ExcScenarioCell::ExcScenarioCell( const sal_uInt16 nC, const sal_uInt16 nR ) + : nCol( nC ), nRow( nR ) +{ +} + +ExcScenario::ExcScenario( XclImpStream& rIn, const RootData& rR ) + : nTab( rR.pIR->GetCurrScTab() ) +{ + sal_uInt16 nCref; + sal_uInt8 nName, nComment; + + nCref = rIn.ReaduInt16(); + nProtected = rIn.ReaduInt8(); + rIn.Ignore( 1 ); // Hide + nName = rIn.ReaduInt8(); + nComment = rIn.ReaduInt8(); + rIn.Ignore( 1 ); // instead of nUser! + + if( nName ) + aName = rIn.ReadUniString( nName ); + else + { + aName = "Scenery"; + rIn.Ignore( 1 ); + } + + rIn.ReadUniString(); // username + + if( nComment ) + aComment = rIn.ReadUniString(); + + sal_uInt16 n = nCref; + sal_uInt16 nC, nR; + aEntries.reserve(n); + while( n ) + { + nR = rIn.ReaduInt16(); + nC = rIn.ReaduInt16(); + + aEntries.emplace_back( nC, nR ); + + n--; + } + + for (auto& rEntry : aEntries) + rEntry.SetValue(rIn.ReadUniString()); +} + +void ExcScenario::Apply( const XclImpRoot& rRoot, const bool bLast ) +{ + ScDocument& r = rRoot.GetDoc(); + OUString aSzenName( aName ); + sal_uInt16 nNewTab = nTab + 1; + + if( !r.InsertTab( nNewTab, aSzenName ) ) + return; + + r.SetScenario( nNewTab, true ); + // do not show scenario frames + const ScScenarioFlags nFlags = ScScenarioFlags::CopyAll + | (nProtected ? ScScenarioFlags::Protected : ScScenarioFlags::NONE); + /* | ScScenarioFlags::ShowFrame*/ + r.SetScenarioData( nNewTab, aComment, COL_LIGHTGRAY, nFlags); + + for (const auto& rEntry : aEntries) + { + sal_uInt16 nCol = rEntry.nCol; + sal_uInt16 nRow = rEntry.nRow; + OUString aVal = rEntry.GetValue(); + + r.ApplyFlagsTab( nCol, nRow, nCol, nRow, nNewTab, ScMF::Scenario ); + + r.SetString( nCol, nRow, nNewTab, aVal ); + } + + if( bLast ) + r.SetActiveScenario( nNewTab, true ); + + // modify what the Active tab is set to if the new + // scenario tab occurs before the active tab. + ScExtDocSettings& rDocSett = rRoot.GetExtDocOptions().GetDocSettings(); + if( (static_cast< SCCOL >( nTab ) < rDocSett.mnDisplTab) && (rDocSett.mnDisplTab < MAXTAB) ) + ++rDocSett.mnDisplTab; + rRoot.GetTabInfo().InsertScTab( nNewTab ); +} + +void ExcScenarioList::Apply( const XclImpRoot& rRoot ) +{ + sal_uInt16 n = static_cast<sal_uInt16>(aEntries.size()); + + std::vector< std::unique_ptr<ExcScenario> >::reverse_iterator iter; + for (iter = aEntries.rbegin(); iter != aEntries.rend(); ++iter) + { + n--; + (*iter)->Apply(rRoot, n == nLastScenario); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/expop2.cxx b/sc/source/filter/excel/expop2.cxx new file mode 100644 index 000000000..ee8ba0fff --- /dev/null +++ b/sc/source/filter/excel/expop2.cxx @@ -0,0 +1,150 @@ +/* -*- 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 <unotools/fltrcfg.hxx> + +#include <osl/diagnose.h> +#include <sfx2/objsh.hxx> +#include <sfx2/docinf.hxx> +#include <filter/msfilter/svxmsbas.hxx> + +#include <oox/ole/vbaexport.hxx> + +#include <scerrors.hxx> + +#include <root.hxx> +#include <excdoc.hxx> +#include <exp_op.hxx> + +#include <xehelper.hxx> + +#include <officecfg/Office/Calc.hxx> + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> + +namespace com::sun::star::document { class XDocumentProperties; } + +namespace { + +enum class VBAExportMode +{ + NONE, + REEXPORT_STREAM, + FULL_EXPORT +}; + +} + +ExportBiff5::ExportBiff5( XclExpRootData& rExpData, SvStream& rStrm ): + ExportTyp( rStrm ), + XclExpRoot( rExpData ) +{ + // only need part of the Root data + pExcRoot = &GetOldRoot(); + pExcRoot->pER = this; // ExcRoot -> XclExpRoot + pExcRoot->eDateiTyp = Biff5; + pExcDoc.reset( new ExcDocument( *this ) ); +} + +ExportBiff5::~ExportBiff5() +{ +} + +ErrCode ExportBiff5::Write() +{ + SfxObjectShell* pDocShell = GetDocShell(); + OSL_ENSURE( pDocShell, "ExportBiff5::Write - no document shell" ); + + tools::SvRef<SotStorage> xRootStrg = GetRootStorage(); + OSL_ENSURE( xRootStrg.is(), "ExportBiff5::Write - no root storage" ); + + VBAExportMode eVbaExportMode = VBAExportMode::NONE; + if( GetBiff() == EXC_BIFF8 ) + { + if (officecfg::Office::Calc::Filter::Import::VBA::UseExport::get()) + eVbaExportMode = VBAExportMode::FULL_EXPORT; + else + { + const SvtFilterOptions& rFilterOpt = SvtFilterOptions::Get(); + if (rFilterOpt.IsLoadExcelBasicStorage()) + eVbaExportMode = VBAExportMode::REEXPORT_STREAM; + } + } + + if ( pDocShell && xRootStrg.is() && eVbaExportMode == VBAExportMode::FULL_EXPORT) + { + VbaExport aExport(pDocShell->GetModel()); + if (aExport.containsVBAProject()) + { + tools::SvRef<SotStorage> xVBARoot = xRootStrg->OpenSotStorage("_VBA_PROJECT_CUR"); + aExport.exportVBA( xVBARoot.get() ); + } + } + else if( pDocShell && xRootStrg.is() && eVbaExportMode == VBAExportMode::REEXPORT_STREAM ) + { + SvxImportMSVBasic aBasicImport( *pDocShell, *xRootStrg ); + const ErrCode nErr = aBasicImport.SaveOrDelMSVBAStorage( true, EXC_STORAGE_VBA_PROJECT ); + if( nErr != ERRCODE_NONE ) + pDocShell->SetError(nErr); + } + + pExcDoc->ReadDoc(); // ScDoc -> ExcDoc + pExcDoc->Write( aOut ); // wechstreamen + + if( pDocShell && xRootStrg.is() ) + { + using namespace ::com::sun::star; + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps + = xDPS->getDocumentProperties(); + if ( SvtFilterOptions::Get().IsEnableCalcPreview() ) + { + std::shared_ptr<GDIMetaFile> xMetaFile = + pDocShell->GetPreviewMetaFile(); + uno::Sequence<sal_Int8> metaFile( + sfx2::convertMetaFile(xMetaFile.get())); + sfx2::SaveOlePropertySet( xDocProps, xRootStrg.get(), &metaFile ); + } + else + sfx2::SaveOlePropertySet( xDocProps, xRootStrg.get() ); + } + + const XclExpAddressConverter& rAddrConv = GetAddressConverter(); + if( rAddrConv.IsRowTruncated() ) + return SCWARN_EXPORT_MAXROW; + if( rAddrConv.IsColTruncated() ) + return SCWARN_EXPORT_MAXCOL; + if( rAddrConv.IsTabTruncated() ) + return SCWARN_EXPORT_MAXTAB; + + return ERRCODE_NONE; +} + +ExportBiff8::ExportBiff8( XclExpRootData& rExpData, SvStream& rStrm ) : + ExportBiff5( rExpData, rStrm ) +{ + pExcRoot->eDateiTyp = Biff8; +} + +ExportBiff8::~ExportBiff8() +{ +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/export/SparklineExt.cxx b/sc/source/filter/excel/export/SparklineExt.cxx new file mode 100644 index 000000000..487698e19 --- /dev/null +++ b/sc/source/filter/excel/export/SparklineExt.cxx @@ -0,0 +1,243 @@ +/* -*- 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/. + */ + +#include <export/SparklineExt.hxx> + +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> +#include <SparklineGroup.hxx> +#include <SparklineList.hxx> + +using namespace oox; + +namespace xcl::exp +{ +SparklineExt::SparklineExt(const XclExpRoot& rRoot) + : XclExpExt(rRoot) +{ + maURI = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"; +} + +void SparklineExt::SaveXml(XclExpXmlStream& rStream) +{ + auto& rDocument = GetDoc(); + + auto* pSparklineList = rDocument.GetSparklineList(GetCurrScTab()); + if (!pSparklineList) + return; + + auto const& rSparklineGroups = pSparklineList->getSparklineGroups(); + + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + rWorksheet->startElement(XML_ext, FSNS(XML_xmlns, XML_x14), + rStream.getNamespaceURL(OOX_NS(xls14Lst)), XML_uri, maURI); + + rWorksheet->startElementNS(XML_x14, XML_sparklineGroups, FSNS(XML_xmlns, XML_xm), + rStream.getNamespaceURL(OOX_NS(xm))); + + for (auto const& pSparklineGroup : rSparklineGroups) + { + auto const& rSparklineVector = pSparklineList->getSparklinesFor(pSparklineGroup); + addSparklineGroup(rStream, *pSparklineGroup, rSparklineVector); + } + + rWorksheet->endElementNS(XML_x14, XML_sparklineGroups); + rWorksheet->endElement(XML_ext); +} + +void SparklineExt::addSparklineGroupAttributes( + rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, + sc::SparklineAttributes& rAttributes) +{ + if (rAttributes.getLineWeight() != 0.75) + pAttrList->add(XML_lineWeight, OString::number(rAttributes.getLineWeight())); + + if (rAttributes.getType() != sc::SparklineType::Line) + { + if (rAttributes.getType() == sc::SparklineType::Column) + pAttrList->add(XML_type, "column"); + else if (rAttributes.getType() == sc::SparklineType::Stacked) + pAttrList->add(XML_type, "stacked"); + } + + if (rAttributes.isDateAxis()) + pAttrList->add(XML_dateAxis, "1"); + + if (rAttributes.getDisplayEmptyCellsAs() != sc::DisplayEmptyCellsAs::Zero) + { + if (rAttributes.getDisplayEmptyCellsAs() == sc::DisplayEmptyCellsAs::Gap) + pAttrList->add(XML_displayEmptyCellsAs, "gap"); + else if (rAttributes.getDisplayEmptyCellsAs() == sc::DisplayEmptyCellsAs::Span) + pAttrList->add(XML_displayEmptyCellsAs, "span"); + } + + if (rAttributes.isMarkers()) + pAttrList->add(XML_markers, "1"); + if (rAttributes.isHigh()) + pAttrList->add(XML_high, "1"); + if (rAttributes.isLow()) + pAttrList->add(XML_low, "1"); + if (rAttributes.isFirst()) + pAttrList->add(XML_first, "1"); + if (rAttributes.isLast()) + pAttrList->add(XML_last, "1"); + if (rAttributes.isNegative()) + pAttrList->add(XML_negative, "1"); + if (rAttributes.shouldDisplayXAxis()) + pAttrList->add(XML_displayXAxis, "1"); + if (rAttributes.shouldDisplayHidden()) + pAttrList->add(XML_displayHidden, "1"); + + if (rAttributes.getMinAxisType() != sc::AxisType::Individual) + { + if (rAttributes.getMinAxisType() == sc::AxisType::Group) + pAttrList->add(XML_minAxisType, "group"); + else if (rAttributes.getMinAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_minAxisType, "custom"); + } + + if (rAttributes.getMaxAxisType() != sc::AxisType::Individual) + { + if (rAttributes.getMaxAxisType() == sc::AxisType::Group) + pAttrList->add(XML_maxAxisType, "group"); + else if (rAttributes.getMaxAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_maxAxisType, "custom"); + } + + if (rAttributes.isRightToLeft()) + pAttrList->add(XML_rightToLeft, "1"); + + if (rAttributes.getManualMax() && rAttributes.getMaxAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_manualMax, OString::number(*rAttributes.getManualMax())); + + if (rAttributes.getManualMin() && rAttributes.getMinAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_manualMin, OString::number(*rAttributes.getManualMin())); +} + +void SparklineExt::addSparklineGroupColors(XclExpXmlStream& rStream, + sc::SparklineAttributes& rAttributes) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + + rWorksheet->singleElementNS(XML_x14, XML_colorSeries, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorSeries())); + + if (rAttributes.getColorNegative() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorNegative, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorNegative())); + } + + if (rAttributes.getColorAxis() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorAxis, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorAxis())); + } + + if (rAttributes.getColorMarkers() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorMarkers, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorMarkers())); + } + + if (rAttributes.getColorFirst() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorFirst, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorFirst())); + } + + if (rAttributes.getColorLast() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorLast, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorLast())); + } + + if (rAttributes.getColorHigh() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorHigh, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorHigh())); + } + + if (rAttributes.getColorLow() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorLow, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorLow())); + } +} + +void SparklineExt::addSparklineGroup(XclExpXmlStream& rStream, sc::SparklineGroup& rSparklineGroup, + std::vector<std::shared_ptr<sc::Sparkline>> const& rSparklines) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + + // Sparkline Group Attributes + auto pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + + // Write ID + OString sUID = rSparklineGroup.getID().getString(); + pAttrList->addNS(XML_xr2, XML_uid, sUID); + + // Write attributes + addSparklineGroupAttributes(pAttrList, rSparklineGroup.getAttributes()); + + rWorksheet->startElementNS(XML_x14, XML_sparklineGroup, pAttrList); + + addSparklineGroupColors(rStream, rSparklineGroup.getAttributes()); + + // Sparklines + + rWorksheet->startElementNS(XML_x14, XML_sparklines); + for (auto const& rSparkline : rSparklines) + { + rWorksheet->startElementNS(XML_x14, XML_sparkline); + + { + rWorksheet->startElementNS(XML_xm, XML_f); + + OUString sRangeFormula; + ScRefFlags eFlags = ScRefFlags::VALID | ScRefFlags::TAB_3D; + rSparkline->getInputRange().Format(sRangeFormula, eFlags, GetDoc(), + formula::FormulaGrammar::CONV_XL_OOX, ' ', true); + + rWorksheet->writeEscaped(sRangeFormula); + rWorksheet->endElementNS(XML_xm, XML_f); + } + + { + rWorksheet->startElementNS(XML_xm, XML_sqref); + + ScAddress::Details detailsXL(formula::FormulaGrammar::CONV_XL_OOX); + ScAddress aAddress(rSparkline->getColumn(), rSparkline->getRow(), GetCurrScTab()); + OUString sLocation = aAddress.Format(ScRefFlags::VALID, &GetDoc(), detailsXL); + + rWorksheet->writeEscaped(sLocation); + rWorksheet->endElementNS(XML_xm, XML_sqref); + } + + rWorksheet->endElementNS(XML_x14, XML_sparkline); + } + rWorksheet->endElementNS(XML_x14, XML_sparklines); + rWorksheet->endElementNS(XML_x14, XML_sparklineGroup); +} + +SparklineBuffer::SparklineBuffer(const XclExpRoot& rRoot, XclExtLstRef const& xExtLst) + : XclExpRoot(rRoot) +{ + auto& rDocument = GetDoc(); + auto* pSparklineList = rDocument.GetSparklineList(GetCurrScTab()); + if (pSparklineList && !pSparklineList->getSparklineGroups().empty()) + { + xExtLst->AddRecord(new xcl::exp::SparklineExt(GetRoot())); + } +} + +} // end namespace xcl::exp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/fontbuff.cxx b/sc/source/filter/excel/fontbuff.cxx new file mode 100644 index 000000000..40e04fcb0 --- /dev/null +++ b/sc/source/filter/excel/fontbuff.cxx @@ -0,0 +1,130 @@ +/* -*- 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 <lotfntbf.hxx> + +#include <scitems.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <osl/diagnose.h> +#include <svl/itemset.hxx> + +void LotusFontBuffer::Fill( const sal_uInt8 nIndex, SfxItemSet& rItemSet ) +{ + sal_uInt8 nIntIndex = nIndex & 0x07; + + ENTRY* pCurrent = pData + nIntIndex; + + if( pCurrent->pFont ) + rItemSet.Put( *pCurrent->pFont ); + + if( pCurrent->pHeight ) + rItemSet.Put( *pCurrent->pHeight ); + + if( nIndex & 0x08 ) + { + SvxWeightItem aWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ); + rItemSet.Put( aWeightItem ); + } + + if( nIndex & 0x10 ) + { + SvxPostureItem aAttr( ITALIC_NORMAL, ATTR_FONT_POSTURE ); + rItemSet.Put( aAttr ); + } + + FontLineStyle eUnderline; + switch( nIndex & 0x60 ) // Bit 5+6 + { + case 0x60: + case 0x20: eUnderline = LINESTYLE_SINGLE; break; + case 0x40: eUnderline = LINESTYLE_DOUBLE; break; + default: eUnderline = LINESTYLE_NONE; + } + if( eUnderline != LINESTYLE_NONE ) + { + SvxUnderlineItem aUndItem( eUnderline, ATTR_FONT_UNDERLINE ); + rItemSet.Put( aUndItem ); + } +} + +void LotusFontBuffer::SetName( const sal_uInt16 nIndex, const OUString& rName ) +{ + OSL_ENSURE( nIndex < nSize, "*LotusFontBuffer::SetName(): Array too small!" ); + if( nIndex < nSize ) + { + ENTRY* pEntry = pData + nIndex; + pEntry->TmpName( rName ); + + if( pEntry->nType >= 0 ) + MakeFont( pEntry ); + } +} + +void LotusFontBuffer::SetHeight( const sal_uInt16 nIndex, const sal_uInt16 nHeight ) +{ + OSL_ENSURE( nIndex < nSize, "*LotusFontBuffer::SetHeight(): Array too small!" ); + if( nIndex < nSize ) + pData[ nIndex ].Height( std::make_unique<SvxFontHeightItem>( static_cast<sal_uInt32>(nHeight) * 20, 100, ATTR_FONT_HEIGHT ) ); +} + +void LotusFontBuffer::SetType( const sal_uInt16 nIndex, const sal_uInt16 nType ) +{ + OSL_ENSURE( nIndex < nSize, "*LotusFontBuffer::SetType(): Array too small!" ); + if( nIndex < nSize ) + { + ENTRY* pEntry = pData + nIndex; + pEntry->Type( nType ); + + if( pEntry->xTmpName ) + MakeFont( pEntry ); + } +} + +void LotusFontBuffer::MakeFont( ENTRY* pEntry ) +{ + FontFamily eFamily = FAMILY_DONTKNOW; + FontPitch ePitch = PITCH_DONTKNOW; + rtl_TextEncoding eCharSet = RTL_TEXTENCODING_DONTKNOW; + + switch( pEntry->nType ) + { + case 0x00: // Helvetica + eFamily = FAMILY_SWISS; + ePitch = PITCH_VARIABLE; + break; + case 0x01: // Times Roman + eFamily = FAMILY_ROMAN; + ePitch = PITCH_VARIABLE; + break; + case 0x02: // Courier + ePitch = PITCH_FIXED; + break; + case 0x03: // Symbol + eCharSet = RTL_TEXTENCODING_SYMBOL; + break; + } + + pEntry->pFont.reset( new SvxFontItem( eFamily, *pEntry->xTmpName, OUString(), ePitch, eCharSet, ATTR_FONT ) ); + + pEntry->xTmpName.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/frmbase.cxx b/sc/source/filter/excel/frmbase.cxx new file mode 100644 index 000000000..73ef59dad --- /dev/null +++ b/sc/source/filter/excel/frmbase.cxx @@ -0,0 +1,209 @@ +/* -*- 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 <formel.hxx> + +#include <osl/diagnose.h> + +ScRangeListTabs::ScRangeListTabs( const XclImpRoot& rRoot ) +: XclImpRoot( rRoot ) +{ +} + +ScRangeListTabs::~ScRangeListTabs() +{ +} + +void ScRangeListTabs::Append( const ScAddress& aSRD, SCTAB nTab ) +{ + ScAddress a = aSRD; + ScDocument& rDoc = GetRoot().GetDoc(); + + if (a.Tab() > MAXTAB) + a.SetTab(MAXTAB); + + if (a.Col() > rDoc.MaxCol()) + a.SetCol(rDoc.MaxCol()); + + if (a.Row() > rDoc.MaxRow()) + a.SetRow(rDoc.MaxRow()); + + if( nTab == SCTAB_MAX) + return; + if( nTab < 0) + nTab = a.Tab(); + + if (nTab < 0 || MAXTAB < nTab) + return; + + TabRangeType::iterator itr = m_TabRanges.find(nTab); + if (itr == m_TabRanges.end()) + { + // No entry for this table yet. Insert a new one. + std::pair<TabRangeType::iterator, bool> r = + m_TabRanges.insert(std::make_pair(nTab, RangeListType())); + + if (!r.second) + // Insertion failed. + return; + + itr = r.first; + } + itr->second.push_back(ScRange(a.Col(),a.Row(),a.Tab())); +} + +void ScRangeListTabs::Append( const ScRange& aCRD, SCTAB nTab ) +{ + ScRange a = aCRD; + ScDocument& rDoc = GetRoot().GetDoc(); + + // ignore 3D ranges + if (a.aStart.Tab() != a.aEnd.Tab()) + return; + + if (a.aStart.Tab() > MAXTAB) + a.aStart.SetTab(MAXTAB); + else if (a.aStart.Tab() < 0) + a.aStart.SetTab(0); + + if (a.aStart.Col() > rDoc.MaxCol()) + a.aStart.SetCol(rDoc.MaxCol()); + else if (a.aStart.Col() < 0) + a.aStart.SetCol(0); + + if (a.aStart.Row() > rDoc.MaxRow()) + a.aStart.SetRow(rDoc.MaxRow()); + else if (a.aStart.Row() < 0) + a.aStart.SetRow(0); + + if (a.aEnd.Col() > rDoc.MaxCol()) + a.aEnd.SetCol(rDoc.MaxCol()); + else if (a.aEnd.Col() < 0) + a.aEnd.SetCol(0); + + if (a.aEnd.Row() > rDoc.MaxRow()) + a.aEnd.SetRow(rDoc.MaxRow()); + else if (a.aEnd.Row() < 0) + a.aEnd.SetRow(0); + + if( nTab == SCTAB_MAX) + return; + + if( nTab < -1) + nTab = a.aStart.Tab(); + + if (nTab < 0 || MAXTAB < nTab) + return; + + TabRangeType::iterator itr = m_TabRanges.find(nTab); + if (itr == m_TabRanges.end()) + { + // No entry for this table yet. Insert a new one. + std::pair<TabRangeType::iterator, bool> r = + m_TabRanges.insert(std::make_pair(nTab, RangeListType())); + + if (!r.second) + // Insertion failed. + return; + + itr = r.first; + } + itr->second.push_back(a); +} + +const ScRange* ScRangeListTabs::First( SCTAB n ) +{ + OSL_ENSURE( ValidTab(n), "-ScRangeListTabs::First(): Good bye!" ); + + TabRangeType::iterator itr = m_TabRanges.find(n); + if (itr == m_TabRanges.end()) + // No range list exists for this table. + return nullptr; + + const RangeListType& rList = itr->second; + maItrCur = rList.begin(); + maItrCurEnd = rList.end(); + return rList.empty() ? nullptr : &(*maItrCur); +} + +const ScRange* ScRangeListTabs::Next () +{ + ++maItrCur; + if (maItrCur == maItrCurEnd) + return nullptr; + + return &(*maItrCur); +} + +ConverterBase::ConverterBase( svl::SharedStringPool& rSPool ) : + aPool(rSPool), + aEingPos( 0, 0, 0 ) +{ +} + +ConverterBase::~ConverterBase() +{ +} + +void ConverterBase::Reset() +{ + aPool.Reset(); + aStack.Reset(); +} + +ExcelConverterBase::ExcelConverterBase( svl::SharedStringPool& rSPool ) : + ConverterBase(rSPool) +{ +} + +ExcelConverterBase::~ExcelConverterBase() +{ +} + +void ExcelConverterBase::Reset( const ScAddress& rEingPos ) +{ + ConverterBase::Reset(); + aEingPos = rEingPos; +} + +void ExcelConverterBase::Reset() +{ + ConverterBase::Reset(); + aEingPos.Set( 0, 0, 0 ); +} + +LotusConverterBase::LotusConverterBase( SvStream &rStr, svl::SharedStringPool& rSPool ) : + ConverterBase(rSPool), + aIn( rStr ), + nBytesLeft( 0 ) +{ +} + +LotusConverterBase::~LotusConverterBase() +{ +} + +void LotusConverterBase::Reset( const ScAddress& rEingPos ) +{ + ConverterBase::Reset(); + nBytesLeft = 0; + aEingPos = rEingPos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/impop.cxx b/sc/source/filter/excel/impop.cxx new file mode 100644 index 000000000..9ddc6e6e7 --- /dev/null +++ b/sc/source/filter/excel/impop.cxx @@ -0,0 +1,1414 @@ +/* -*- 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 <memory> +#include <imp_op.hxx> + +#include <filter/msfilter/countryid.hxx> + +#include <scitems.hxx> + +#include <o3tl/safeint.hxx> +#include <sfx2/docfile.hxx> +#include <svx/svxids.hrc> +#include <svl/numformat.hxx> +#include <unotools/configmgr.hxx> +#include <sal/log.hxx> + +#include <sfx2/objsh.hxx> +#include <tools/urlobj.hxx> +#include <docuno.hxx> + +#include <formulacell.hxx> +#include <document.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <global.hxx> +#include <olinetab.hxx> +#include <stlpool.hxx> +#include <viewopti.hxx> +#include <docoptio.hxx> +#include <scextopt.hxx> +#include <unonames.hxx> +#include <paramisc.hxx> +#include <colrowst.hxx> +#include <otlnbuff.hxx> +#include <xistyle.hxx> + +#include <namebuff.hxx> +#include <xltools.hxx> +#include <xltable.hxx> +#include <xltracer.hxx> +#include <xihelper.hxx> +#include <xipage.hxx> +#include <xiview.hxx> +#include <xiescher.hxx> +#include <xicontent.hxx> + +#include <excform.hxx> +#include <documentimport.hxx> + +#if defined(_WIN32) +#include <math.h> +#endif + +using namespace ::com::sun::star; + +ImportTyp::ImportTyp(ScDocument& rDoc, rtl_TextEncoding eQ) + : eQuellChar(eQ) + , rD(rDoc) + +{ +} + +ImportTyp::~ImportTyp() +{ +} + +ImportExcel::ImportExcel( XclImpRootData& rImpData, SvStream& rStrm ): + ImportTyp( rImpData.mrDoc, rImpData.meTextEnc ), + XclImpRoot( rImpData ), + maStrm( rStrm, GetRoot() ), + aIn( maStrm ), + maScOleSize( ScAddress::INITIALIZE_INVALID ), + pColOutlineBuff(nullptr), + pRowOutlineBuff(nullptr), + pColRowBuff(nullptr), + mpLastFormula(nullptr), + mnLastRefIdx( 0 ), + mnIxfeIndex( 0 ), + mnLastRecId(0), + mbBiff2HasXfs(false), + mbBiff2HasXfsValid(false) +{ + nBdshtTab = 0; + + // fill in root data - after new's without root as parameter + pExcRoot = &GetOldRoot(); + pExcRoot->pIR = this; // ExcRoot -> XclImpRoot + pExcRoot->eDateiTyp = BiffX; + pExcRoot->pExtSheetBuff.reset( new ExtSheetBuffer( pExcRoot ) ); //&aExtSheetBuff; + pExcRoot->pShrfmlaBuff.reset( new SharedFormulaBuffer( pExcRoot ) ); //&aShrfrmlaBuff; + pExcRoot->pExtNameBuff.reset( new ExtNameBuff ( *this ) ); + + pOutlineListBuffer.reset(new XclImpOutlineListBuffer); + + // from Biff8 on + pFormConv.reset(new ExcelToSc( GetRoot() )); + pExcRoot->pFmlaConverter = pFormConv.get(); + + bTabTruncated = false; + + // Excel document per Default on 31.12.1899, accords to Excel settings with 1.1.1900 + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetDate( 30, 12, 1899 ); + rD.SetDocOptions( aOpt ); + rD.GetFormatTable()->ChangeNullDate( 30, 12, 1899 ); + + ScDocOptions aDocOpt( rD.GetDocOptions() ); + aDocOpt.SetIgnoreCase( true ); // always in Excel + aDocOpt.SetFormulaRegexEnabled( false ); // regular expressions? what's that? + aDocOpt.SetFormulaWildcardsEnabled( true ); // Excel uses wildcard expressions + aDocOpt.SetLookUpColRowNames( false ); // default: no natural language refs + rD.SetDocOptions( aDocOpt ); +} + +ImportExcel::~ImportExcel() +{ + GetDoc().SetSrcCharSet( GetTextEncoding() ); + + pOutlineListBuffer.reset(); + + pFormConv.reset(); +} + +void ImportExcel::SetLastFormula( SCCOL nCol, SCROW nRow, double fVal, sal_uInt16 nXF, ScFormulaCell* pCell ) +{ + LastFormulaMapType::iterator it = maLastFormulaCells.find(nCol); + if (it == maLastFormulaCells.end()) + { + std::pair<LastFormulaMapType::iterator, bool> r = + maLastFormulaCells.emplace(nCol, LastFormula()); + it = r.first; + } + + it->second.mnCol = nCol; + it->second.mnRow = nRow; + it->second.mpCell = pCell; + it->second.mfValue = fVal; + it->second.mnXF = nXF; + + mpLastFormula = &it->second; +} + +void ImportExcel::ReadFileSharing() +{ + sal_uInt16 nRecommendReadOnly, nPasswordHash; + nRecommendReadOnly = maStrm.ReaduInt16(); + nPasswordHash = maStrm.ReaduInt16(); + + if((nRecommendReadOnly == 0) && (nPasswordHash == 0)) + return; + + if( SfxItemSet* pItemSet = GetMedium().GetItemSet() ) + pItemSet->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + + if( SfxObjectShell* pShell = GetDocShell() ) + { + if( nRecommendReadOnly != 0 ) + pShell->SetLoadReadonly( true ); + if( nPasswordHash != 0 ) + pShell->SetModifyPasswordHash( nPasswordHash ); + } +} + +sal_uInt16 ImportExcel::ReadXFIndex( const ScAddress& rScPos, bool bBiff2 ) +{ + sal_uInt16 nXFIdx = 0; + if( bBiff2 ) + { + /* #i71453# On first call, check if the file contains XF records (by + trying to access the first XF with index 0). If there are no XFs, + the explicit formatting information contained in each cell record + will be used instead. */ + if( !mbBiff2HasXfsValid ) + { + mbBiff2HasXfsValid = true; + mbBiff2HasXfs = GetXFBuffer().GetXF( 0 ) != nullptr; + } + // read formatting information (includes the XF identifier) + sal_uInt8 nFlags1, nFlags2, nFlags3; + nFlags1 = maStrm.ReaduInt8(); + nFlags2 = maStrm.ReaduInt8(); + nFlags3 = maStrm.ReaduInt8(); + /* If the file contains XFs, extract and set the XF identifier, + otherwise get the explicit formatting. */ + if( mbBiff2HasXfs ) + { + nXFIdx = ::extract_value< sal_uInt16 >( nFlags1, 0, 6 ); + /* If the identifier is equal to 63, then the real identifier is + contained in the preceding IXFE record (stored in mnBiff2XfId). */ + if( nXFIdx == 63 ) + nXFIdx = mnIxfeIndex; + } + else + { + /* Let the XclImpXF class do the conversion of the imported + formatting. The XF buffer is empty, therefore will not do any + conversion based on the XF index later on. */ + XclImpXF::ApplyPatternForBiff2CellFormat( GetRoot(), rScPos, nFlags1, nFlags2, nFlags3 ); + } + } + else + nXFIdx = aIn.ReaduInt16(); + return nXFIdx; +} + +void ImportExcel::ReadDimensions() +{ + XclRange aXclUsedArea; + if( (maStrm.GetRecId() == EXC_ID2_DIMENSIONS) || (GetBiff() <= EXC_BIFF5) ) + { + maStrm >> aXclUsedArea; + if( (aXclUsedArea.GetColCount() > 1) && (aXclUsedArea.GetRowCount() > 1) ) + { + // Excel stores first unused row/column index + --aXclUsedArea.maLast.mnCol; + --aXclUsedArea.maLast.mnRow; + // create the Calc range + SCTAB nScTab = GetCurrScTab(); + ScRange& rScUsedArea = GetExtDocOptions().GetOrCreateTabSettings( nScTab ).maUsedArea; + GetAddressConverter().ConvertRange( rScUsedArea, aXclUsedArea, nScTab, nScTab, false ); + // if any error occurs in ConvertRange(), rScUsedArea keeps untouched + } + } + else + { + sal_uInt32 nXclRow1 = 0, nXclRow2 = 0; + nXclRow1 = maStrm.ReaduInt32(); + nXclRow2 = maStrm.ReaduInt32(); + aXclUsedArea.maFirst.mnCol = maStrm.ReaduInt16(); + aXclUsedArea.maLast.mnCol = maStrm.ReaduInt16(); + if( (nXclRow1 < nXclRow2) && (aXclUsedArea.GetColCount() > 1) && + (nXclRow1 <= o3tl::make_unsigned( GetScMaxPos().Row() )) ) + { + // Excel stores first unused row/column index + --nXclRow2; + --aXclUsedArea.maLast.mnCol; + // convert row indexes to 16-bit values + aXclUsedArea.maFirst.mnRow = static_cast< sal_uInt16 >( nXclRow1 ); + aXclUsedArea.maLast.mnRow = limit_cast< sal_uInt16 >( nXclRow2, aXclUsedArea.maFirst.mnRow, SAL_MAX_UINT16 ); + // create the Calc range + SCTAB nScTab = GetCurrScTab(); + ScRange& rScUsedArea = GetExtDocOptions().GetOrCreateTabSettings( nScTab ).maUsedArea; + GetAddressConverter().ConvertRange( rScUsedArea, aXclUsedArea, nScTab, nScTab, false ); + // if any error occurs in ConvertRange(), rScUsedArea keeps untouched + } + } +} + +void ImportExcel::ReadBlank() +{ + XclAddress aXclPos; + aIn >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, maStrm.GetRecId() == EXC_ID2_BLANK ); + + GetXFRangeBuffer().SetBlankXF( aScPos, nXFIdx ); + } +} + +void ImportExcel::ReadInteger() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, true ); + sal_uInt16 nValue; + nValue = maStrm.ReaduInt16(); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + GetDocImport().setNumericCell(aScPos, nValue); + } +} + +void ImportExcel::ReadNumber() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, maStrm.GetRecId() == EXC_ID2_NUMBER ); + double fValue; + fValue = maStrm.ReadDouble(); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + GetDocImport().setNumericCell(aScPos, fValue); + } +} + +void ImportExcel::ReadLabel() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + return; + + /* Record ID BIFF XF type String type + 0x0004 2-7 3 byte 8-bit length, byte string + 0x0004 8 3 byte 16-bit length, unicode string + 0x0204 2-7 2 byte 16-bit length, byte string + 0x0204 8 2 byte 16-bit length, unicode string */ + bool bBiff2 = maStrm.GetRecId() == EXC_ID2_LABEL; + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, bBiff2 ); + XclStrFlags nFlags = (bBiff2 && (GetBiff() <= EXC_BIFF5)) ? XclStrFlags::EightBitLength : XclStrFlags::NONE; + XclImpString aString; + + // #i63105# use text encoding from FONT record + rtl_TextEncoding eOldTextEnc = GetTextEncoding(); + if( const XclImpFont* pFont = GetXFBuffer().GetFont( nXFIdx ) ) + SetTextEncoding( pFont->GetFontEncoding() ); + aString.Read( maStrm, nFlags ); + SetTextEncoding( eOldTextEnc ); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + XclImpStringHelper::SetToDocument(GetDocImport(), aScPos, GetRoot(), aString, nXFIdx); +} + +void ImportExcel::ReadBoolErr() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + return; + + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, maStrm.GetRecId() == EXC_ID2_BOOLERR ); + sal_uInt8 nValue, nType; + nValue = maStrm.ReaduInt8(); + nType = maStrm.ReaduInt8(); + + if( nType == EXC_BOOLERR_BOOL ) + GetXFRangeBuffer().SetBoolXF( aScPos, nXFIdx ); + else + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + + double fValue; + std::unique_ptr<ScTokenArray> pScTokArr = ErrorToFormula( nType != EXC_BOOLERR_BOOL, nValue, fValue ); + ScFormulaCell* pCell = pScTokArr + ? new ScFormulaCell(rD, aScPos, std::move(pScTokArr)) + : new ScFormulaCell(rD, aScPos); + pCell->SetHybridDouble( fValue ); + GetDocImport().setFormulaCell(aScPos, pCell); +} + +void ImportExcel::ReadRk() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, false ); + sal_Int32 nRk; + nRk = maStrm.ReadInt32(); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + GetDocImport().setNumericCell(aScPos, XclTools::GetDoubleFromRK(nRk)); + } +} + +void ImportExcel::Window1() +{ + GetDocViewSettings().ReadWindow1( maStrm ); +} + +void ImportExcel::Row25() +{ + sal_uInt16 nRow, nRowHeight; + + nRow = aIn.ReaduInt16(); + aIn.Ignore( 4 ); + + if( !GetRoot().GetDoc().ValidRow( nRow ) ) + return; + + nRowHeight = aIn.ReaduInt16(); // specify direct in Twips + aIn.Ignore( 2 ); + + if( GetBiff() == EXC_BIFF2 ) + {// -------------------- BIFF2 + pColRowBuff->SetHeight( nRow, nRowHeight ); + } + else + {// -------------------- BIFF5 + sal_uInt16 nGrbit; + + aIn.Ignore( 2 ); // reserved + nGrbit = aIn.ReaduInt16(); + + sal_uInt8 nLevel = ::extract_value< sal_uInt8 >( nGrbit, 0, 3 ); + pRowOutlineBuff->SetLevel( nRow, nLevel, ::get_flag( nGrbit, EXC_ROW_COLLAPSED ) ); + pColRowBuff->SetRowSettings( nRow, nRowHeight, nGrbit ); + } +} + +void ImportExcel::Bof2() +{ + sal_uInt16 nSubType; + maStrm.DisableDecryption(); + maStrm.Ignore( 2 ); + nSubType = maStrm.ReaduInt16(); + + if( nSubType == 0x0020 ) // Chart + pExcRoot->eDateiTyp = Biff2C; + else if( nSubType == 0x0040 ) // Macro + pExcRoot->eDateiTyp = Biff2M; + else // #i51490# Excel interprets invalid indexes as worksheet + pExcRoot->eDateiTyp = Biff2; +} + +void ImportExcel::Eof() +{ + // POST: cannot be called after an invalid table! + EndSheet(); + IncCurrScTab(); +} + +void ImportExcel::SheetPassword() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetSheetProtectBuffer().ReadPasswordHash( aIn, GetCurrScTab() ); +} + +void ImportExcel::Externsheet() +{ + OUString aUrl, aTabName; + bool bSameWorkBook; + OUString aEncodedUrl( aIn.ReadByteString( false ) ); + XclImpUrlHelper::DecodeUrl( aUrl, aTabName, bSameWorkBook, *pExcRoot->pIR, aEncodedUrl ); + mnLastRefIdx = pExcRoot->pExtSheetBuff->Add( aUrl, aTabName, bSameWorkBook ); +} + +void ImportExcel:: WinProtection() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetDocProtectBuffer().ReadWinProtect( aIn ); +} + +void ImportExcel::Columndefault() +{// Default Cell Attributes + sal_uInt16 nColMic, nColMac; + sal_uInt8 nOpt0; + + nColMic = aIn.ReaduInt16(); + nColMac = aIn.ReaduInt16(); + + OSL_ENSURE( aIn.GetRecLeft() == static_cast<std::size_t>(nColMac - nColMic) * 3 + 2, + "ImportExcel::Columndefault - wrong record size" ); + + nColMac--; + + if( nColMac > rD.MaxCol() ) + nColMac = static_cast<sal_uInt16>(rD.MaxCol()); + + for( sal_uInt16 nCol = nColMic ; nCol <= nColMac ; nCol++ ) + { + nOpt0 = aIn.ReaduInt8(); + aIn.Ignore( 2 ); // only 0. Attribute-Byte used + + if( nOpt0 & 0x80 ) // Col hidden? + pColRowBuff->HideCol( nCol ); + } +} + +void ImportExcel::Array25() +{ + sal_uInt16 nFormLen; + sal_uInt16 nFirstRow = aIn.ReaduInt16(); + sal_uInt16 nLastRow = aIn.ReaduInt16(); + sal_uInt8 nFirstCol = aIn.ReaduInt8(); + sal_uInt8 nLastCol = aIn.ReaduInt8(); + + if( GetBiff() == EXC_BIFF2 ) + {// BIFF2 + aIn.Ignore( 1 ); + nFormLen = aIn.ReaduInt8(); + } + else + {// BIFF5 + aIn.Ignore( 6 ); + nFormLen = aIn.ReaduInt16(); + } + + std::unique_ptr<ScTokenArray> pResult; + + if (GetRoot().GetDoc().ValidColRow(nLastCol, nLastRow)) + { + // the read mark is now on the formula, length in nFormLen + + pFormConv->Reset( ScAddress( static_cast<SCCOL>(nFirstCol), + static_cast<SCROW>(nFirstRow), GetCurrScTab() ) ); + pFormConv->Convert(pResult, maStrm, nFormLen, true); + + SAL_WARN_IF(!pResult, "sc", "*ImportExcel::Array25(): ScTokenArray is NULL!"); + } + + if (pResult) + { + ScDocumentImport& rDoc = GetDocImport(); + ScRange aArrayRange(nFirstCol, nFirstRow, GetCurrScTab(), nLastCol, nLastRow, GetCurrScTab()); + rDoc.setMatrixCells(aArrayRange, *pResult, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1); + } +} + +void ImportExcel::Rec1904() +{ + sal_uInt16 n1904; + + n1904 = aIn.ReaduInt16(); + + if( n1904 ) + {// 1904 date system + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetDate( 1, 1, 1904 ); + rD.SetDocOptions( aOpt ); + rD.GetFormatTable()->ChangeNullDate( 1, 1, 1904 ); + } +} + +void ImportExcel::Externname25() +{ + sal_uInt32 nRes; + sal_uInt16 nOpt; + + nOpt = aIn.ReaduInt16(); + nRes = aIn.ReaduInt32(); + + aIn.ReadByteString( false ); // name + + if( ( nOpt & 0x0001 ) || ( ( nOpt & 0xFFFE ) == 0x0000 ) ) + {// external name + pExcRoot->pExtNameBuff->AddName( mnLastRefIdx ); + } + else if( nOpt & 0x0010 ) + {// ole link + pExcRoot->pExtNameBuff->AddOLE( mnLastRefIdx, nRes ); // nRes is storage ID + } + else + {// dde link + pExcRoot->pExtNameBuff->AddDDE( mnLastRefIdx ); + } +} + +void ImportExcel::Colwidth() +{// Column Width + sal_uInt8 nColFirst, nColLast; + sal_uInt16 nColWidth; + + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + nColWidth = aIn.ReaduInt16(); + +//TODO: add a check for the unlikely case of changed MAXCOL (-> XclImpAddressConverter) +// if( nColLast > rD.MaxCol() ) +// nColLast = static_cast<sal_uInt16>(rD.MaxCol()); + + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( nColWidth, GetCharWidth() ); + pColRowBuff->SetWidthRange( nColFirst, nColLast, nScWidth ); +} + +void ImportExcel::Defrowheight2() +{ + sal_uInt16 nDefHeight; + nDefHeight = maStrm.ReaduInt16(); + nDefHeight &= 0x7FFF; + pColRowBuff->SetDefHeight( nDefHeight, EXC_DEFROW_UNSYNCED ); +} + +void ImportExcel::SheetProtect() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetSheetProtectBuffer().ReadProtect( aIn, GetCurrScTab() ); +} + +void ImportExcel::DocProtect() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetDocProtectBuffer().ReadDocProtect( aIn ); +} + +void ImportExcel::DocPassword() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetDocProtectBuffer().ReadPasswordHash( aIn ); +} + +void ImportExcel::Codepage() +{ + SetCodePage( maStrm.ReaduInt16() ); +} + +void ImportExcel::Ixfe() +{ + mnIxfeIndex = maStrm.ReaduInt16(); +} + +void ImportExcel::DefColWidth() +{ + // stored as entire characters -> convert to 1/256 of characters (as in COLINFO) + double fDefWidth = 256.0 * maStrm.ReaduInt16(); + + if (!pColRowBuff) + { + SAL_WARN("sc", "*ImportExcel::DefColWidth(): pColRowBuff is NULL!"); + return; + } + + // #i3006# additional space for default width - Excel adds space depending on font size + tools::Long nFontHt = GetFontBuffer().GetAppFontData().mnHeight; + fDefWidth += XclTools::GetXclDefColWidthCorrection( nFontHt ); + + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( limit_cast< sal_uInt16 >( fDefWidth ), GetCharWidth() ); + pColRowBuff->SetDefWidth( nScWidth ); +} + +void ImportExcel::Colinfo() +{// Column Formatting Information + sal_uInt16 nColFirst, nColLast, nColWidth, nXF; + sal_uInt16 nOpt; + + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + nColWidth = aIn.ReaduInt16(); + nXF = aIn.ReaduInt16(); + nOpt = aIn.ReaduInt16(); + + if( nColFirst > rD.MaxCol() ) + return; + + if( nColLast > rD.MaxCol() ) + nColLast = static_cast<sal_uInt16>(rD.MaxCol()); + + bool bHidden = ::get_flag( nOpt, EXC_COLINFO_HIDDEN ); + bool bCollapsed = ::get_flag( nOpt, EXC_COLINFO_COLLAPSED ); + sal_uInt8 nLevel = ::extract_value< sal_uInt8 >( nOpt, 8, 3 ); + pColOutlineBuff->SetLevelRange( nColFirst, nColLast, nLevel, bCollapsed ); + + if( bHidden ) + pColRowBuff->HideColRange( nColFirst, nColLast ); + + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( nColWidth, GetCharWidth() ); + pColRowBuff->SetWidthRange( nColFirst, nColLast, nScWidth ); + pColRowBuff->SetDefaultXF( nColFirst, nColLast, nXF ); +} + +void ImportExcel::Wsbool() +{ + sal_uInt16 nFlags; + nFlags = aIn.ReaduInt16(); + + pRowOutlineBuff->SetButtonMode( ::get_flag( nFlags, EXC_WSBOOL_ROWBELOW ) ); + pColOutlineBuff->SetButtonMode( ::get_flag( nFlags, EXC_WSBOOL_COLBELOW ) ); + + GetPageSettings().SetFitToPages( ::get_flag( nFlags, EXC_WSBOOL_FITTOPAGE ) ); +} + +void ImportExcel::Boundsheet() +{ + sal_uInt16 nGrbit = 0; + + if( GetBiff() == EXC_BIFF5 ) + { + aIn.DisableDecryption(); + maSheetOffsets.push_back( aIn.ReaduInt32() ); + aIn.EnableDecryption(); + nGrbit = aIn.ReaduInt16(); + } + + OUString aName( aIn.ReadByteString( false ) ); + + SCTAB nScTab = nBdshtTab; + if( nScTab > 0 ) + { + OSL_ENSURE( !rD.HasTable( nScTab ), "ImportExcel::Boundsheet - sheet exists already" ); + rD.MakeTable( nScTab ); + } + + if( ( nGrbit & 0x0001 ) || ( nGrbit & 0x0002 ) ) + rD.SetVisible( nScTab, false ); + + if( !rD.RenameTab( nScTab, aName ) ) + { + rD.CreateValidTabName( aName ); + rD.RenameTab( nScTab, aName ); + } + + nBdshtTab++; +} + +void ImportExcel::Country() +{ + sal_uInt16 nUICountry, nDocCountry; + nUICountry = maStrm.ReaduInt16(); + nDocCountry = maStrm.ReaduInt16(); + + // Store system language in XclRoot + LanguageType eLanguage = ::msfilter::ConvertCountryToLanguage( static_cast< ::msfilter::CountryId >( nDocCountry ) ); + if( eLanguage != LANGUAGE_DONTKNOW ) + SetDocLanguage( eLanguage ); + + // Set Excel UI language in add-in name translator + eLanguage = ::msfilter::ConvertCountryToLanguage( static_cast< ::msfilter::CountryId >( nUICountry ) ); + if( eLanguage != LANGUAGE_DONTKNOW ) + SetUILanguage( eLanguage ); +} + +void ImportExcel::ReadUsesElfs() +{ + if( maStrm.ReaduInt16() != 0 ) + { + ScDocOptions aDocOpt = GetDoc().GetDocOptions(); + aDocOpt.SetLookUpColRowNames( true ); + GetDoc().SetDocOptions( aDocOpt ); + } +} + +void ImportExcel::Hideobj() +{ + sal_uInt16 nHide; + ScVObjMode eOle, eChart, eDraw; + + nHide = aIn.ReaduInt16(); + + ScViewOptions aOpts( rD.GetViewOptions() ); + + switch( nHide ) + { + case 1: // Placeholders + eOle = VOBJ_MODE_SHOW; // in Excel 97 only charts as place holder are displayed + eChart = VOBJ_MODE_SHOW; //#i80528# VOBJ_MODE_DUMMY replaced by VOBJ_MODE_SHOW now + eDraw = VOBJ_MODE_SHOW; + break; + case 2: // Hide all + eOle = VOBJ_MODE_HIDE; + eChart = VOBJ_MODE_HIDE; + eDraw = VOBJ_MODE_HIDE; + break; + default: // Show all + eOle = VOBJ_MODE_SHOW; + eChart = VOBJ_MODE_SHOW; + eDraw = VOBJ_MODE_SHOW; + break; + } + + aOpts.SetObjMode( VOBJ_TYPE_OLE, eOle ); + aOpts.SetObjMode( VOBJ_TYPE_CHART, eChart ); + aOpts.SetObjMode( VOBJ_TYPE_DRAW, eDraw ); + + rD.SetViewOptions( aOpts ); +} + +void ImportExcel::Standardwidth() +{ + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( maStrm.ReaduInt16(), GetCharWidth() ); + if (!pColRowBuff) + { + SAL_WARN("sc", "*ImportExcel::Standardwidth(): pColRowBuff is NULL!"); + return; + } + pColRowBuff->SetDefWidth( nScWidth, true ); +} + +void ImportExcel::Shrfmla() +{ + switch (mnLastRecId) + { + case EXC_ID2_FORMULA: + case EXC_ID3_FORMULA: + case EXC_ID4_FORMULA: + // This record MUST immediately follow a FORMULA record. + break; + default: + return; + } + + if (!mpLastFormula) + // The last FORMULA record should have left this data. + return; + + aIn.Ignore( 8 ); + sal_uInt16 nLenExpr = aIn.ReaduInt16(); + + // read mark is now on the formula + + std::unique_ptr<ScTokenArray> pResult; + + // The shared range in this record is erroneous more than half the time. + // Don't ever rely on it. Use the one from the formula cell above. + SCCOL nCol1 = mpLastFormula->mnCol; + SCROW nRow1 = mpLastFormula->mnRow; + + ScAddress aPos(nCol1, nRow1, GetCurrScTab()); + pFormConv->Reset(aPos); + pFormConv->Convert( pResult, maStrm, nLenExpr, true, FT_SharedFormula ); + + if (!pResult) + { + SAL_WARN("sc", "+ImportExcel::Shrfmla(): ScTokenArray is NULL!"); + return; + } + + pExcRoot->pShrfmlaBuff->Store(aPos, *pResult); + + // Create formula cell for the last formula record. + + ScDocumentImport& rDoc = GetDocImport(); + + ScFormulaCell* pCell = new ScFormulaCell(rD, aPos, std::move(pResult)); + pCell->GetCode()->WrapReference(aPos, EXC_MAXCOL8, EXC_MAXROW8); + rDoc.getDoc().CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + rDoc.getDoc().EnsureTable(aPos.Tab()); + rDoc.setFormulaCell(aPos, pCell); + pCell->SetNeedNumberFormat(false); + if (std::isfinite(mpLastFormula->mfValue)) + pCell->SetResultDouble(mpLastFormula->mfValue); + + GetXFRangeBuffer().SetXF(aPos, mpLastFormula->mnXF); + mpLastFormula->mpCell = pCell; +} + +void ImportExcel::Mulrk() +{ + /* rw (2 bytes): An Rw structure that specifies the row containing the + cells with numeric data. + + colFirst (2 bytes): A Col structure that specifies the first column in + the series of numeric cells within the sheet. The value of colFirst.col + MUST be less than or equal to 254. + + rgrkrec (variable): An array of RkRec structures. Each element in the + array specifies an RkRec in the row. The number of entries in the array + MUST be equal to the value given by the following formula: + + Number of entries in rgrkrec = (colLast.col – colFirst.col +1) + + colLast (2 bytes): A Col structure that specifies the last column in + the set of numeric cells within the sheet. This colLast.col value MUST + be greater than the colFirst.col value. */ + + XclAddress aXclPos; + aIn >> aXclPos; + + XclAddress aCurrXclPos(aXclPos); + while (true) + { + if (aXclPos.mnCol > aCurrXclPos.mnCol) + break; + if (aIn.GetRecLeft() <= 2) + break; + + sal_uInt16 nXF = aIn.ReaduInt16(); + sal_Int32 nRkNum = aIn.ReadInt32(); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aCurrXclPos, GetCurrScTab(), true ) ) + { + GetXFRangeBuffer().SetXF( aScPos, nXF ); + GetDocImport().setNumericCell(aScPos, XclTools::GetDoubleFromRK(nRkNum)); + } + ++aCurrXclPos.mnCol; + } +} + +void ImportExcel::Mulblank() +{ + /* rw (2 bytes): An Rw structure that specifies a row containing the blank + cells. + + colFirst (2 bytes): A Col structure that specifies the first column in + the series of blank cells within the sheet. The value of colFirst.col + MUST be less than or equal to 254. + + rgixfe (variable): An array of IXFCell structures. Each element of this + array contains an IXFCell structure corresponding to a blank cell in the + series. The number of entries in the array MUST be equal to the value + given by the following formula: + + Number of entries in rgixfe = (colLast.col – colFirst.col +1) + + colLast (2 bytes): A Col structure that specifies the last column in + the series of blank cells within the sheet. This colLast.col value MUST + be greater than colFirst.col value. */ + + XclAddress aXclPos; + aIn >> aXclPos; + + XclAddress aCurrXclPos(aXclPos); + while (true) + { + if (aXclPos.mnCol > aCurrXclPos.mnCol) + break; + if (aIn.GetRecLeft() <= 2) + break; + + sal_uInt16 nXF = aIn.ReaduInt16(); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aCurrXclPos, GetCurrScTab(), true ) ) + GetXFRangeBuffer().SetBlankXF( aScPos, nXF ); + ++aCurrXclPos.mnCol; + } +} + +void ImportExcel::Rstring() +{ + XclAddress aXclPos; + sal_uInt16 nXFIdx; + aIn >> aXclPos; + nXFIdx = aIn.ReaduInt16(); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + return; + + // unformatted Unicode string with separate formatting information + XclImpString aString; + aString.Read( maStrm ); + + // character formatting runs + if( !aString.IsRich() ) + aString.ReadFormats( maStrm ); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + XclImpStringHelper::SetToDocument(GetDocImport(), aScPos, *this, aString, nXFIdx); +} + +void ImportExcel::Cellmerging() +{ + XclImpAddressConverter& rAddrConv = GetAddressConverter(); + SCTAB nScTab = GetCurrScTab(); + + sal_uInt16 nCount = maStrm.ReaduInt16(); + sal_uInt16 nIdx = 0; + while (true) + { + if (maStrm.GetRecLeft() < 8) + break; + if (nIdx >= nCount) + break; + XclRange aXclRange; + maStrm >> aXclRange; // 16-bit rows and columns + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( rAddrConv.ConvertRange( aScRange, aXclRange, nScTab, nScTab, true ) ) + GetXFRangeBuffer().SetMerge( aScRange.aStart.Col(), aScRange.aStart.Row(), aScRange.aEnd.Col(), aScRange.aEnd.Row() ); + ++nIdx; + } +} + +void ImportExcel::Olesize() +{ + XclRange aXclOleSize( ScAddress::UNINITIALIZED ); + maStrm.Ignore( 2 ); + aXclOleSize.Read( maStrm, false ); + + SCTAB nScTab = GetCurrScTab(); + GetAddressConverter().ConvertRange( maScOleSize, aXclOleSize, nScTab, nScTab, false ); +} + +void ImportExcel::Row34() +{ + sal_uInt16 nRow, nRowHeight, nGrbit, nXF; + + nRow = aIn.ReaduInt16(); + aIn.Ignore( 4 ); + + SCROW nScRow = static_cast< SCROW >( nRow ); + + if( !GetRoot().GetDoc().ValidRow( nScRow ) ) + return; + + nRowHeight = aIn.ReaduInt16(); // specify direct in Twips + aIn.Ignore( 4 ); + + nRowHeight = nRowHeight & 0x7FFF; // Bit 15: Row Height not changed manually + if( !nRowHeight ) + nRowHeight = (GetBiff() == EXC_BIFF2) ? 0x25 : 0x225; + + nGrbit = aIn.ReaduInt16(); + nXF = aIn.ReaduInt16(); + + sal_uInt8 nLevel = ::extract_value< sal_uInt8 >( nGrbit, 0, 3 ); + pRowOutlineBuff->SetLevel( nScRow, nLevel, ::get_flag( nGrbit, EXC_ROW_COLLAPSED ) ); + pColRowBuff->SetRowSettings( nScRow, nRowHeight, nGrbit ); + + if( nGrbit & EXC_ROW_USEDEFXF ) + GetXFRangeBuffer().SetRowDefXF( nScRow, nXF & EXC_ROW_XFMASK ); +} + +void ImportExcel::Bof3() +{ + sal_uInt16 nSubType; + maStrm.DisableDecryption(); + maStrm.Ignore( 2 ); + nSubType = maStrm.ReaduInt16(); + + OSL_ENSURE( nSubType != 0x0100, "*ImportExcel::Bof3(): Biff3 as Workbook?!" ); + if( nSubType == 0x0100 ) // Book + pExcRoot->eDateiTyp = Biff3W; + else if( nSubType == 0x0020 ) // Chart + pExcRoot->eDateiTyp = Biff3C; + else if( nSubType == 0x0040 ) // Macro + pExcRoot->eDateiTyp = Biff3M; + else // #i51490# Excel interprets invalid indexes as worksheet + pExcRoot->eDateiTyp = Biff3; +} + +void ImportExcel::Array34() +{ + sal_uInt16 nFirstRow, nLastRow, nFormLen; + sal_uInt8 nFirstCol, nLastCol; + + nFirstRow = aIn.ReaduInt16(); + nLastRow = aIn.ReaduInt16(); + nFirstCol = aIn.ReaduInt8(); + nLastCol = aIn.ReaduInt8(); + aIn.Ignore( (GetBiff() >= EXC_BIFF5) ? 6 : 2 ); + nFormLen = aIn.ReaduInt16(); + + std::unique_ptr<ScTokenArray> pResult; + + if( GetRoot().GetDoc().ValidColRow( nLastCol, nLastRow ) ) + { + // the read mark is now on the formula, length in nFormLen + + pFormConv->Reset( ScAddress( static_cast<SCCOL>(nFirstCol), + static_cast<SCROW>(nFirstRow), GetCurrScTab() ) ); + pFormConv->Convert( pResult, maStrm, nFormLen, true ); + + SAL_WARN_IF(!pResult, "sc", "+ImportExcel::Array34(): ScTokenArray is NULL!"); + } + + if (pResult) + { + ScDocumentImport& rDoc = GetDocImport(); + ScRange aArrayRange(nFirstCol, nFirstRow, GetCurrScTab(), nLastCol, nLastRow, GetCurrScTab()); + rDoc.setMatrixCells(aArrayRange, *pResult, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1); + } +} + +void ImportExcel::Defrowheight345() +{ + sal_uInt16 nFlags, nDefHeight; + nFlags = maStrm.ReaduInt16(); + nDefHeight = maStrm.ReaduInt16(); + + if (!pColRowBuff) + { + SAL_WARN("sc", "*ImportExcel::Defrowheight345(): pColRowBuff is NULL!"); + return; + } + + pColRowBuff->SetDefHeight( nDefHeight, nFlags ); +} + +void ImportExcel::TableOp() +{ + sal_uInt16 nFirstRow = aIn.ReaduInt16(); + sal_uInt16 nLastRow = aIn.ReaduInt16(); + sal_uInt8 nFirstCol = aIn.ReaduInt8(); + sal_uInt8 nLastCol = aIn.ReaduInt8(); + sal_uInt16 nGrbit = aIn.ReaduInt16(); + sal_uInt16 nInpRow = aIn.ReaduInt16(); + sal_uInt16 nInpCol = aIn.ReaduInt16(); + sal_uInt16 nInpRow2 = aIn.ReaduInt16(); + sal_uInt16 nInpCol2 = aIn.ReaduInt16(); + + if (utl::ConfigManager::IsFuzzing()) + { + //shrink to smallish arbitrary value to not timeout + nLastRow = std::min<sal_uInt16>(nLastRow, MAXROW_30 / 2); + } + + if( GetRoot().GetDoc().ValidColRow( nLastCol, nLastRow ) ) + { + if( nFirstCol && nFirstRow ) + { + ScTabOpParam aTabOpParam; + aTabOpParam.meMode = (nGrbit & EXC_TABLEOP_BOTH) ? ScTabOpParam::Both : ((nGrbit & EXC_TABLEOP_ROW) ? ScTabOpParam::Row : ScTabOpParam::Column); + sal_uInt16 nCol = nFirstCol - 1; + sal_uInt16 nRow = nFirstRow - 1; + SCTAB nTab = GetCurrScTab(); + switch (aTabOpParam.meMode) + { + case ScTabOpParam::Column: + aTabOpParam.aRefFormulaCell.Set( + static_cast<SCCOL>(nFirstCol), + static_cast<SCROW>(nFirstRow - 1), nTab, false, + false, false ); + aTabOpParam.aRefFormulaEnd.Set( + static_cast<SCCOL>(nLastCol), + static_cast<SCROW>(nFirstRow - 1), nTab, false, + false, false ); + aTabOpParam.aRefColCell.Set( static_cast<SCCOL>(nInpCol), + static_cast<SCROW>(nInpRow), nTab, false, false, + false ); + nRow++; + break; + case ScTabOpParam::Row: + aTabOpParam.aRefFormulaCell.Set( + static_cast<SCCOL>(nFirstCol - 1), + static_cast<SCROW>(nFirstRow), nTab, false, false, + false ); + aTabOpParam.aRefFormulaEnd.Set( + static_cast<SCCOL>(nFirstCol - 1), + static_cast<SCROW>(nLastRow), nTab, false, false, + false ); + aTabOpParam.aRefRowCell.Set( static_cast<SCCOL>(nInpCol), + static_cast<SCROW>(nInpRow), nTab, false, false, + false ); + nCol++; + break; + case ScTabOpParam::Both: // TWO-INPUT + aTabOpParam.aRefFormulaCell.Set( + static_cast<SCCOL>(nFirstCol - 1), + static_cast<SCROW>(nFirstRow - 1), nTab, false, + false, false ); + aTabOpParam.aRefRowCell.Set( static_cast<SCCOL>(nInpCol), + static_cast<SCROW>(nInpRow), nTab, false, false, + false ); + aTabOpParam.aRefColCell.Set( static_cast<SCCOL>(nInpCol2), + static_cast<SCROW>(nInpRow2), nTab, false, false, + false ); + break; + } + + ScDocumentImport& rDoc = GetDocImport(); + ScRange aTabOpRange(nCol, nRow, nTab, nLastCol, nLastRow, nTab); + rDoc.setTableOpCells(aTabOpRange, aTabOpParam); + } + } + else + { + bTabTruncated = true; + GetTracer().TraceInvalidRow(nLastRow, rD.MaxRow()); + } +} + +void ImportExcel::Bof4() +{ + sal_uInt16 nSubType; + maStrm.DisableDecryption(); + maStrm.Ignore( 2 ); + nSubType = maStrm.ReaduInt16(); + + if( nSubType == 0x0100 ) // Book + pExcRoot->eDateiTyp = Biff4W; + else if( nSubType == 0x0020 ) // Chart + pExcRoot->eDateiTyp = Biff4C; + else if( nSubType == 0x0040 ) // Macro + pExcRoot->eDateiTyp = Biff4M; + else // #i51490# Excel interprets invalid indexes as worksheet + pExcRoot->eDateiTyp = Biff4; +} + +void ImportExcel::Bof5() +{ + //POST: eDateiTyp = Type of the file to be read + sal_uInt16 nSubType, nVers; + BiffTyp eDatei; + + maStrm.DisableDecryption(); + nVers = maStrm.ReaduInt16(); + nSubType = maStrm.ReaduInt16( ); + + switch( nSubType ) + { + case 0x0005: eDatei = Biff5W; break; // workbook globals + case 0x0006: eDatei = Biff5V; break; // VB module + case 0x0020: eDatei = Biff5C; break; // chart + case 0x0040: eDatei = Biff5M4; break; // macro sheet + case 0x0010: // worksheet + default: eDatei = Biff5; break; // tdf#144732 Excel interprets invalid indexes as worksheet + } + + if( nVers == 0x0600 && (GetBiff() == EXC_BIFF8) ) + eDatei = static_cast<BiffTyp>( eDatei - Biff5 + Biff8 ); + + pExcRoot->eDateiTyp = eDatei; +} + +void ImportExcel::EndSheet() +{ + pExcRoot->pExtSheetBuff->Reset(); + + if( GetBiff() <= EXC_BIFF5 ) + { + pExcRoot->pExtNameBuff->Reset(); + mnLastRefIdx = 0; + } + + FinalizeTable(); +} + +void ImportExcel::NewTable() +{ + SCTAB nTab = GetCurrScTab(); + if( nTab > 0 && !rD.HasTable( nTab ) ) + rD.MakeTable( nTab ); + + if (nTab == 0 && GetBiff() == EXC_BIFF2) + { + // For Excel 2.1 Worksheet file, we need to set the file name as the + // sheet name. + INetURLObject aURL(GetDocUrl()); + rD.RenameTab(0, aURL.getBase()); + } + + pExcRoot->pShrfmlaBuff->Clear(); + maLastFormulaCells.clear(); + mpLastFormula = nullptr; + + InitializeTable( nTab ); + + XclImpOutlineDataBuffer* pNewItem = new XclImpOutlineDataBuffer( GetRoot(), nTab ); + pOutlineListBuffer->push_back( std::unique_ptr<XclImpOutlineDataBuffer>(pNewItem) ); + pExcRoot->pColRowBuff = pColRowBuff = pNewItem->GetColRowBuff(); + pColOutlineBuff = pNewItem->GetColOutline(); + pRowOutlineBuff = pNewItem->GetRowOutline(); +} + +std::unique_ptr<ScTokenArray> ImportExcel::ErrorToFormula( bool bErrOrVal, sal_uInt8 nError, double& rVal ) +{ + return pFormConv->GetBoolErr( XclTools::ErrorToEnum( rVal, bErrOrVal, nError ) ); +} + +void ImportExcel::AdjustRowHeight() +{ + /* Speed up chart import: import all sheets without charts, then + update row heights (here), last load all charts -> do not any longer + update inside of ScDocShell::ConvertFrom() (causes update of existing + charts during each and every change of row height). */ + if( ScModelObj* pDocObj = GetDocModelObj() ) + pDocObj->UpdateAllRowHeights(); +} + +void ImportExcel::PostDocLoad() +{ + /* Set automatic page numbering in Default page style (default is "page number = 1"). + Otherwise hidden tables (i.e. for scenarios) which have Default page style will + break automatic page numbering. */ + if( SfxStyleSheetBase* pStyleSheet = GetStyleSheetPool().Find( ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Page ) ) + pStyleSheet->GetItemSet().Put( SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, 0 ) ); + + // outlines for all sheets, sets hidden rows and columns (#i11776# after filtered ranges) + for (auto& rxBuffer : *pOutlineListBuffer) + rxBuffer->Convert(); + + // document view settings (before visible OLE area) + GetDocViewSettings().Finalize(); + + // process all drawing objects (including OLE, charts, controls; after hiding rows/columns; before visible OLE area) + GetObjectManager().ConvertObjects(); + + // visible area (used if this document is an embedded OLE object) + if( SfxObjectShell* pDocShell = GetDocShell() ) + { + // visible area if embedded + const ScExtDocSettings& rDocSett = GetExtDocOptions().GetDocSettings(); + SCTAB nDisplScTab = rDocSett.mnDisplTab; + + /* #i44077# If a new OLE object is inserted from file, there is no + OLESIZE record in the Excel file. Calculate used area from file + contents (used cells and drawing objects). */ + if( !maScOleSize.IsValid() ) + { + // used area of displayed sheet (cell contents) + if( const ScExtTabSettings* pTabSett = GetExtDocOptions().GetTabSettings( nDisplScTab ) ) + maScOleSize = pTabSett->maUsedArea; + // add all valid drawing objects + ScRange aScObjArea = GetObjectManager().GetUsedArea( nDisplScTab ); + if( aScObjArea.IsValid() ) + maScOleSize.ExtendTo( aScObjArea ); + } + + // valid size found - set it at the document + if( maScOleSize.IsValid() ) + { + pDocShell->SetVisArea( GetDoc().GetMMRect( + maScOleSize.aStart.Col(), maScOleSize.aStart.Row(), + maScOleSize.aEnd.Col(), maScOleSize.aEnd.Row(), nDisplScTab ) ); + GetDoc().SetVisibleTab( nDisplScTab ); + } + } + + // open forms in alive mode (has no effect, if no controls in document) + if( ScModelObj* pDocObj = GetDocModelObj() ) + pDocObj->setPropertyValue( SC_UNO_APPLYFMDES, uno::Any( false ) ); + + // enables extended options to be set to the view after import + GetExtDocOptions().SetChanged( true ); + + // root data owns the extended document options -> create a new object + GetDoc().SetExtDocOptions( std::make_unique<ScExtDocOptions>( GetExtDocOptions() ) ); + + const SCTAB nLast = rD.GetTableCount(); + const ScRange* p; + + if( GetRoot().GetPrintAreaBuffer().HasRanges() ) + { + for( SCTAB n = 0 ; n < nLast ; n++ ) + { + p = GetRoot().GetPrintAreaBuffer().First(n); + if( p ) + { + rD.ClearPrintRanges( n ); + while( p ) + { + rD.AddPrintRange( n, *p ); + p = GetRoot().GetPrintAreaBuffer().Next(); + } + } + else + { + // #i4063# no print ranges -> print entire sheet + rD.SetPrintEntireSheet( n ); + } + } + GetTracer().TracePrintRange(); + } + + if( !GetRoot().GetTitleAreaBuffer().HasRanges() ) + return; + + for( SCTAB n = 0 ; n < nLast ; n++ ) + { + p = GetRoot().GetTitleAreaBuffer().First(n); + if( p ) + { + bool bRowVirgin = true; + bool bColVirgin = true; + + while( p ) + { + if( p->aStart.Col() == 0 && p->aEnd.Col() == rD.MaxCol() && bRowVirgin ) + { + rD.SetRepeatRowRange( n, *p ); + bRowVirgin = false; + } + + if( p->aStart.Row() == 0 && p->aEnd.Row() == rD.MaxRow() && bColVirgin ) + { + rD.SetRepeatColRange( n, *p ); + bColVirgin = false; + } + + p = GetRoot().GetTitleAreaBuffer().Next(); + } + } + } +} + +XclImpOutlineDataBuffer::XclImpOutlineDataBuffer( const XclImpRoot& rRoot, SCTAB nScTab ) : + XclImpRoot( rRoot ), + mxColOutlineBuff( std::make_shared<XclImpOutlineBuffer>( rRoot.GetXclMaxPos().Col() + 1 ) ), + mxRowOutlineBuff( std::make_shared<XclImpOutlineBuffer>( rRoot.GetXclMaxPos().Row() + 1 ) ), + mxColRowBuff( std::make_shared<XclImpColRowSettings>( rRoot ) ), + mnScTab( nScTab ) +{ +} + +XclImpOutlineDataBuffer::~XclImpOutlineDataBuffer() +{ +} + +void XclImpOutlineDataBuffer::Convert() +{ + mxColOutlineBuff->SetOutlineArray( &GetDoc().GetOutlineTable( mnScTab, true )->GetColArray() ); + mxColOutlineBuff->MakeScOutline(); + + mxRowOutlineBuff->SetOutlineArray( &GetDoc().GetOutlineTable( mnScTab, true )->GetRowArray() ); + mxRowOutlineBuff->MakeScOutline(); + + mxColRowBuff->ConvertHiddenFlags( mnScTab ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/namebuff.cxx b/sc/source/filter/excel/namebuff.cxx new file mode 100644 index 000000000..523145209 --- /dev/null +++ b/sc/source/filter/excel/namebuff.cxx @@ -0,0 +1,187 @@ +/* -*- 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 <namebuff.hxx> + +#include <document.hxx> +#include <scextopt.hxx> +#include <tokenarray.hxx> + +#include <root.hxx> +#include <xiroot.hxx> + +#include <osl/diagnose.h> + +sal_uInt32 StringHashEntry::MakeHashCode( const OUString& r ) +{ + sal_uInt32 n = 0; + const sal_Unicode* pCurrent = r.getStr(); + sal_Unicode cCurrent = *pCurrent; + + while( cCurrent ) + { + n *= 70; + n += static_cast<sal_uInt32>(cCurrent); + pCurrent++; + cCurrent = *pCurrent; + } + + return n; +} + +SharedFormulaBuffer::SharedFormulaBuffer(RootData* pRD) + : ExcRoot(pRD) +{ +} + +SharedFormulaBuffer::~SharedFormulaBuffer() +{ + Clear(); +} + +void SharedFormulaBuffer::Clear() +{ + maTokenArrays.clear(); +} + +void SharedFormulaBuffer::Store( const ScAddress& rPos, const ScTokenArray& rArray ) +{ + ScTokenArray aCode(rArray.CloneValue()); + aCode.GenHash(); + maTokenArrays.emplace(rPos, std::move(aCode)); +} + +const ScTokenArray* SharedFormulaBuffer::Find( const ScAddress& rRefPos ) const +{ + TokenArraysType::const_iterator it = maTokenArrays.find(rRefPos); + if (it == maTokenArrays.end()) + return nullptr; + + return &it->second; +} + +sal_Int16 ExtSheetBuffer::Add( const OUString& rFPAN, const OUString& rTN, const bool bSWB ) +{ + maEntries.emplace_back( rFPAN, rTN, bSWB ); + // return 1-based index of EXTERNSHEET + return static_cast< sal_Int16 >( maEntries.size() ); +} + +bool ExtSheetBuffer::GetScTabIndex( sal_uInt16 nExcIndex, sal_uInt16& rScIndex ) +{ + OSL_ENSURE( nExcIndex, + "*ExtSheetBuffer::GetScTabIndex(): Sheet-Index == 0!" ); + + if ( !nExcIndex || nExcIndex > maEntries.size() ) + return false; + + Cont* pCur = &maEntries[ nExcIndex - 1 ]; + sal_uInt16& rTabNum = pCur->nTabNum; + + if( rTabNum < 0xFFFD ) + { + rScIndex = rTabNum; + return true; + } + + if( rTabNum == 0xFFFF ) + {// create new table + SCTAB nNewTabNum; + if( pCur->bSWB ) + {// table is in the same workbook! + if( pExcRoot->pIR->GetDoc().GetTable( pCur->aTab, nNewTabNum ) ) + { + rScIndex = rTabNum = static_cast<sal_uInt16>(nNewTabNum); + return true; + } + else + rTabNum = 0xFFFD; + } + else if( pExcRoot->pIR->GetDocShell() ) + {// table is 'really' external + if( pExcRoot->pIR->GetExtDocOptions().GetDocSettings().mnLinkCnt == 0 ) + { + OUString aURL( ScGlobal::GetAbsDocName( pCur->aFile, + pExcRoot->pIR->GetDocShell() ) ); + OUString aTabName( ScGlobal::GetDocTabName( aURL, pCur->aTab ) ); + if( pExcRoot->pIR->GetDoc().LinkExternalTab( nNewTabNum, aTabName, aURL, pCur->aTab ) ) + { + rScIndex = rTabNum = static_cast<sal_uInt16>(nNewTabNum); + return true; + } + else + rTabNum = 0xFFFE; // no table is created for now -> and likely + // will not be created later... + } + else + rTabNum = 0xFFFE; + + } + } + + return false; +} + +void ExtSheetBuffer::Reset() +{ + maEntries.clear(); +} + +bool ExtName::IsOLE() const +{ + return ( nFlags & 0x0002 ) != 0; +} + +ExtNameBuff::ExtNameBuff( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void ExtNameBuff::AddDDE( sal_Int16 nRefIdx ) +{ + ExtName aNew( 0x0001 ); + maExtNames[ nRefIdx ].push_back( aNew ); +} + +void ExtNameBuff::AddOLE( sal_Int16 nRefIdx, sal_uInt32 nStorageId ) +{ + ExtName aNew( 0x0002 ); + aNew.nStorageId = nStorageId; + maExtNames[ nRefIdx ].push_back( aNew ); +} + +void ExtNameBuff::AddName( sal_Int16 nRefIdx ) +{ + ExtName aNew( 0x0004 ); + maExtNames[ nRefIdx ].push_back( aNew ); +} + +const ExtName* ExtNameBuff::GetNameByIndex( sal_Int16 nRefIdx, sal_uInt16 nNameIdx ) const +{ + OSL_ENSURE( nNameIdx > 0, "ExtNameBuff::GetNameByIndex() - invalid name index" ); + ExtNameMap::const_iterator aIt = maExtNames.find( nRefIdx ); + return ((aIt != maExtNames.end()) && (0 < nNameIdx) && (nNameIdx <= aIt->second.size())) ? &aIt->second[ nNameIdx - 1 ] : nullptr; +} + +void ExtNameBuff::Reset() +{ + maExtNames.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/ooxml-export-TODO.txt b/sc/source/filter/excel/ooxml-export-TODO.txt new file mode 100644 index 000000000..d995598d2 --- /dev/null +++ b/sc/source/filter/excel/ooxml-export-TODO.txt @@ -0,0 +1,164 @@ +# +# 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 . +# + +TODO/Unimplemented Calc OOXML Export Features: +============================================= + +Partially implemented features are not mentioned here; grep for OOXTODO within +sc/source/filter/*. + +In updated OfficeFileFormatsProtocols.zip [MS-XLS].pdf, +Section §2.3.1 (p.154) provides the record name :: record number mapping, and +Section §2.3.2 (p.165) provides the record number :: record name mapping. + +Elements: + - Workbook (§3.2): + - customWorkbookViews (§3.2.3) + - ext (§3.2.7) + - extLst (§3.2.10) + - fileRecoveryPr (§3.2.11) [ CRASHRECERR? 865h ] + - fileSharing (§3.2.12) [ FILESHARING 5Bh ] + - functionGroup (§3.2.14) [ FNGRP12 898h; FNGROUPNAME 9Ah ] + - functionGroups (§3.2.15) [ FNGROUPCOUNT: 9Ch ] + - oleSize (§3.2.16) [ OLESIZE DEh ] + - smartTagPr (§3.2.21) [ BOOKEXT 863h ] + - smartTagType (§3.2.22) [ unknown record ] + - smartTagTypes (§3.2.23) [ unknown record ] + - webPublishing (§3.2.24) [ WOPT 80Bh ] + - webPublishObject (§3.2.25) [ WEBPUB 801h ] + - webPublishObjects (§3.2.26) [ unsupported ] + - Worksheets (§3.3.1): + - autoFilter (§3.3.1.1) [ AutoFilter 9Eh ] + - cellSmartTag (§3.3.1.4) [ FEAT 868h ] + - cellSmartTagPr (§3.3.1.5) [ FEAT? 868h ] + - cellSmartTags (§3.3.1.6) [ FEAT 868h ] + - cellWatch (§3.3.1.7) [ CELLWATCH 86Ch ] + - cellWatches (§3.3.1.8) [ CELLWATCH 86Ch ] + - cfRule (§3.3.1.9) [ CF 1B1h ] + - cfvo (§3.3.1.10) [ CF12 87Ah ] + - chartsheet (§3.3.1.11) [ CHARTFRTINFO 850h, FRTWRAPPER 851h...] + - color (§3.3.1.14) [ DXF 88Dh xfpropBorder? + XFEXT 87Dh xclrType? ] + - colorScale (§3.3.1.15) [ DXF 88Dh? ] + - control (§3.3.1.18) [ ??? ] + - controls (§3.3.1.19) [ ??? ] + - customPr (§3.3.1.20) [ ??? ] + - customProperties (§3.3.1.21) [ ??? ] + - customSheetView (§3.3.1.22) [ ???; for charts; see chartsheet? ] + - customSheetView (§3.3.1.23) [ ??? ] + - customSheetViews (§3.3.1.24) [ ???; for charts; see chartsheet? ] + - customSheetViews (§3.3.1.25) [ ??? ] + - dataBar (§3.3.1.26) [ CF12 87Ah ct=Databar ] + - dataConsolidate (§3.3.1.27) [ DCON 50h ] + - dataRef (§3.3.1.28) [ DCONBIN 1B5h ] + - dataRefs (§3.3.1.29) [ ??? ] + - dialogsheet (§3.3.1.32) [ ??? ] + - drawing (§3.3.1.34) [ ??? ] + - evenFooter (§3.3.1.35) [ HeaderFooter 89Ch ] + - evenHeader (§3.3.1.36) [ HeaderFooter 89Ch ] + - firstFooter (§3.3.1.38) [ HeaderFooter 89Ch ] + - firstHeader (§3.3.1.39) [ HeaderFooter 89Ch ] + - formula (§3.3.1.40) [ CF 1B1h ] + - iconSet (§3.3.1.46) [ CF12 87Ah ct=CFMultistate ] + - ignoredError (§3.3.1.47) [ Feat/FeatFormulaErr2/FFErrorCheck 868h ] + - ignoredErrors (§3.3.1.48) [ Feat 868h ] + - legacyDrawing (§3.3.1.51) [ MsoDrawing ECh ] + - legacyDrawingHF (§3.3.1.52) [ ??? ] + - oleObject (§3.3.1.57) [ ??? ] + - oleObjects (§3.3.1.58) [ ??? ] + - outlinePr (§3.3.1.59) [ ??? ] + - pageSetup (§3.3.1.62) [ ???; for charts; see chartsheet? ] + - picture (§3.3.1.65) [ BkHim E9h; see XclExpBitmap ] + - pivotArea (§3.3.1.66) [ ??? ] + - pivotSelection (§3.3.1.67) [ ??? ] + - protectedRange (§3.3.1.69) [ ??? ] + - protectedRanges (§3.3.1.70) [ ??? ] + - sheetCalcPr (§3.3.1.76) [ REFRESHALL?? ] + - sheetFormatPr (§3.3.1.78) [ lots of records? ] + @defaultColWidth: DefColWidth + @defaultRowHeight: DEFROWHEIGHT + @baseColWidth: ColInfo/coldx? + @customHeight: ColInfo/fUserSet? + @zeroHeight: ColInfo/fHidden? + @thickTop: ? + @thickBottom: ? + @outlineLevelRow: ? + @outlineLevelCol: ColInfo/iOutLevel? + - sheetPr (§3.3.1.80) [ ??? ; for charts ] + - sheetView (§3.3.1.84) [ ??? ; for charts ] + - sheetViews (§3.3.1.86) [ ??? ; for charts ] + - smartTags (§3.3.1.87) [ FEAT 868h; isf=ISFFACTOID ] + - sortCondition (§3.3.1.88) [ SortData 895h? ] + - sortState (§3.3.1.89) [ Sort 90h ] + - tabColor (§3.3.1.90) [ SheetExt 862h ] + - tablePart (§3.3.1.91) [ ??? ] + - tableParts (§3.3.1.92) [ ??? ] + - webPublishItem (§3.3.1.94) [ WebPub 801h ] + - webPublishItems (§3.3.1.95) + - AutoFilter Settings (§3.3.2): + - colorFilter (§3.3.2.1) [ AutoFilter12 87Eh, + DXFN12NoCB struct ] + - dateGroupItem (§3.3.2.4) [ AutoFilter12 87Eh, + AF12DateInfo struct ] + - dynamicFilter (§3.3.2.5) [ AutoFilter12 87Eh, cft field ] + - filter (§3.3.2.6) [ AutoFilter12 87Eh, rgCriteria? ] + - filters (§3.3.2.9) [ AutoFilter12 87Eh, rgCriteria? ] + - iconFilter (§3.3.2.9) [ AutoFilter12 87Eh, + AF12CellIcon struct ] + - Shared String Table (§3.4): + - phoneticPr (§3.4.3) + - rPh (§3.4.6) + - Tables (§3.5.1): + - calculatedColumnFormula (§3.5.1.1) + [ ??? ] + - table (§3.5.1.2) [ ??? ] + - tableColumn (§3.5.1.3) [ ??? ] + - tableColumns (§3.5.1.4) [ ??? ] + - tableStyleInfo (§3.5.1.5) [ ??? ] + - totalRowFormula (§3.5.1.6) [ ??? ] + - xmlColumnPr (§3.5.1.7) [ ??? ] + - Single Cell Tables (§3.5.2): + - singleXmlCell (§3.5.2.1) [ ??? ] + - singleXmlCells (§3.5.2.2) [ ??? ] + - xmlCellPr (§3.5.2.3) [ ??? ] + - xmlPr (§3.5.2.4) [ ??? ] + - Calculation Chain (§3.6): + - c (§3.6.1) [ ??? ] + - calcChain (§3.6.2) [ ??? ] + - Comments (§3.7): + - Note: Excel *requires* that there be a drawing object associated + with the comment before it will show it. If you _just_ generate the + <comments/> XML part and create a <Relationship/> for it, Excel + will NOT display the comment. + - As drawing is not currently implemented, comments support is + incomplete. + - TODO: text formatting. Currently we only write unformatted text + into comments?.xml, as I'm not sure how formatted text is handled. + - Styles (§3.8): + - dxf (§3.8.14): [ DXF 88Dh; unsupported ] + - dxfs (§3.8.15): [ DXF 88Dh ] + - gradientFill (§3.8.23): [ ??? ] + - horizontal (§3.8.24): [ DXF 88Dh fNewBorder, xfprops ] + - mruColors (§3.8.28): [ ??? ] + - scheme (§3.8.36): [ ??? ] + - stop (§3.8.38): [ ??? ] + - tableStyle (§3.8.40): [ TableStyle 88Fh; unsupported ] + - tableStyleElement (§3.8.41): [ TableStyleElement 890h; unsupported ] + - tableStyles (§3.8.42): [ TableStyles 88Eh; unsupported ] + - vertical (§3.8.44): [ DXF 88Dh fNewBorder, xfprops ] + diff --git a/sc/source/filter/excel/read.cxx b/sc/source/filter/excel/read.cxx new file mode 100644 index 000000000..cf9465a37 --- /dev/null +++ b/sc/source/filter/excel/read.cxx @@ -0,0 +1,1314 @@ +/* -*- 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 <document.hxx> +#include <scerrors.hxx> +#include <fprogressbar.hxx> +#include <globstr.hrc> +#include <xlcontent.hxx> +#include <xltracer.hxx> +#include <xltable.hxx> +#include <xihelper.hxx> +#include <xipage.hxx> +#include <xiview.hxx> +#include <xilink.hxx> +#include <xiname.hxx> +#include <xlname.hxx> +#include <xicontent.hxx> +#include <xiescher.hxx> +#include <xipivot.hxx> +#include <xistyle.hxx> +#include <XclImpChangeTrack.hxx> +#include <documentimport.hxx> + +#include <root.hxx> +#include <imp_op.hxx> +#include <excimp8.hxx> + +#include <unotools/configmgr.hxx> + +#include <memory> + +namespace +{ + bool TryStartNextRecord(XclImpStream& rIn, std::size_t nProgressBasePos) + { + bool bValid = true; + // i#115255 fdo#40304 BOUNDSHEET doesn't point to a valid + // BOF record position. Scan the records manually (from + // the BOUNDSHEET position) until we find a BOF. Some 3rd + // party Russian programs generate invalid xls docs with + // this kind of silliness. + if (rIn.PeekRecId(nProgressBasePos) == EXC_ID5_BOF) + // BOUNDSHEET points to a valid BOF record. Good. + rIn.StartNextRecord(nProgressBasePos); + else + { + while (bValid && rIn.GetRecId() != EXC_ID5_BOF) + bValid = rIn.StartNextRecord(); + } + return bValid; + } +} + +ErrCode ImportExcel::Read() +{ + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + XclImpPalette& rPal = GetPalette(); + XclImpFontBuffer& rFontBfr = GetFontBuffer(); + XclImpNumFmtBuffer& rNumFmtBfr = GetNumFmtBuffer(); + XclImpXFBuffer& rXFBfr = GetXFBuffer(); + XclImpNameManager& rNameMgr = GetNameManager(); + // call to GetCurrSheetDrawing() cannot be cached (changes in new sheets) + + enum STATE { + Z_BiffNull, // not a valid Biff-Format + Z_Biff2, // Biff2: only one table + + Z_Biff3, // Biff3: only one table + + Z_Biff4, // Biff4: only one table + Z_Biff4W, // Biff4 Workbook: Globals + Z_Biff4T, // Biff4 Workbook: a table itself + Z_Biff4E, // Biff4 Workbook: between tables + + Z_Biff5WPre,// Biff5: Prefetch Workbook + Z_Biff5W, // Biff5: Globals + Z_Biff5TPre,// Biff5: Prefetch for Shrfmla/Array Formula + Z_Biff5T, // Biff5: a table itself + Z_Biff5E, // Biff5: between tables + Z_Biffn0, // all Biffs: skip table till next EOF + Z_End }; + + STATE eCurrent = Z_BiffNull, ePrev = Z_BiffNull; + + ErrCode eLastErr = ERRCODE_NONE; + sal_uInt16 nOpcode; + sal_uInt16 nBofLevel = 0; + + std::unique_ptr< ScfSimpleProgressBar > pProgress( new ScfSimpleProgressBar( + aIn.GetSvStreamSize(), GetDocShell(), STR_LOAD_DOC ) ); + + /* #i104057# Need to track a base position for progress bar calculation, + because sheet substreams may not be in order of sheets. */ + std::size_t nProgressBasePos = 0; + std::size_t nProgressBaseSize = 0; + + for (; eCurrent != Z_End; mnLastRecId = nOpcode) + { + if( eCurrent == Z_Biff5E ) + { + sal_uInt16 nScTab = GetCurrScTab(); + if( nScTab < maSheetOffsets.size() ) + { + nProgressBaseSize += (aIn.GetSvStreamPos() - nProgressBasePos); + nProgressBasePos = maSheetOffsets[ nScTab ]; + + bool bValid = TryStartNextRecord(aIn, nProgressBasePos); + if (!bValid) + { + // Safeguard ourselves from potential infinite loop. + eCurrent = Z_End; + } + } + else + eCurrent = Z_End; + } + else + aIn.StartNextRecord(); + + nOpcode = aIn.GetRecId(); + + if( !aIn.IsValid() ) + { + // finalize table if EOF is missing + switch( eCurrent ) + { + case Z_Biff2: + case Z_Biff3: + case Z_Biff4: + case Z_Biff4T: + case Z_Biff5TPre: + case Z_Biff5T: + rNumFmtBfr.CreateScFormats(); + Eof(); + break; + default:; + } + break; + } + + if( eCurrent == Z_End ) + break; + + if( eCurrent != Z_Biff5TPre && eCurrent != Z_Biff5WPre ) + pProgress->ProgressAbs( nProgressBaseSize + aIn.GetSvStreamPos() - nProgressBasePos ); + + switch( eCurrent ) + { + + case Z_BiffNull: // ------------------------------- Z_BiffNull - + { + switch( nOpcode ) + { + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: + { + // #i23425# don't rely on the record ID, but on the detected BIFF version + switch( GetBiff() ) + { + case EXC_BIFF2: + Bof2(); + if( pExcRoot->eDateiTyp == Biff2 ) + { + eCurrent = Z_Biff2; + NewTable(); + } + break; + case EXC_BIFF3: + Bof3(); + if( pExcRoot->eDateiTyp == Biff3 ) + { + eCurrent = Z_Biff3; + NewTable(); + } + break; + case EXC_BIFF4: + Bof4(); + if( pExcRoot->eDateiTyp == Biff4 ) + { + eCurrent = Z_Biff4; + NewTable(); + } + else if( pExcRoot->eDateiTyp == Biff4W ) + eCurrent = Z_Biff4W; + break; + case EXC_BIFF5: + Bof5(); + if( pExcRoot->eDateiTyp == Biff5W ) + { + eCurrent = Z_Biff5WPre; + + nBdshtTab = 0; + + aIn.StoreGlobalPosition(); // store position + } + else if( pExcRoot->eDateiTyp == Biff5 ) + { + // #i62752# possible to have BIFF5 sheet without globals + NewTable(); + eCurrent = Z_Biff5TPre; // Shrfmla Prefetch, Row-Prefetch + nBofLevel = 0; + aIn.StoreGlobalPosition(); // store position + } + break; + default: + DBG_ERROR_BIFF(); + } + } + break; + } + } + break; + + case Z_Biff2: // ---------------------------------- Z_Biff2 - + { + switch( nOpcode ) + { + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x06: Formula25(); break; // FORMULA [ 2 5] + case 0x08: Row25(); break; // ROW [ 2 5] + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_End; + break; + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x18: rNameMgr.ReadName( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x1E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x20: Columndefault(); break; // COLUMNDEFAULT[ 2 ] + case 0x21: Array25(); break; // ARRAY [ 2 5] + case 0x23: Externname25(); break; // EXTERNNAME [ 2 5] + case 0x24: Colwidth(); break; // COLWIDTH [ 2 ] + case 0x25: Defrowheight2(); break; // DEFAULTROWHEI[ 2 ] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID2_FONT: rFontBfr.ReadFont( maStrm ); break; + case EXC_ID_EFONT: rFontBfr.ReadEfont( maStrm ); break; + case 0x3E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x43: rXFBfr.ReadXF( maStrm ); break; + case 0x44: Ixfe(); break; // IXFE [ 2 ] + } + } + break; + + case Z_Biff3: // ---------------------------------- Z_Biff3 - + { + switch( nOpcode ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_End; + break; + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x1E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x22: Rec1904(); break; // 1904 [ 2345] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x0206: Formula3(); break; // FORMULA [ 3 ] + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0221: Array34(); break; // ARRAY [ 34 ] + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x0243: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + } + break; + + case Z_Biff4: // ---------------------------------- Z_Biff4 - + { + switch( nOpcode ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_End; + break; + case 0x12: SheetProtect(); break; // SHEET PROTECTION + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x22: Rec1904(); break; // 1904 [ 2345] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0xA1: rPageSett.ReadSetup( maStrm ); break; + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0221: Array34(); break; // ARRAY [ 34 ] + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x0406: Formula4(); break; // FORMULA [ 4 ] + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x0443: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + } + break; + + case Z_Biff4W: // --------------------------------- Z_Biff4W - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + rNameMgr.ConvertAllTokens(); + eCurrent = Z_End; + break; + case 0x12: DocProtect(); break; // PROTECT [ 5] + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x8F: break; // BUNDLEHEADER [ 4 ] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case EXC_ID4_BOF: // BOF [ 4 ] + Bof4(); + if( pExcRoot->eDateiTyp == Biff4 ) + { + eCurrent = Z_Biff4T; + NewTable(); + } + else + eCurrent = Z_End; + break; + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x0443: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + + } + break; + + case Z_Biff4T: // --------------------------------- Z_Biff4T - + { + switch( nOpcode ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x0A: // EOF [ 2345] + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_Biff4E; + break; + case 0x12: SheetProtect(); break; // SHEET PROTECTION + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x8F: break; // BUNDLEHEADER [ 4 ] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0xA1: rPageSett.ReadSetup( maStrm ); break; + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0221: Array34(); break; + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x0406: Formula4(); break; + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x0443: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + + } + break; + + case Z_Biff4E: // --------------------------------- Z_Biff4E - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + eCurrent = Z_End; + break; + case 0x8F: break; // BUNDLEHEADER [ 4 ] + case EXC_ID4_BOF: // BOF [ 4 ] + Bof4(); + NewTable(); + if( pExcRoot->eDateiTyp == Biff4 ) + { + eCurrent = Z_Biff4T; + } + else + { + ePrev = eCurrent; + eCurrent = Z_Biffn0; + } + break; + } + + } + break; + case Z_Biff5WPre: // ------------------------------ Z_Biff5WPre - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + eCurrent = Z_Biff5W; + aIn.SeekGlobalPosition(); // and back to old position + break; + case 0x12: DocProtect(); break; // PROTECT [ 5] + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x3D: Window1(); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x85: Boundsheet(); break; // BOUNDSHEET [ 5] + case 0x8C: Country(); break; // COUNTRY [ 345] + // PALETTE follows XFs, but already needed while reading the XFs + case 0x92: rPal.ReadPalette( maStrm ); break; + } + } + break; + case Z_Biff5W: // --------------------------------- Z_Biff5W - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rXFBfr.CreateUserStyles(); + rNameMgr.ConvertAllTokens(); + eCurrent = Z_Biff5E; + break; + case 0x18: rNameMgr.ReadName( maStrm ); break; + case 0x1E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x22: Rec1904(); break; // 1904 [ 2345] + case 0x31: rFontBfr.ReadFont( maStrm ); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x8D: Hideobj(); break; // HIDEOBJ [ 345] + case 0xDE: Olesize(); break; + case 0xE0: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + } + + } + break; + + case Z_Biff5TPre: // ------------------------------- Z_Biff5Pre - + { + if (nOpcode == EXC_ID5_BOF) + nBofLevel++; + else if( (nOpcode == 0x000A) && nBofLevel ) + nBofLevel--; + else if( !nBofLevel ) // don't read chart records + { + switch( nOpcode ) + { + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case 0x08: Row25(); break; // ROW [ 2 5] + case 0x0A: // EOF [ 2345] + eCurrent = Z_Biff5T; + aIn.SeekGlobalPosition(); // and back to old position + break; + case 0x12: SheetProtect(); break; // SHEET PROTECTION + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x21: Array25(); break; // ARRAY [ 2 5] + case 0x23: Externname25(); break; // EXTERNNAME [ 2 5] + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x81: Wsbool(); break; // WSBOOL [ 2345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0221: Array34(); break; // ARRAY [ 34 ] + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + } + } + } + break; + + case Z_Biff5T: // --------------------------------- Z_Biff5T - + { + switch( nOpcode ) + { + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case EXC_ID2_FORMULA: + case EXC_ID3_FORMULA: + case EXC_ID4_FORMULA: Formula25(); break; + case EXC_ID_SHRFMLA: Shrfmla(); break; + case 0x0A: Eof(); eCurrent = Z_Biff5E; break; + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x23: Externname25(); break; // EXTERNNAME [ 2 5] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x83: + case 0x84: rPageSett.ReadCenter( maStrm ); break; + case 0xA0: rTabViewSett.ReadScl( maStrm ); break; + case 0xA1: rPageSett.ReadSetup( maStrm ); break; + case 0xBD: Mulrk(); break; // MULRK [ 5] + case 0xBE: Mulblank(); break; // MULBLANK [ 5] + case 0xD6: Rstring(); break; // RSTRING [ 5] + case 0x00E5: Cellmerging(); break; // #i62300# + case 0x0236: TableOp(); break; // TABLE [ 5] + case EXC_ID5_BOF: // BOF [ 5] + XclTools::SkipSubStream( maStrm ); + break; + } + + } + break; + + case Z_Biff5E: // --------------------------------- Z_Biff5E - + { + switch( nOpcode ) + { + case EXC_ID5_BOF: // BOF [ 5] + Bof5(); + NewTable(); + switch( pExcRoot->eDateiTyp ) + { + case Biff5: + case Biff5M4: + eCurrent = Z_Biff5TPre; // Shrfmla Prefetch, Row-Prefetch + nBofLevel = 0; + aIn.StoreGlobalPosition(); // store position + break; + case Biff5C: // chart sheet + GetCurrSheetDrawing().ReadTabChart( maStrm ); + Eof(); + GetTracer().TraceChartOnlySheet(); + break; + case Biff5V: + default: + rD.SetVisible( GetCurrScTab(), false ); + ePrev = eCurrent; + eCurrent = Z_Biffn0; + } + OSL_ENSURE( pExcRoot->eDateiTyp != Biff5W, + "+ImportExcel::Read(): Doppel-Whopper-Workbook!" ); + + break; + } + + } + break; + case Z_Biffn0: // --------------------------------- Z_Biffn0 - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + eCurrent = ePrev; + IncCurrScTab(); + break; + } + + } + break; + + case Z_End: // ----------------------------------- Z_End - + OSL_FAIL( "*ImportExcel::Read(): Not possible state!" ); + break; + default: OSL_FAIL( "-ImportExcel::Read(): state forgotten!" ); + } + } + + if( eLastErr == ERRCODE_NONE ) + { + pProgress.reset(); + + GetDocImport().finalize(); + if (!utl::ConfigManager::IsFuzzing()) + AdjustRowHeight(); + PostDocLoad(); + + rD.CalcAfterLoad(false); + + const XclImpAddressConverter& rAddrConv = GetAddressConverter(); + if( rAddrConv.IsTabTruncated() ) + eLastErr = SCWARN_IMPORT_SHEET_OVERFLOW; + else if( bTabTruncated || rAddrConv.IsRowTruncated() ) + eLastErr = SCWARN_IMPORT_ROW_OVERFLOW; + else if( rAddrConv.IsColTruncated() ) + eLastErr = SCWARN_IMPORT_COLUMN_OVERFLOW; + } + + return eLastErr; +} + +ErrCode ImportExcel8::Read() +{ +#ifdef EXC_INCL_DUMPER + { + Biff8RecDumper aDumper( GetRoot(), sal_True ); + if( aDumper.Dump( aIn ) ) + return ERRCODE_ABORT; + } +#endif + // read the entire BIFF8 stream + // don't look too close - this stuff seriously needs to be reworked + + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + XclImpPalette& rPal = GetPalette(); + XclImpFontBuffer& rFontBfr = GetFontBuffer(); + XclImpNumFmtBuffer& rNumFmtBfr = GetNumFmtBuffer(); + XclImpXFBuffer& rXFBfr = GetXFBuffer(); + XclImpSst& rSst = GetSst(); + XclImpTabInfo& rTabInfo = GetTabInfo(); + XclImpNameManager& rNameMgr = GetNameManager(); + XclImpLinkManager& rLinkMgr = GetLinkManager(); + XclImpObjectManager& rObjMgr = GetObjectManager(); + // call to GetCurrSheetDrawing() cannot be cached (changes in new sheets) + XclImpCondFormatManager& rCondFmtMgr = GetCondFormatManager(); + XclImpValidationManager& rValidMgr = GetValidationManager(); + XclImpPivotTableManager& rPTableMgr = GetPivotTableManager(); + XclImpWebQueryBuffer& rWQBfr = GetWebQueryBuffer(); + + bool bInUserView = false; // true = In USERSVIEW(BEGIN|END) record block. + + enum XclImpReadState + { + EXC_STATE_BEFORE_GLOBALS, /// Before workbook globals (wait for initial BOF). + EXC_STATE_GLOBALS_PRE, /// Prefetch for workbook globals. + EXC_STATE_GLOBALS, /// Workbook globals. + EXC_STATE_BEFORE_SHEET, /// Before worksheet (wait for new worksheet BOF). + EXC_STATE_SHEET_PRE, /// Prefetch for worksheet. + EXC_STATE_SHEET, /// Worksheet. + EXC_STATE_END /// Stop reading. + }; + + XclImpReadState eCurrent = EXC_STATE_BEFORE_GLOBALS; + + ErrCode eLastErr = ERRCODE_NONE; + + std::unique_ptr< ScfSimpleProgressBar > pProgress( new ScfSimpleProgressBar( + aIn.GetSvStreamSize(), GetDocShell(), STR_LOAD_DOC ) ); + + /* #i104057# Need to track a base position for progress bar calculation, + because sheet substreams may not be in order of sheets. */ + std::size_t nProgressBasePos = 0; + std::size_t nProgressBaseSize = 0; + + bool bSheetHasCodeName = false; + + std::vector<OUString> aCodeNames; + std::vector < SCTAB > nTabsWithNoCodeName; + + sal_uInt16 nRecId = 0; + + for (; eCurrent != EXC_STATE_END; mnLastRecId = nRecId) + { + if( eCurrent == EXC_STATE_BEFORE_SHEET ) + { + sal_uInt16 nScTab = GetCurrScTab(); + if( nScTab < maSheetOffsets.size() ) + { + nProgressBaseSize += (maStrm.GetSvStreamPos() - nProgressBasePos); + nProgressBasePos = maSheetOffsets[ nScTab ]; + + bool bValid = TryStartNextRecord(aIn, nProgressBasePos); + if (!bValid) + { + // Safeguard ourselves from potential infinite loop. + eCurrent = EXC_STATE_END; + } + + // import only 256 sheets + if( nScTab > GetScMaxPos().Tab() ) + { + if( maStrm.GetRecId() != EXC_ID_EOF ) + XclTools::SkipSubStream( maStrm ); + // #i29930# show warning box + GetAddressConverter().CheckScTab( nScTab ); + eCurrent = EXC_STATE_END; + } + else + { + // #i109800# SHEET record may point to any record inside the + // sheet substream + bool bIsBof = maStrm.GetRecId() == EXC_ID5_BOF; + if( bIsBof ) + Bof5(); // read the BOF record + else + pExcRoot->eDateiTyp = Biff8; // on missing BOF, assume a standard worksheet + NewTable(); + switch( pExcRoot->eDateiTyp ) + { + case Biff8: // worksheet + case Biff8M4: // macro sheet + eCurrent = EXC_STATE_SHEET_PRE; // Shrfmla Prefetch, Row-Prefetch + // go to next record + if( bIsBof ) maStrm.StartNextRecord(); + maStrm.StoreGlobalPosition(); + break; + case Biff8C: // chart sheet + GetCurrSheetDrawing().ReadTabChart( maStrm ); + Eof(); + GetTracer().TraceChartOnlySheet(); + break; + case Biff8W: // workbook + OSL_FAIL( "ImportExcel8::Read - double workbook globals" ); + [[fallthrough]]; + case Biff8V: // VB module + default: + // TODO: do not create a sheet in the Calc document + rD.SetVisible( nScTab, false ); + XclTools::SkipSubStream( maStrm ); + IncCurrScTab(); + } + } + } + else + eCurrent = EXC_STATE_END; + } + else + aIn.StartNextRecord(); + + if( !aIn.IsValid() ) + { + // #i63591# finalize table if EOF is missing + switch( eCurrent ) + { + case EXC_STATE_SHEET_PRE: + eCurrent = EXC_STATE_SHEET; + aIn.SeekGlobalPosition(); + continue; // next iteration in while loop + case EXC_STATE_SHEET: + Eof(); + eCurrent = EXC_STATE_END; + break; + default: + eCurrent = EXC_STATE_END; + } + } + + if( eCurrent == EXC_STATE_END ) + break; + + if( eCurrent != EXC_STATE_SHEET_PRE && eCurrent != EXC_STATE_GLOBALS_PRE ) + pProgress->ProgressAbs( nProgressBaseSize + aIn.GetSvStreamPos() - nProgressBasePos ); + + nRecId = aIn.GetRecId(); + + /* #i39464# Ignore records between USERSVIEWBEGIN and USERSVIEWEND + completely (user specific view settings). Otherwise view settings + and filters are loaded multiple times, which at least causes + problems in auto-filters. */ + switch( nRecId ) + { + case EXC_ID_USERSVIEWBEGIN: + OSL_ENSURE( !bInUserView, "ImportExcel8::Read - nested user view settings" ); + bInUserView = true; + break; + case EXC_ID_USERSVIEWEND: + OSL_ENSURE( bInUserView, "ImportExcel8::Read - not in user view settings" ); + bInUserView = false; + break; + } + + if( !bInUserView ) switch( eCurrent ) + { + + // before workbook globals: wait for initial workbook globals BOF + case EXC_STATE_BEFORE_GLOBALS: + { + if( nRecId == EXC_ID5_BOF ) + { + OSL_ENSURE( GetBiff() == EXC_BIFF8, "ImportExcel8::Read - wrong BIFF version" ); + Bof5(); + if( pExcRoot->eDateiTyp == Biff8W ) + { + eCurrent = EXC_STATE_GLOBALS_PRE; + maStrm.StoreGlobalPosition(); + nBdshtTab = 0; + } + else if( pExcRoot->eDateiTyp == Biff8 ) + { + // #i62752# possible to have BIFF8 sheet without globals + NewTable(); + eCurrent = EXC_STATE_SHEET_PRE; // Shrfmla Prefetch, Row-Prefetch + bSheetHasCodeName = false; // reset + aIn.StoreGlobalPosition(); + } + } + } + break; + + // prefetch for workbook globals + case EXC_STATE_GLOBALS_PRE: + { + switch( nRecId ) + { + case EXC_ID_EOF: + case EXC_ID_EXTSST: + /* #i56376# evil hack: if EOF for globals is missing, + simulate it. This hack works only for the bugdoc + given in the issue, where the sheet substreams + start directly after the EXTSST record. A future + implementation should be more robust against + missing EOFs. */ + if( (nRecId == EXC_ID_EOF) || + ((nRecId == EXC_ID_EXTSST) && (maStrm.GetNextRecId() == EXC_ID5_BOF)) ) + { + eCurrent = EXC_STATE_GLOBALS; + aIn.SeekGlobalPosition(); + } + break; + case 0x12: DocProtect(); break; // PROTECT [ 5678] + case 0x13: DocPassword(); break; + case 0x19: WinProtection(); break; + case 0x2F: // FILEPASS [ 2345 ] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = EXC_STATE_END; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x3D: Window1(); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345 ] + case 0x85: Boundsheet(); break; // BOUNDSHEET [ 5 ] + case 0x8C: Country(); break; // COUNTRY [ 345 ] + + // PALETTE follows XFs, but already needed while reading the XFs + case EXC_ID_PALETTE: rPal.ReadPalette( maStrm ); break; + } + } + break; + + // workbook globals + case EXC_STATE_GLOBALS: + { + switch( nRecId ) + { + case EXC_ID_EOF: + case EXC_ID_EXTSST: + /* #i56376# evil hack: if EOF for globals is missing, + simulate it. This hack works only for the bugdoc + given in the issue, where the sheet substreams + start directly after the EXTSST record. A future + implementation should be more robust against + missing EOFs. */ + if( (nRecId == EXC_ID_EOF) || + ((nRecId == EXC_ID_EXTSST) && (maStrm.GetNextRecId() == EXC_ID5_BOF)) ) + { + rNumFmtBfr.CreateScFormats(); + rXFBfr.CreateUserStyles(); + rPTableMgr.ReadPivotCaches( maStrm ); + rNameMgr.ConvertAllTokens(); + eCurrent = EXC_STATE_BEFORE_SHEET; + } + break; + case 0x0E: Precision(); break; // PRECISION + case 0x22: Rec1904(); break; // 1904 [ 2345 ] + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x8D: Hideobj(); break; // HIDEOBJ [ 345 ] + case 0xD3: SetHasBasic(); break; + case 0xDE: Olesize(); break; + + case EXC_ID_CODENAME: ReadCodeName( aIn, true ); break; + case EXC_ID_USESELFS: ReadUsesElfs(); break; + + case EXC_ID2_FONT: rFontBfr.ReadFont( maStrm ); break; + case EXC_ID4_FORMAT: rNumFmtBfr.ReadFormat( maStrm ); break; + case EXC_ID5_XF: rXFBfr.ReadXF( maStrm ); break; + case EXC_ID_STYLE: rXFBfr.ReadStyle( maStrm ); break; + + case EXC_ID_SST: rSst.ReadSst( maStrm ); break; + case EXC_ID_TABID: rTabInfo.ReadTabid( maStrm ); break; + case EXC_ID_NAME: rNameMgr.ReadName( maStrm ); break; + + case EXC_ID_EXTERNSHEET: rLinkMgr.ReadExternsheet( maStrm ); break; + case EXC_ID_SUPBOOK: rLinkMgr.ReadSupbook( maStrm ); break; + case EXC_ID_XCT: rLinkMgr.ReadXct( maStrm ); break; + case EXC_ID_CRN: rLinkMgr.ReadCrn( maStrm ); break; + case EXC_ID_EXTERNNAME: rLinkMgr.ReadExternname( maStrm, pFormConv.get() ); break; + + case EXC_ID_MSODRAWINGGROUP:rObjMgr.ReadMsoDrawingGroup( maStrm ); break; + + case EXC_ID_SXIDSTM: rPTableMgr.ReadSxidstm( maStrm ); break; + case EXC_ID_SXVS: rPTableMgr.ReadSxvs( maStrm ); break; + case EXC_ID_DCONREF: rPTableMgr.ReadDconref( maStrm ); break; + case EXC_ID_DCONNAME: rPTableMgr.ReadDConName( maStrm ); break; + } + + } + break; + + // prefetch for worksheet + case EXC_STATE_SHEET_PRE: + { + switch( nRecId ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID_WINDOW2: rTabViewSett.ReadWindow2( maStrm, false );break; + case EXC_ID_SCL: rTabViewSett.ReadScl( maStrm ); break; + case EXC_ID_PANE: rTabViewSett.ReadPane( maStrm ); break; + case EXC_ID_SELECTION: rTabViewSett.ReadSelection( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + + case EXC_ID_CODENAME: ReadCodeName( aIn, false ); bSheetHasCodeName = true; break; + + case 0x0A: // EOF [ 2345 ] + { + eCurrent = EXC_STATE_SHEET; + OUString sName; + GetDoc().GetName( GetCurrScTab(), sName ); + if ( !bSheetHasCodeName ) + { + nTabsWithNoCodeName.push_back( GetCurrScTab() ); + } + else + { + OUString sCodeName; + GetDoc().GetCodeName( GetCurrScTab(), sCodeName ); + aCodeNames.push_back( sCodeName ); + } + + bSheetHasCodeName = false; // reset + + aIn.SeekGlobalPosition(); // and back to old position + break; + } + case 0x12: SheetProtect(); break; + case 0x13: SheetPassword(); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345 ] + case 0x55: DefColWidth(); break; + case 0x7D: Colinfo(); break; // COLINFO [ 345 ] + case 0x81: Wsbool(); break; // WSBOOL [ 2345 ] + case 0x8C: Country(); break; // COUNTRY [ 345 ] + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45 ] + case 0x9B: FilterMode(); break; // FILTERMODE + case EXC_ID_AUTOFILTERINFO: AutoFilterInfo(); break;// AUTOFILTERINFO + case EXC_ID_AUTOFILTER: AutoFilter(); break; // AUTOFILTER + case 0x0208: Row34(); break; // ROW [ 34 ] + case EXC_ID2_ARRAY: + case EXC_ID3_ARRAY: Array34(); break; // ARRAY [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345 ] + case 0x0867: FeatHdr(); break; // FEATHDR + case 0x0868: Feat(); break; // FEAT + } + } + break; + + // worksheet + case EXC_STATE_SHEET: + { + switch( nRecId ) + { + // skip unknown substreams + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID_EOF: Eof(); eCurrent = EXC_STATE_BEFORE_SHEET; break; + + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case EXC_ID2_FORMULA: + case EXC_ID3_FORMULA: + case EXC_ID4_FORMULA: Formula25(); break; + case EXC_ID_SHRFMLA: Shrfmla(); break; + case 0x000C: Calccount(); break; // CALCCOUNT + case 0x0010: Delta(); break; // DELTA + case 0x0011: Iteration(); break; // ITERATION + case 0x007E: + case 0x00AE: Scenman(); break; // SCENMAN + case 0x00AF: Scenario(); break; // SCENARIO + case 0x00BD: Mulrk(); break; // MULRK [ 5 ] + case 0x00BE: Mulblank(); break; // MULBLANK [ 5 ] + case 0x00D6: Rstring(); break; // RSTRING [ 5 ] + case 0x00E5: Cellmerging(); break; // CELLMERGING + case 0x00FD: Labelsst(); break; // LABELSST [ 8 ] + case 0x0236: TableOp(); break; // TABLE + + case EXC_ID_HORPAGEBREAKS: + case EXC_ID_VERPAGEBREAKS: rPageSett.ReadPageBreaks( maStrm ); break; + case EXC_ID_HEADER: + case EXC_ID_FOOTER: rPageSett.ReadHeaderFooter( maStrm ); break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: rPageSett.ReadMargin( maStrm ); break; + case EXC_ID_PRINTHEADERS: rPageSett.ReadPrintHeaders( maStrm ); break; + case EXC_ID_PRINTGRIDLINES: rPageSett.ReadPrintGridLines( maStrm ); break; + case EXC_ID_HCENTER: + case EXC_ID_VCENTER: rPageSett.ReadCenter( maStrm ); break; + case EXC_ID_SETUP: rPageSett.ReadSetup( maStrm ); break; + case EXC_ID8_IMGDATA: rPageSett.ReadImgData( maStrm ); break; + + case EXC_ID_MSODRAWING: GetCurrSheetDrawing().ReadMsoDrawing( maStrm ); break; + // #i61786# weird documents: OBJ without MSODRAWING -> read in BIFF5 format + case EXC_ID_OBJ: GetCurrSheetDrawing().ReadObj( maStrm ); break; + case EXC_ID_NOTE: GetCurrSheetDrawing().ReadNote( maStrm ); break; + + case EXC_ID_HLINK: XclImpHyperlink::ReadHlink( maStrm ); break; + case EXC_ID_LABELRANGES: XclImpLabelranges::ReadLabelranges( maStrm ); break; + + case EXC_ID_CONDFMT: rCondFmtMgr.ReadCondfmt( maStrm ); break; + case EXC_ID_CF: rCondFmtMgr.ReadCF( maStrm ); break; + + case EXC_ID_DVAL: XclImpValidationManager::ReadDval( maStrm ); break; + case EXC_ID_DV: rValidMgr.ReadDV( maStrm ); break; + + case EXC_ID_QSI: rWQBfr.ReadQsi( maStrm ); break; + case EXC_ID_WQSTRING: rWQBfr.ReadWqstring( maStrm ); break; + case EXC_ID_PQRY: rWQBfr.ReadParamqry( maStrm ); break; + case EXC_ID_WQSETT: rWQBfr.ReadWqsettings( maStrm ); break; + case EXC_ID_WQTABLES: rWQBfr.ReadWqtables( maStrm ); break; + + case EXC_ID_SXVIEW: rPTableMgr.ReadSxview( maStrm ); break; + case EXC_ID_SXVD: rPTableMgr.ReadSxvd( maStrm ); break; + case EXC_ID_SXVI: rPTableMgr.ReadSxvi( maStrm ); break; + case EXC_ID_SXIVD: rPTableMgr.ReadSxivd( maStrm ); break; + case EXC_ID_SXPI: rPTableMgr.ReadSxpi( maStrm ); break; + case EXC_ID_SXDI: rPTableMgr.ReadSxdi( maStrm ); break; + case EXC_ID_SXVDEX: rPTableMgr.ReadSxvdex( maStrm ); break; + case EXC_ID_SXEX: rPTableMgr.ReadSxex( maStrm ); break; + case EXC_ID_SHEETEXT: rTabViewSett.ReadTabBgColor( maStrm, rPal ); break; + case EXC_ID_SXVIEWEX9: rPTableMgr.ReadSxViewEx9( maStrm ); break; + case EXC_ID_SXADDL: rPTableMgr.ReadSxAddl( maStrm ); break; + } + } + break; + + default:; + } + } + + if( eLastErr == ERRCODE_NONE ) + { + // In some strange circumstances the codename might be missing + // # Create any missing Sheet CodeNames + for ( const auto& rTab : nTabsWithNoCodeName ) + { + SCTAB nTab = 1; + while ( true ) + { + OUStringBuffer aBuf; + aBuf.append("Sheet"); + aBuf.append(static_cast<sal_Int32>(nTab++)); + OUString sTmpName = aBuf.makeStringAndClear(); + + if ( std::find(aCodeNames.begin(), aCodeNames.end(), sTmpName) == aCodeNames.end() ) // generated codename not found + { + // Set new codename + GetDoc().SetCodeName( rTab, sTmpName ); + // Record newly used codename + aCodeNames.push_back(sTmpName); + break; + } + } + } + // #i45843# Convert pivot tables before calculation, so they are available + // for the GETPIVOTDATA function. + if( GetBiff() == EXC_BIFF8 ) + GetPivotTableManager().ConvertPivotTables(); + + ScDocumentImport& rDoc = GetDocImport(); + rDoc.finalize(); + pProgress.reset(); +#if 0 + // Excel documents look much better without this call; better in the + // sense that the row heights are identical to the original heights in + // Excel. + if ( !rD.IsAdjustHeightLocked()) + AdjustRowHeight(); +#endif + PostDocLoad(); + + rD.CalcAfterLoad(false); + + // import change tracking data + XclImpChangeTrack aImpChTr( GetRoot(), maStrm ); + aImpChTr.Apply(); + + const XclImpAddressConverter& rAddrConv = GetAddressConverter(); + if( rAddrConv.IsTabTruncated() ) + eLastErr = SCWARN_IMPORT_SHEET_OVERFLOW; + else if( bTabTruncated || rAddrConv.IsRowTruncated() ) + eLastErr = SCWARN_IMPORT_ROW_OVERFLOW; + else if( rAddrConv.IsColTruncated() ) + eLastErr = SCWARN_IMPORT_COLUMN_OVERFLOW; + + if( GetBiff() == EXC_BIFF8 ) + GetPivotTableManager().MaybeRefreshPivotTables(); + } + + return eLastErr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/tokstack.cxx b/sc/source/filter/excel/tokstack.cxx new file mode 100644 index 000000000..a0e3974ca --- /dev/null +++ b/sc/source/filter/excel/tokstack.cxx @@ -0,0 +1,752 @@ +/* -*- 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 <tokstack.hxx> +#include <scmatrix.hxx> + +#include <svl/sharedstringpool.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <algorithm> +#include <string.h> + +const sal_uInt16 TokenPool::nScTokenOff = 8192; + +TokenStack::TokenStack( ) + : pStack( new TokenId[ nSize ] ) +{ + Reset(); +} + +TokenStack::~TokenStack() +{ +} + +// !ATTENTION!": to the outside the numbering starts with 1! +// !ATTENTION!": SC-Token are stored with an offset nScTokenOff +// -> to distinguish from other tokens + +TokenPool::TokenPool( svl::SharedStringPool& rSPool ) : + mrStringPool(rSPool) +{ + // pool for Id-sequences + nP_Id = 256; + pP_Id.reset( new sal_uInt16[ nP_Id ] ); + + // pool for Ids + nElement = 32; + pElement.reset( new sal_uInt16[ nElement ] ); + pType.reset( new E_TYPE[ nElement ] ); + pSize.reset( new sal_uInt16[ nElement ] ); + nP_IdLast = 0; + + nP_Matrix = 16; + ppP_Matrix.reset( new ScMatrix*[ nP_Matrix ] ); + memset( ppP_Matrix.get(), 0, sizeof( ScMatrix* ) * nP_Matrix ); + + Reset(); +} + +TokenPool::~TokenPool() +{ + ClearMatrix(); +} + +/** Returns the new number of elements, or 0 if overflow. */ +static sal_uInt16 lcl_canGrow( sal_uInt16 nOld ) +{ + if (!nOld) + return 1; + if (nOld == SAL_MAX_UINT16) + return 0; + sal_uInt32 nNew = ::std::max( static_cast<sal_uInt32>(nOld) * 2, + static_cast<sal_uInt32>(nOld) + 1); + if (nNew > SAL_MAX_UINT16) + nNew = SAL_MAX_UINT16; + if (nNew - 1 < nOld) + nNew = 0; + return static_cast<sal_uInt16>(nNew); +} + +bool TokenPool::GrowId() +{ + sal_uInt16 nP_IdNew = lcl_canGrow( nP_Id); + if (!nP_IdNew) + return false; + + sal_uInt16* pP_IdNew = new (::std::nothrow) sal_uInt16[ nP_IdNew ]; + if (!pP_IdNew) + return false; + + for( sal_uInt16 nL = 0 ; nL < nP_Id ; nL++ ) + pP_IdNew[ nL ] = pP_Id[ nL ]; + + nP_Id = nP_IdNew; + + pP_Id.reset( pP_IdNew ); + return true; +} + +bool TokenPool::CheckElementOrGrow() +{ + // Last possible ID to be assigned somewhere is nElementCurrent+1 + if (nElementCurrent + 1 == nScTokenOff - 1) + { + SAL_WARN("sc.filter","TokenPool::CheckElementOrGrow - last possible ID " << nElementCurrent+1); + return false; + } + + if (nElementCurrent >= nElement) + return GrowElement(); + + return true; +} + +bool TokenPool::GrowElement() +{ + sal_uInt16 nElementNew = lcl_canGrow( nElement); + if (!nElementNew) + return false; + + std::unique_ptr<sal_uInt16[]> pElementNew(new (::std::nothrow) sal_uInt16[ nElementNew ]); + std::unique_ptr<E_TYPE[]> pTypeNew(new (::std::nothrow) E_TYPE[ nElementNew ]); + std::unique_ptr<sal_uInt16[]> pSizeNew(new (::std::nothrow) sal_uInt16[ nElementNew ]); + if (!pElementNew || !pTypeNew || !pSizeNew) + { + return false; + } + + for( sal_uInt16 nL = 0 ; nL < nElement ; nL++ ) + { + pElementNew[ nL ] = pElement[ nL ]; + pTypeNew[ nL ] = pType[ nL ]; + pSizeNew[ nL ] = pSize[ nL ]; + } + + nElement = nElementNew; + + pElement = std::move( pElementNew ); + pType = std::move( pTypeNew ); + pSize = std::move( pSizeNew ); + return true; +} + +bool TokenPool::GrowMatrix() +{ + sal_uInt16 nNewSize = lcl_canGrow( nP_Matrix); + if (!nNewSize) + return false; + + ScMatrix** ppNew = new (::std::nothrow) ScMatrix*[ nNewSize ]; + if (!ppNew) + return false; + + memset( ppNew, 0, sizeof( ScMatrix* ) * nNewSize ); + for( sal_uInt16 nL = 0 ; nL < nP_Matrix ; nL++ ) + ppNew[ nL ] = ppP_Matrix[ nL ]; + + ppP_Matrix.reset( ppNew ); + nP_Matrix = nNewSize; + return true; +} + +bool TokenPool::GetElement( const sal_uInt16 nId, ScTokenArray* pScToken ) +{ + if (nId >= nElementCurrent) + { + SAL_WARN("sc.filter","TokenPool::GetElement - Id too large, " << nId << " >= " << nElementCurrent); + return false; + } + + bool bRet = true; + if( pType[ nId ] == T_Id ) + bRet = GetElementRek( nId, pScToken ); + else + { + switch( pType[ nId ] ) + { + case T_Str: + { + sal_uInt16 n = pElement[ nId ]; + auto* p = ppP_Str.getIfInRange( n ); + if (p) + pScToken->AddString(mrStringPool.intern(**p)); + else + bRet = false; + } + break; + case T_D: + { + sal_uInt16 n = pElement[ nId ]; + if (n < pP_Dbl.m_writemark) + pScToken->AddDouble( pP_Dbl[ n ] ); + else + bRet = false; + } + break; + case T_Err: + break; +/* TODO: in case we had FormulaTokenArray::AddError() */ +#if 0 + { + sal_uInt16 n = pElement[ nId ]; + if (n < nP_Err) + pScToken->AddError( pP_Err[ n ] ); + else + bRet = false; + } +#endif + case T_RefC: + { + sal_uInt16 n = pElement[ nId ]; + auto p = ppP_RefTr.getIfInRange(n); + if (p) + pScToken->AddSingleReference( **p ); + else + bRet = false; + } + break; + case T_RefA: + { + sal_uInt16 n = pElement[ nId ]; + if (n < ppP_RefTr.m_writemark && ppP_RefTr[ n ] && n+1 < ppP_RefTr.m_writemark && ppP_RefTr[ n + 1 ]) + { + ScComplexRefData aScComplexRefData; + aScComplexRefData.Ref1 = *ppP_RefTr[ n ]; + aScComplexRefData.Ref2 = *ppP_RefTr[ n + 1 ]; + pScToken->AddDoubleReference( aScComplexRefData ); + } + else + bRet = false; + } + break; + case T_RN: + { + sal_uInt16 n = pElement[nId]; + if (n < maRangeNames.size()) + { + const RangeName& r = maRangeNames[n]; + pScToken->AddRangeName(r.mnIndex, r.mnSheet); + } + } + break; + case T_Ext: + { + sal_uInt16 n = pElement[ nId ]; + auto p = ppP_Ext.getIfInRange(n); + + if( p ) + { + if( (*p)->eId == ocEuroConvert ) + pScToken->AddOpCode( (*p)->eId ); + else + pScToken->AddExternal( (*p)->aText, (*p)->eId ); + } + else + bRet = false; + } + break; + case T_Nlf: + { + sal_uInt16 n = pElement[ nId ]; + auto p = ppP_Nlf.getIfInRange(n); + + if( p ) + pScToken->AddColRowName( **p ); + else + bRet = false; + } + break; + case T_Matrix: + { + sal_uInt16 n = pElement[ nId ]; + ScMatrix* p = ( n < nP_Matrix )? ppP_Matrix[ n ] : nullptr; + + if( p ) + pScToken->AddMatrix( p ); + else + bRet = false; + } + break; + case T_ExtName: + { + sal_uInt16 n = pElement[nId]; + if (n < maExtNames.size()) + { + const ExtName& r = maExtNames[n]; + pScToken->AddExternalName(r.mnFileId, mrStringPool.intern( r.maName)); + } + else + bRet = false; + } + break; + case T_ExtRefC: + { + sal_uInt16 n = pElement[nId]; + if (n < maExtCellRefs.size()) + { + const ExtCellRef& r = maExtCellRefs[n]; + pScToken->AddExternalSingleReference(r.mnFileId, mrStringPool.intern( r.maTabName), r.maRef); + } + else + bRet = false; + } + break; + case T_ExtRefA: + { + sal_uInt16 n = pElement[nId]; + if (n < maExtAreaRefs.size()) + { + const ExtAreaRef& r = maExtAreaRefs[n]; + pScToken->AddExternalDoubleReference(r.mnFileId, mrStringPool.intern( r.maTabName), r.maRef); + } + else + bRet = false; + } + break; + default: + OSL_FAIL("-TokenPool::GetElement(): undefined state!?"); + bRet = false; + } + } + return bRet; +} + +bool TokenPool::GetElementRek( const sal_uInt16 nId, ScTokenArray* pScToken ) +{ +#ifdef DBG_UTIL + m_nRek++; + OSL_ENSURE(m_nRek <= nP_Id, "*TokenPool::GetElement(): recursion loops!?"); +#endif + + OSL_ENSURE( nId < nElementCurrent, "*TokenPool::GetElementRek(): nId >= nElementCurrent" ); + + if (nId >= nElementCurrent) + { + SAL_WARN("sc.filter", "*TokenPool::GetElementRek(): nId >= nElementCurrent"); +#ifdef DBG_UTIL + m_nRek--; +#endif + return false; + } + + if (pType[ nId ] != T_Id) + { + SAL_WARN("sc.filter", "-TokenPool::GetElementRek(): pType[ nId ] != T_Id"); +#ifdef DBG_UTIL + m_nRek--; +#endif + return false; + } + + bool bRet = true; + sal_uInt16 nCnt = pSize[ nId ]; + sal_uInt16 nFirstId = pElement[ nId ]; + if (nFirstId >= nP_Id) + { + SAL_WARN("sc.filter", "TokenPool::GetElementRek: nFirstId >= nP_Id"); + nCnt = 0; + bRet = false; + } + sal_uInt16* pCurrent = nCnt ? &pP_Id[ nFirstId ] : nullptr; + if (nCnt > nP_Id - nFirstId) + { + SAL_WARN("sc.filter", "TokenPool::GetElementRek: nCnt > nP_Id - nFirstId"); + nCnt = nP_Id - nFirstId; + bRet = false; + } + for( ; nCnt > 0 ; nCnt--, pCurrent++ ) + { + assert(pCurrent); + if( *pCurrent < nScTokenOff ) + {// recursion or not? + if (*pCurrent >= nElementCurrent) + { + SAL_WARN("sc.filter", "TokenPool::GetElementRek: *pCurrent >= nElementCurrent"); + bRet = false; + } + else + { + if (pType[ *pCurrent ] == T_Id) + bRet = GetElementRek( *pCurrent, pScToken ); + else + bRet = GetElement( *pCurrent, pScToken ); + } + } + else // elementary SC_Token + pScToken->AddOpCode( static_cast<DefTokenId>( *pCurrent - nScTokenOff ) ); + } + +#ifdef DBG_UTIL + m_nRek--; +#endif + return bRet; +} + +void TokenPool::operator >>( TokenId& rId ) +{ + rId = static_cast<TokenId>( nElementCurrent + 1 ); + + if (!CheckElementOrGrow()) + return; + + pElement[ nElementCurrent ] = nP_IdLast; // Start of Token-sequence + pType[ nElementCurrent ] = T_Id; // set Typeinfo + pSize[ nElementCurrent ] = nP_IdCurrent - nP_IdLast; + // write from nP_IdLast to nP_IdCurrent-1 -> length of the sequence + + nElementCurrent++; // start value for next sequence + nP_IdLast = nP_IdCurrent; +} + +TokenId TokenPool::Store( const double& rDouble ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( pP_Dbl.m_writemark >= pP_Dbl.m_capacity ) + if (!pP_Dbl.Grow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = pP_Dbl.m_writemark; // Index in Double-Array + pType[ nElementCurrent ] = T_D; // set Typeinfo Double + + pP_Dbl[ pP_Dbl.m_writemark ] = rDouble; + + pSize[ nElementCurrent ] = 1; // does not matter + + nElementCurrent++; + pP_Dbl.m_writemark++; + + return static_cast<const TokenId>(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const sal_uInt16 nIndex ) +{ + return StoreName(nIndex, -1); +} + +TokenId TokenPool::Store( const OUString& rString ) +{ + // mostly copied to Store( const char* ), to avoid a temporary string + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( ppP_Str.m_writemark >= ppP_Str.m_capacity ) + if (!ppP_Str.Grow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_Str.m_writemark; // Index in String-Array + pType[ nElementCurrent ] = T_Str; // set Typeinfo String + + // create String + if( !ppP_Str[ ppP_Str.m_writemark ] ) + //...but only, if it does not exist already + ppP_Str[ ppP_Str.m_writemark ].reset( new OUString( rString ) ); + else + //...copy otherwise + *ppP_Str[ ppP_Str.m_writemark ] = rString; + + /* attention truncate to 16 bits */ + pSize[ nElementCurrent ] = static_cast<sal_uInt16>(ppP_Str[ ppP_Str.m_writemark ]->getLength()); + + nElementCurrent++; + ppP_Str.m_writemark++; + + return static_cast<const TokenId>(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const ScSingleRefData& rTr ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( ppP_RefTr.m_writemark >= ppP_RefTr.m_capacity ) + if (!ppP_RefTr.Grow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_RefTr.m_writemark; + pType[ nElementCurrent ] = T_RefC; // set Typeinfo Cell-Ref + + if( !ppP_RefTr[ ppP_RefTr.m_writemark ] ) + ppP_RefTr[ ppP_RefTr.m_writemark ].reset( new ScSingleRefData( rTr ) ); + else + *ppP_RefTr[ ppP_RefTr.m_writemark ] = rTr; + + nElementCurrent++; + ppP_RefTr.m_writemark++; + + return static_cast<const TokenId>(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const ScComplexRefData& rTr ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( ppP_RefTr.m_writemark + 1 >= ppP_RefTr.m_capacity ) + if (!ppP_RefTr.Grow(2)) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_RefTr.m_writemark; + pType[ nElementCurrent ] = T_RefA; // setTypeinfo Area-Ref + + if( !ppP_RefTr[ ppP_RefTr.m_writemark ] ) + ppP_RefTr[ ppP_RefTr.m_writemark ].reset( new ScSingleRefData( rTr.Ref1 ) ); + else + *ppP_RefTr[ ppP_RefTr.m_writemark ] = rTr.Ref1; + ppP_RefTr.m_writemark++; + + if( !ppP_RefTr[ ppP_RefTr.m_writemark ] ) + ppP_RefTr[ ppP_RefTr.m_writemark ].reset( new ScSingleRefData( rTr.Ref2 ) ); + else + *ppP_RefTr[ ppP_RefTr.m_writemark ] = rTr.Ref2; + ppP_RefTr.m_writemark++; + + nElementCurrent++; + + return static_cast<const TokenId>(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const DefTokenId e, const OUString& r ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( ppP_Ext.m_writemark >= ppP_Ext.m_capacity ) + if (!ppP_Ext.Grow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_Ext.m_writemark; + pType[ nElementCurrent ] = T_Ext; // set Typeinfo String + + if( ppP_Ext[ ppP_Ext.m_writemark ] ) + { + ppP_Ext[ ppP_Ext.m_writemark ]->eId = e; + ppP_Ext[ ppP_Ext.m_writemark ]->aText = r; + } + else + ppP_Ext[ ppP_Ext.m_writemark ].reset( new EXTCONT( e, r ) ); + + nElementCurrent++; + ppP_Ext.m_writemark++; + + return static_cast<const TokenId>(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::StoreNlf( const ScSingleRefData& rTr ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( ppP_Nlf.m_writemark >= ppP_Nlf.m_capacity ) + if (!ppP_Nlf.Grow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_Nlf.m_writemark; + pType[ nElementCurrent ] = T_Nlf; + + if( ppP_Nlf[ ppP_Nlf.m_writemark ] ) + { + *ppP_Nlf[ ppP_Nlf.m_writemark ] = rTr; + } + else + ppP_Nlf[ ppP_Nlf.m_writemark ].reset( new ScSingleRefData( rTr ) ); + + nElementCurrent++; + ppP_Nlf.m_writemark++; + + return static_cast<const TokenId>(nElementCurrent); +} + +TokenId TokenPool::StoreMatrix() +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + if( nP_MatrixCurrent >= nP_Matrix ) + if (!GrowMatrix()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[ nElementCurrent ] = nP_MatrixCurrent; + pType[ nElementCurrent ] = T_Matrix; + + ScMatrix* pM = new ScMatrix( 0, 0 ); + pM->IncRef( ); + ppP_Matrix[ nP_MatrixCurrent ] = pM; + + nElementCurrent++; + nP_MatrixCurrent++; + + return static_cast<const TokenId>(nElementCurrent); +} + +TokenId TokenPool::StoreName( sal_uInt16 nIndex, sal_Int16 nSheet ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast<sal_uInt16>(maRangeNames.size()); + pType[nElementCurrent] = T_RN; + + maRangeNames.emplace_back(); + RangeName& r = maRangeNames.back(); + r.mnIndex = nIndex; + r.mnSheet = nSheet; + + ++nElementCurrent; + + return static_cast<const TokenId>(nElementCurrent); +} + +TokenId TokenPool::StoreExtName( sal_uInt16 nFileId, const OUString& rName ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast<sal_uInt16>(maExtNames.size()); + pType[nElementCurrent] = T_ExtName; + + maExtNames.emplace_back(); + ExtName& r = maExtNames.back(); + r.mnFileId = nFileId; + r.maName = rName; + + ++nElementCurrent; + + return static_cast<const TokenId>(nElementCurrent); +} + +TokenId TokenPool::StoreExtRef( sal_uInt16 nFileId, const OUString& rTabName, const ScSingleRefData& rRef ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast<sal_uInt16>(maExtCellRefs.size()); + pType[nElementCurrent] = T_ExtRefC; + + maExtCellRefs.emplace_back(); + ExtCellRef& r = maExtCellRefs.back(); + r.mnFileId = nFileId; + r.maTabName = rTabName; + r.maRef = rRef; + + ++nElementCurrent; + + return static_cast<const TokenId>(nElementCurrent); +} + +TokenId TokenPool::StoreExtRef( sal_uInt16 nFileId, const OUString& rTabName, const ScComplexRefData& rRef ) +{ + if (!CheckElementOrGrow()) + return static_cast<const TokenId>(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast<sal_uInt16>(maExtAreaRefs.size()); + pType[nElementCurrent] = T_ExtRefA; + + maExtAreaRefs.emplace_back(); + ExtAreaRef& r = maExtAreaRefs.back(); + r.mnFileId = nFileId; + r.maTabName = rTabName; + r.maRef = rRef; + + ++nElementCurrent; + + return static_cast<const TokenId>(nElementCurrent); +} + +void TokenPool::Reset() +{ + nP_IdCurrent = nP_IdLast = nElementCurrent + = ppP_Str.m_writemark = pP_Dbl.m_writemark = pP_Err.m_writemark + = ppP_RefTr.m_writemark = ppP_Ext.m_writemark = ppP_Nlf.m_writemark = nP_MatrixCurrent = 0; + maRangeNames.clear(); + maExtNames.clear(); + maExtCellRefs.clear(); + maExtAreaRefs.clear(); + ClearMatrix(); +} + +bool TokenPool::IsSingleOp( const TokenId& rId, const DefTokenId eId ) const +{ + sal_uInt16 nId = static_cast<sal_uInt16>(rId); + if( nId && nId <= nElementCurrent ) + {// existent? + nId--; + if( T_Id == pType[ nId ] ) + {// Token-Sequence? + if( pSize[ nId ] == 1 ) + {// EXACTLY 1 Token + sal_uInt16 nPid = pElement[ nId ]; + if (nPid < nP_Id) + { + sal_uInt16 nSecId = pP_Id[ nPid ]; + if( nSecId >= nScTokenOff ) + {// Default-Token? + return static_cast<DefTokenId>( nSecId - nScTokenOff ) == eId; // wanted? + } + } + } + } + } + + return false; +} + +const OUString* TokenPool::GetExternal( const TokenId& rId ) const +{ + const OUString* p = nullptr; + sal_uInt16 n = static_cast<sal_uInt16>(rId); + if( n && n <= nElementCurrent ) + { + n--; + if( pType[ n ] == T_Ext ) + { + sal_uInt16 nExt = pElement[ n ]; + if ( nExt < ppP_Ext.m_writemark && ppP_Ext[ nExt ] ) + p = &ppP_Ext[ nExt ]->aText; + } + } + + return p; +} + +ScMatrix* TokenPool::GetMatrix( unsigned int n ) const +{ + if( n < nP_MatrixCurrent ) + return ppP_Matrix[ n ]; + else + SAL_WARN("sc.filter", "GetMatrix: " << n << " >= " << nP_MatrixCurrent); + return nullptr; +} + +void TokenPool::ClearMatrix() +{ + for(sal_uInt16 n = 0 ; n < nP_Matrix ; n++ ) + { + if( ppP_Matrix[ n ] ) + { + ppP_Matrix[ n ]->DecRef( ); + ppP_Matrix[n] = nullptr; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xechart.cxx b/sc/source/filter/excel/xechart.cxx new file mode 100644 index 000000000..64525457f --- /dev/null +++ b/sc/source/filter/excel/xechart.cxx @@ -0,0 +1,3475 @@ +/* -*- 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 <xechart.hxx> + +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/chart/XChartDocument.hpp> +#include <com/sun/star/chart/ChartAxisLabelPosition.hpp> +#include <com/sun/star/chart/ChartAxisPosition.hpp> +#include <com/sun/star/chart/ChartLegendExpansion.hpp> +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart/ErrorBarStyle.hpp> +#include <com/sun/star/chart/MissingValueTreatment.hpp> +#include <com/sun/star/chart/TimeInterval.hpp> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart/XAxisSupplier.hpp> +#include <com/sun/star/chart/XDiagramPositioning.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/XDiagram.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XDataSeriesContainer.hpp> +#include <com/sun/star/chart2/XRegressionCurveContainer.hpp> +#include <com/sun/star/chart2/XTitled.hpp> +#include <com/sun/star/chart2/XColorScheme.hpp> +#include <com/sun/star/chart2/data/XDataSource.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/chart2/CurveStyle.hpp> +#include <com/sun/star/chart2/DataPointGeometry3D.hpp> +#include <com/sun/star/chart2/DataPointLabel.hpp> +#include <com/sun/star/chart2/LegendPosition.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/StackingDirection.hpp> +#include <com/sun/star/chart2/TickmarkStyle.hpp> + +#include <tools/gen.hxx> +#include <filter/msfilter/escherex.hxx> + +#include <document.hxx> +#include <compiler.hxx> +#include <tokenarray.hxx> +#include <xeescher.hxx> +#include <xeformula.hxx> +#include <xehelper.hxx> +#include <xepage.hxx> +#include <xestyle.hxx> +#include <xltools.hxx> + +#include <memory> + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::i18n::XBreakIterator; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XShapes; + +using ::com::sun::star::chart2::IncrementData; +using ::com::sun::star::chart2::RelativePosition; +using ::com::sun::star::chart2::RelativeSize; +using ::com::sun::star::chart2::ScaleData; +using ::com::sun::star::chart2::SubIncrement; +using ::com::sun::star::chart2::XAxis; +using ::com::sun::star::chart2::XChartDocument; +using ::com::sun::star::chart2::XChartTypeContainer; +using ::com::sun::star::chart2::XColorScheme; +using ::com::sun::star::chart2::XCoordinateSystem; +using ::com::sun::star::chart2::XCoordinateSystemContainer; +using ::com::sun::star::chart2::XChartType; +using ::com::sun::star::chart2::XDataSeries; +using ::com::sun::star::chart2::XDataSeriesContainer; +using ::com::sun::star::chart2::XDiagram; +using ::com::sun::star::chart2::XFormattedString; +using ::com::sun::star::chart2::XLegend; +using ::com::sun::star::chart2::XRegressionCurve; +using ::com::sun::star::chart2::XRegressionCurveContainer; +using ::com::sun::star::chart2::XTitle; +using ::com::sun::star::chart2::XTitled; + +using ::com::sun::star::chart2::data::XDataSequence; +using ::com::sun::star::chart2::data::XDataSource; +using ::com::sun::star::chart2::data::XLabeledDataSequence; + +using ::formula::FormulaToken; +using ::formula::FormulaTokenArrayPlainIterator; + +namespace cssc = ::com::sun::star::chart; +namespace cssc2 = ::com::sun::star::chart2; + +// Helpers ==================================================================== + +namespace { + +XclExpStream& operator<<( XclExpStream& rStrm, const XclChRectangle& rRect ) +{ + return rStrm << rRect.mnX << rRect.mnY << rRect.mnWidth << rRect.mnHeight; +} + +void lclSaveRecord( XclExpStream& rStrm, XclExpRecordRef const & xRec ) +{ + if( xRec ) + xRec->Save( rStrm ); +} + +/** Saves the passed record (group) together with a leading value record. */ +template< typename Type > +void lclSaveRecord( XclExpStream& rStrm, XclExpRecordRef const & xRec, sal_uInt16 nRecId, Type nValue ) +{ + if( xRec ) + { + XclExpValueRecord< Type >( nRecId, nValue ).Save( rStrm ); + xRec->Save( rStrm ); + } +} + +template<typename ValueType, typename KeyType> +void lclSaveRecord(XclExpStream& rStrm, ValueType* pRec, sal_uInt16 nRecId, KeyType nValue) +{ + if (pRec) + { + XclExpValueRecord<KeyType>(nRecId, nValue).Save(rStrm); + pRec->Save(rStrm); + } +} + +void lclWriteChFrBlockRecord( XclExpStream& rStrm, const XclChFrBlock& rFrBlock, bool bBegin ) +{ + sal_uInt16 nRecId = bBegin ? EXC_ID_CHFRBLOCKBEGIN : EXC_ID_CHFRBLOCKEND; + rStrm.StartRecord( nRecId, 12 ); + rStrm << nRecId << EXC_FUTUREREC_EMPTYFLAGS << rFrBlock.mnType << rFrBlock.mnContext << rFrBlock.mnValue1 << rFrBlock.mnValue2; + rStrm.EndRecord(); +} + +template< typename Type > +bool lclIsAutoAnyOrGetValue( Type& rValue, const Any& rAny ) +{ + return !rAny.hasValue() || !(rAny >>= rValue); +} + +bool lclIsAutoAnyOrGetScaledValue( double& rfValue, const Any& rAny, bool bLogScale ) +{ + bool bIsAuto = lclIsAutoAnyOrGetValue( rfValue, rAny ); + if( !bIsAuto && bLogScale ) + rfValue = log( rfValue ) / log( 10.0 ); + return bIsAuto; +} + +sal_uInt16 lclGetTimeValue( const XclExpRoot& rRoot, double fSerialDate, sal_uInt16 nTimeUnit ) +{ + DateTime aDateTime = rRoot.GetDateTimeFromDouble( fSerialDate ); + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: + return ::limit_cast< sal_uInt16, double >( fSerialDate, 0, SAL_MAX_UINT16 ); + case EXC_CHDATERANGE_MONTHS: + return ::limit_cast< sal_uInt16, sal_uInt16 >( 12 * (aDateTime.GetYear() - rRoot.GetBaseYear()) + aDateTime.GetMonth() - 1, 0, SAL_MAX_INT16 ); + case EXC_CHDATERANGE_YEARS: + return ::limit_cast< sal_uInt16, sal_uInt16 >( aDateTime.GetYear() - rRoot.GetBaseYear(), 0, SAL_MAX_INT16 ); + default: + OSL_ENSURE( false, "lclGetTimeValue - unexpected time unit" ); + } + return ::limit_cast< sal_uInt16, double >( fSerialDate, 0, SAL_MAX_UINT16 ); +} + +bool lclConvertTimeValue( const XclExpRoot& rRoot, sal_uInt16& rnValue, const Any& rAny, sal_uInt16 nTimeUnit ) +{ + double fSerialDate = 0; + bool bAuto = lclIsAutoAnyOrGetValue( fSerialDate, rAny ); + if( !bAuto ) + rnValue = lclGetTimeValue( rRoot, fSerialDate, nTimeUnit ); + return bAuto; +} + +sal_uInt16 lclGetTimeUnit( sal_Int32 nApiTimeUnit ) +{ + switch( nApiTimeUnit ) + { + case cssc::TimeUnit::DAY: return EXC_CHDATERANGE_DAYS; + case cssc::TimeUnit::MONTH: return EXC_CHDATERANGE_MONTHS; + case cssc::TimeUnit::YEAR: return EXC_CHDATERANGE_YEARS; + default: OSL_ENSURE( false, "lclGetTimeUnit - unexpected time unit" ); + } + return EXC_CHDATERANGE_DAYS; +} + +bool lclConvertTimeInterval( sal_uInt16& rnValue, sal_uInt16& rnTimeUnit, const Any& rAny ) +{ + cssc::TimeInterval aInterval; + bool bAuto = lclIsAutoAnyOrGetValue( aInterval, rAny ); + if( !bAuto ) + { + rnValue = ::limit_cast< sal_uInt16, sal_Int32 >( aInterval.Number, 1, SAL_MAX_UINT16 ); + rnTimeUnit = lclGetTimeUnit( aInterval.TimeUnit ); + } + return bAuto; +} + +} // namespace + +// Common ===================================================================== + +/** Stores global data needed in various classes of the Chart export filter. */ +struct XclExpChRootData : public XclChRootData +{ + typedef ::std::vector< XclChFrBlock > XclChFrBlockVector; + + XclExpChChart& mrChartData; /// The chart data object. + XclChFrBlockVector maWrittenFrBlocks; /// Stack of future record levels already written out. + XclChFrBlockVector maUnwrittenFrBlocks; /// Stack of future record levels not yet written out. + + explicit XclExpChRootData( XclExpChChart& rChartData ) : mrChartData( rChartData ) {} + + /** Registers a new future record level. */ + void RegisterFutureRecBlock( const XclChFrBlock& rFrBlock ); + /** Initializes the current future record level (writes all unwritten CHFRBLOCKBEGIN records). */ + void InitializeFutureRecBlock( XclExpStream& rStrm ); + /** Finalizes the current future record level (writes CHFRBLOCKEND record if needed). */ + void FinalizeFutureRecBlock( XclExpStream& rStrm ); +}; + +void XclExpChRootData::RegisterFutureRecBlock( const XclChFrBlock& rFrBlock ) +{ + maUnwrittenFrBlocks.push_back( rFrBlock ); +} + +void XclExpChRootData::InitializeFutureRecBlock( XclExpStream& rStrm ) +{ + // first call from a future record writes all missing CHFRBLOCKBEGIN records + if( maUnwrittenFrBlocks.empty() ) + return; + + // write the leading CHFRINFO record + if( maWrittenFrBlocks.empty() ) + { + rStrm.StartRecord( EXC_ID_CHFRINFO, 20 ); + rStrm << EXC_ID_CHFRINFO << EXC_FUTUREREC_EMPTYFLAGS << EXC_CHFRINFO_EXCELXP2003 << EXC_CHFRINFO_EXCELXP2003 << sal_uInt16( 3 ); + rStrm << sal_uInt16( 0x0850 ) << sal_uInt16( 0x085A ) << sal_uInt16( 0x0861 ) << sal_uInt16( 0x0861 ) << sal_uInt16( 0x086A ) << sal_uInt16( 0x086B ); + rStrm.EndRecord(); + } + // write all unwritten CHFRBLOCKBEGIN records + for( const auto& rUnwrittenFrBlock : maUnwrittenFrBlocks ) + { + OSL_ENSURE( rUnwrittenFrBlock.mnType != EXC_CHFRBLOCK_TYPE_UNKNOWN, "XclExpChRootData::InitializeFutureRecBlock - unknown future record block type" ); + lclWriteChFrBlockRecord( rStrm, rUnwrittenFrBlock, true ); + } + // move all record infos to vector of written blocks + maWrittenFrBlocks.insert( maWrittenFrBlocks.end(), maUnwrittenFrBlocks.begin(), maUnwrittenFrBlocks.end() ); + maUnwrittenFrBlocks.clear(); +} + +void XclExpChRootData::FinalizeFutureRecBlock( XclExpStream& rStrm ) +{ + OSL_ENSURE( !maUnwrittenFrBlocks.empty() || !maWrittenFrBlocks.empty(), "XclExpChRootData::FinalizeFutureRecBlock - no future record level found" ); + if( !maUnwrittenFrBlocks.empty() ) + { + // no future record has been written, just forget the topmost level + maUnwrittenFrBlocks.pop_back(); + } + else if( !maWrittenFrBlocks.empty() ) + { + // write the CHFRBLOCKEND record for the topmost block and delete it + lclWriteChFrBlockRecord( rStrm, maWrittenFrBlocks.back(), false ); + maWrittenFrBlocks.pop_back(); + } +} + +XclExpChRoot::XclExpChRoot( const XclExpRoot& rRoot, XclExpChChart& rChartData ) : + XclExpRoot( rRoot ), + mxChData( std::make_shared<XclExpChRootData>( rChartData ) ) +{ +} + +XclExpChRoot::~XclExpChRoot() +{ +} + +Reference< XChartDocument > const & XclExpChRoot::GetChartDocument() const +{ + return mxChData->mxChartDoc; +} + +XclExpChChart& XclExpChRoot::GetChartData() const +{ + return mxChData->mrChartData; +} + +const XclChTypeInfo& XclExpChRoot::GetChartTypeInfo( XclChTypeId eType ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfo( eType ); +} + +const XclChTypeInfo& XclExpChRoot::GetChartTypeInfo( std::u16string_view rServiceName ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfoFromService( rServiceName ); +} + +const XclChFormatInfo& XclExpChRoot::GetFormatInfo( XclChObjectType eObjType ) const +{ + return mxChData->mxFmtInfoProv->GetFormatInfo( eObjType ); +} + +void XclExpChRoot::InitConversion( css::uno::Reference< css::chart2::XChartDocument > const & xChartDoc, const tools::Rectangle& rChartRect ) const +{ + mxChData->InitConversion( GetRoot(), xChartDoc, rChartRect ); +} + +void XclExpChRoot::FinishConversion() const +{ + mxChData->FinishConversion(); +} + +bool XclExpChRoot::IsSystemColor( const Color& rColor, sal_uInt16 nSysColorIdx ) const +{ + XclExpPalette& rPal = GetPalette(); + return rPal.IsSystemColor( nSysColorIdx ) && (rColor == rPal.GetDefColor( nSysColorIdx )); +} + +void XclExpChRoot::SetSystemColor( Color& rColor, sal_uInt32& rnColorId, sal_uInt16 nSysColorIdx ) const +{ + OSL_ENSURE( GetPalette().IsSystemColor( nSysColorIdx ), "XclExpChRoot::SetSystemColor - invalid color index" ); + rColor = GetPalette().GetDefColor( nSysColorIdx ); + rnColorId = XclExpPalette::GetColorIdFromIndex( nSysColorIdx ); +} + +sal_Int32 XclExpChRoot::CalcChartXFromHmm( sal_Int32 nPosX ) const +{ + return ::limit_cast< sal_Int32, double >( (nPosX - mxChData->mnBorderGapX) / mxChData->mfUnitSizeX, 0, EXC_CHART_TOTALUNITS ); +} + +sal_Int32 XclExpChRoot::CalcChartYFromHmm( sal_Int32 nPosY ) const +{ + return ::limit_cast< sal_Int32, double >( (nPosY - mxChData->mnBorderGapY) / mxChData->mfUnitSizeY, 0, EXC_CHART_TOTALUNITS ); +} + +XclChRectangle XclExpChRoot::CalcChartRectFromHmm( const css::awt::Rectangle& rRect ) const +{ + XclChRectangle aRect; + aRect.mnX = CalcChartXFromHmm( rRect.X ); + aRect.mnY = CalcChartYFromHmm( rRect.Y ); + aRect.mnWidth = CalcChartXFromHmm( rRect.Width ); + aRect.mnHeight = CalcChartYFromHmm( rRect.Height ); + return aRect; +} + +void XclExpChRoot::ConvertLineFormat( XclChLineFormat& rLineFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().ReadLineProperties( + rLineFmt, *mxChData->mxLineDashTable, rPropSet, ePropMode ); +} + +bool XclExpChRoot::ConvertAreaFormat( XclChAreaFormat& rAreaFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) const +{ + return GetChartPropSetHelper().ReadAreaProperties( rAreaFmt, rPropSet, ePropMode ); +} + +void XclExpChRoot::ConvertEscherFormat( + XclChEscherFormat& rEscherFmt, XclChPicFormat& rPicFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().ReadEscherProperties( rEscherFmt, rPicFmt, + *mxChData->mxGradientTable, *mxChData->mxHatchTable, *mxChData->mxBitmapTable, rPropSet, ePropMode ); +} + +sal_uInt16 XclExpChRoot::ConvertFont( const ScfPropertySet& rPropSet, sal_Int16 nScript ) const +{ + XclFontData aFontData; + GetFontPropSetHelper().ReadFontProperties( aFontData, rPropSet, EXC_FONTPROPSET_CHART, nScript ); + return GetFontBuffer().Insert( aFontData, EXC_COLOR_CHARTTEXT ); +} + +sal_uInt16 XclExpChRoot::ConvertPieRotation( const ScfPropertySet& rPropSet ) +{ + sal_Int32 nApiRot = 0; + rPropSet.GetProperty( nApiRot, EXC_CHPROP_STARTINGANGLE ); + return static_cast< sal_uInt16 >( (450 - (nApiRot % 360)) % 360 ); +} + +void XclExpChRoot::RegisterFutureRecBlock( const XclChFrBlock& rFrBlock ) +{ + mxChData->RegisterFutureRecBlock( rFrBlock ); +} + +void XclExpChRoot::InitializeFutureRecBlock( XclExpStream& rStrm ) +{ + mxChData->InitializeFutureRecBlock( rStrm ); +} + +void XclExpChRoot::FinalizeFutureRecBlock( XclExpStream& rStrm ) +{ + mxChData->FinalizeFutureRecBlock( rStrm ); +} + +XclExpChGroupBase::XclExpChGroupBase( const XclExpChRoot& rRoot, + sal_uInt16 nFrType, sal_uInt16 nRecId, std::size_t nRecSize ) : + XclExpRecord( nRecId, nRecSize ), + XclExpChRoot( rRoot ), + maFrBlock( nFrType ) +{ +} + +XclExpChGroupBase::~XclExpChGroupBase() +{ +} + +void XclExpChGroupBase::Save( XclExpStream& rStrm ) +{ + // header record + XclExpRecord::Save( rStrm ); + // group records + if( !HasSubRecords() ) + return; + + // register the future record context corresponding to this record group + RegisterFutureRecBlock( maFrBlock ); + // CHBEGIN record + XclExpEmptyRecord( EXC_ID_CHBEGIN ).Save( rStrm ); + // embedded records + WriteSubRecords( rStrm ); + // finalize the future records, must be done before the closing CHEND + FinalizeFutureRecBlock( rStrm ); + // CHEND record + XclExpEmptyRecord( EXC_ID_CHEND ).Save( rStrm ); +} + +bool XclExpChGroupBase::HasSubRecords() const +{ + return true; +} + +void XclExpChGroupBase::SetFutureRecordContext( sal_uInt16 nFrContext, sal_uInt16 nFrValue1, sal_uInt16 nFrValue2 ) +{ + maFrBlock.mnContext = nFrContext; + maFrBlock.mnValue1 = nFrValue1; + maFrBlock.mnValue2 = nFrValue2; +} + +XclExpChFutureRecordBase::XclExpChFutureRecordBase( const XclExpChRoot& rRoot, + XclFutureRecType eRecType, sal_uInt16 nRecId, std::size_t nRecSize ) : + XclExpFutureRecord( eRecType, nRecId, nRecSize ), + XclExpChRoot( rRoot ) +{ +} + +void XclExpChFutureRecordBase::Save( XclExpStream& rStrm ) +{ + InitializeFutureRecBlock( rStrm ); + XclExpFutureRecord::Save( rStrm ); +} + +// Frame formatting =========================================================== + +XclExpChFramePos::XclExpChFramePos( sal_uInt16 nTLMode ) : + XclExpRecord( EXC_ID_CHFRAMEPOS, 20 ) +{ + maData.mnTLMode = nTLMode; + maData.mnBRMode = EXC_CHFRAMEPOS_PARENT; +} + +void XclExpChFramePos::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnTLMode << maData.mnBRMode << maData.maRect; +} + +XclExpChLineFormat::XclExpChLineFormat( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHLINEFORMAT, (rRoot.GetBiff() == EXC_BIFF8) ? 12 : 10 ), + mnColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +void XclExpChLineFormat::SetDefault( XclChFrameType eDefFrameType ) +{ + switch( eDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + SetAuto( true ); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + SetAuto( false ); + maData.mnPattern = EXC_CHLINEFORMAT_NONE; + break; + default: + OSL_FAIL( "XclExpChLineFormat::SetDefault - unknown frame type" ); + } +} + +void XclExpChLineFormat::Convert( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + rRoot.ConvertLineFormat( maData, rPropSet, rFmtInfo.mePropMode ); + if( HasLine() ) + { + // detect system color, set color identifier (TODO: detect automatic series line) + if( (eObjType != EXC_CHOBJTYPE_LINEARSERIES) && rRoot.IsSystemColor( maData.maColor, rFmtInfo.mnAutoLineColorIdx ) ) + { + // store color index from automatic format data + mnColorId = XclExpPalette::GetColorIdFromIndex( rFmtInfo.mnAutoLineColorIdx ); + // try to set automatic mode + bool bAuto = (maData.mnPattern == EXC_CHLINEFORMAT_SOLID) && (maData.mnWeight == rFmtInfo.mnAutoLineWeight); + ::set_flag( maData.mnFlags, EXC_CHLINEFORMAT_AUTO, bAuto ); + } + else + { + // user defined color - register in palette + mnColorId = rRoot.GetPalette().InsertColor( maData.maColor, EXC_COLOR_CHARTLINE ); + } + } + else + { + // no line - set default system color + rRoot.SetSystemColor( maData.maColor, mnColorId, EXC_COLOR_CHWINDOWTEXT ); + } +} + +bool XclExpChLineFormat::IsDefault( XclChFrameType eDefFrameType ) const +{ + return + ((eDefFrameType == EXC_CHFRAMETYPE_INVISIBLE) && !HasLine()) || + ((eDefFrameType == EXC_CHFRAMETYPE_AUTO) && IsAuto()); +} + +void XclExpChLineFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maColor << maData.mnPattern << maData.mnWeight << maData.mnFlags; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + rStrm << rStrm.GetRoot().GetPalette().GetColorIndex( mnColorId ); +} + +namespace { + +/** Creates a CHLINEFORMAT record from the passed property set. */ +XclExpChLineFormatRef lclCreateLineFormat( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + XclExpChLineFormatRef xLineFmt = new XclExpChLineFormat( rRoot ); + xLineFmt->Convert( rRoot, rPropSet, eObjType ); + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( rFmtInfo.mbDeleteDefFrame && xLineFmt->IsDefault( rFmtInfo.meDefFrameType ) ) + xLineFmt.clear(); + return xLineFmt; +} + +} // namespace + +XclExpChAreaFormat::XclExpChAreaFormat( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHAREAFORMAT, (rRoot.GetBiff() == EXC_BIFF8) ? 16 : 12 ), + mnPattColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ), + mnBackColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +bool XclExpChAreaFormat::Convert( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + bool bComplexFill = rRoot.ConvertAreaFormat( maData, rPropSet, rFmtInfo.mePropMode ); + if( HasArea() ) + { + bool bSolid = maData.mnPattern == EXC_PATT_SOLID; + // detect system color, set color identifier (TODO: detect automatic series area) + if( (eObjType != EXC_CHOBJTYPE_FILLEDSERIES) && rRoot.IsSystemColor( maData.maPattColor, rFmtInfo.mnAutoPattColorIdx ) ) + { + // store color index from automatic format data + mnPattColorId = XclExpPalette::GetColorIdFromIndex( rFmtInfo.mnAutoPattColorIdx ); + // set automatic mode + ::set_flag( maData.mnFlags, EXC_CHAREAFORMAT_AUTO, bSolid ); + } + else + { + // user defined color - register color in palette + mnPattColorId = rRoot.GetPalette().InsertColor( maData.maPattColor, EXC_COLOR_CHARTAREA ); + } + // background color (default system color for solid fills) + if( bSolid ) + rRoot.SetSystemColor( maData.maBackColor, mnBackColorId, EXC_COLOR_CHWINDOWTEXT ); + else + mnBackColorId = rRoot.GetPalette().InsertColor( maData.maBackColor, EXC_COLOR_CHARTAREA ); + } + else + { + // no area - set default system colors + rRoot.SetSystemColor( maData.maPattColor, mnPattColorId, EXC_COLOR_CHWINDOWBACK ); + rRoot.SetSystemColor( maData.maBackColor, mnBackColorId, EXC_COLOR_CHWINDOWTEXT ); + } + return bComplexFill; +} + +void XclExpChAreaFormat::SetDefault( XclChFrameType eDefFrameType ) +{ + switch( eDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + SetAuto( true ); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + SetAuto( false ); + maData.mnPattern = EXC_PATT_NONE; + break; + default: + OSL_FAIL( "XclExpChAreaFormat::SetDefault - unknown frame type" ); + } +} + +bool XclExpChAreaFormat::IsDefault( XclChFrameType eDefFrameType ) const +{ + return + ((eDefFrameType == EXC_CHFRAMETYPE_INVISIBLE) && !HasArea()) || + ((eDefFrameType == EXC_CHFRAMETYPE_AUTO) && IsAuto()); +} + +void XclExpChAreaFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maPattColor << maData.maBackColor << maData.mnPattern << maData.mnFlags; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + const XclExpPalette& rPal = rStrm.GetRoot().GetPalette(); + rStrm << rPal.GetColorIndex( mnPattColorId ) << rPal.GetColorIndex( mnBackColorId ); + } +} + +XclExpChEscherFormat::XclExpChEscherFormat( const XclExpChRoot& rRoot ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_UNKNOWN, EXC_ID_CHESCHERFORMAT ), + mnColor1Id( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ), + mnColor2Id( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); +} + +void XclExpChEscherFormat::Convert( const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + const XclChFormatInfo& rFmtInfo = GetFormatInfo( eObjType ); + ConvertEscherFormat( maData, maPicFmt, rPropSet, rFmtInfo.mePropMode ); + // register colors in palette + mnColor1Id = RegisterColor( ESCHER_Prop_fillColor ); + mnColor2Id = RegisterColor( ESCHER_Prop_fillBackColor ); +} + +bool XclExpChEscherFormat::IsValid() const +{ + return static_cast< bool >(maData.mxEscherSet); +} + +void XclExpChEscherFormat::Save( XclExpStream& rStrm ) +{ + if( maData.mxEscherSet ) + { + // replace RGB colors with palette indexes in the Escher container + const XclExpPalette& rPal = GetPalette(); + maData.mxEscherSet->AddOpt( ESCHER_Prop_fillColor, 0x08000000 | rPal.GetColorIndex( mnColor1Id ) ); + maData.mxEscherSet->AddOpt( ESCHER_Prop_fillBackColor, 0x08000000 | rPal.GetColorIndex( mnColor2Id ) ); + + // save the record group + XclExpChGroupBase::Save( rStrm ); + } +} + +bool XclExpChEscherFormat::HasSubRecords() const +{ + // no subrecords for gradients + return maPicFmt.mnBmpMode != EXC_CHPICFORMAT_NONE; +} + +void XclExpChEscherFormat::WriteSubRecords( XclExpStream& rStrm ) +{ + rStrm.StartRecord( EXC_ID_CHPICFORMAT, 14 ); + rStrm << maPicFmt.mnBmpMode << sal_uInt16( 0 ) << maPicFmt.mnFlags << maPicFmt.mfScale; + rStrm.EndRecord(); +} + +sal_uInt32 XclExpChEscherFormat::RegisterColor( sal_uInt16 nPropId ) +{ + sal_uInt32 nBGRValue; + if( maData.mxEscherSet && maData.mxEscherSet->GetOpt( nPropId, nBGRValue ) ) + { + // swap red and blue + Color aColor( nBGRValue & 0xff, (nBGRValue >> 8) & 0xff, (nBGRValue >> 16) & 0xff ); + return GetPalette().InsertColor( aColor, EXC_COLOR_CHARTAREA ); + } + return XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ); +} + +void XclExpChEscherFormat::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE( maData.mxEscherSet, "XclExpChEscherFormat::WriteBody - missing property container" ); + // write Escher property container via temporary memory stream + SvMemoryStream aMemStrm; + maData.mxEscherSet->Commit( aMemStrm ); + aMemStrm.FlushBuffer(); + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + rStrm.CopyFromStream( aMemStrm ); +} + +XclExpChFrameBase::XclExpChFrameBase() +{ +} + +XclExpChFrameBase::~XclExpChFrameBase() +{ +} + +void XclExpChFrameBase::ConvertFrameBase( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + // line format + mxLineFmt = new XclExpChLineFormat( rRoot ); + mxLineFmt->Convert( rRoot, rPropSet, eObjType ); + // area format (only for frame objects) + if( !rRoot.GetFormatInfo( eObjType ).mbIsFrame ) + return; + + mxAreaFmt = new XclExpChAreaFormat( rRoot ); + bool bComplexFill = mxAreaFmt->Convert( rRoot, rPropSet, eObjType ); + if( (rRoot.GetBiff() == EXC_BIFF8) && bComplexFill ) + { + mxEscherFmt = new XclExpChEscherFormat( rRoot ); + mxEscherFmt->Convert( rPropSet, eObjType ); + if( mxEscherFmt->IsValid() ) + mxAreaFmt->SetAuto( false ); + else + mxEscherFmt.clear(); + } +} + +void XclExpChFrameBase::SetDefaultFrameBase( const XclExpChRoot& rRoot, + XclChFrameType eDefFrameType, bool bIsFrame ) +{ + // line format + mxLineFmt = new XclExpChLineFormat( rRoot ); + mxLineFmt->SetDefault( eDefFrameType ); + // area format (only for frame objects) + if( bIsFrame ) + { + mxAreaFmt = new XclExpChAreaFormat( rRoot ); + mxAreaFmt->SetDefault( eDefFrameType ); + mxEscherFmt.clear(); + } +} + +bool XclExpChFrameBase::IsDefaultFrameBase( XclChFrameType eDefFrameType ) const +{ + return + (!mxLineFmt || mxLineFmt->IsDefault( eDefFrameType )) && + (!mxAreaFmt || mxAreaFmt->IsDefault( eDefFrameType )); +} + +void XclExpChFrameBase::WriteFrameRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxLineFmt ); + lclSaveRecord( rStrm, mxAreaFmt ); + lclSaveRecord( rStrm, mxEscherFmt ); +} + +XclExpChFrame::XclExpChFrame( const XclExpChRoot& rRoot, XclChObjectType eObjType ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_FRAME, EXC_ID_CHFRAME, 4 ), + meObjType( eObjType ) +{ +} + +void XclExpChFrame::Convert( const ScfPropertySet& rPropSet ) +{ + ConvertFrameBase( GetChRoot(), rPropSet, meObjType ); +} + +void XclExpChFrame::SetAutoFlags( bool bAutoPos, bool bAutoSize ) +{ + ::set_flag( maData.mnFlags, EXC_CHFRAME_AUTOPOS, bAutoPos ); + ::set_flag( maData.mnFlags, EXC_CHFRAME_AUTOSIZE, bAutoSize ); +} + +bool XclExpChFrame::IsDefault() const +{ + return IsDefaultFrameBase( GetFormatInfo( meObjType ).meDefFrameType ); +} + +bool XclExpChFrame::IsDeleteable() const +{ + return IsDefault() && GetFormatInfo( meObjType ).mbDeleteDefFrame; +} + +void XclExpChFrame::Save( XclExpStream& rStrm ) +{ + switch( meObjType ) + { + // wall/floor frame without CHFRAME header record + case EXC_CHOBJTYPE_WALL3D: + case EXC_CHOBJTYPE_FLOOR3D: + WriteFrameRecords( rStrm ); + break; + default: + XclExpChGroupBase::Save( rStrm ); + } +} + +void XclExpChFrame::WriteSubRecords( XclExpStream& rStrm ) +{ + WriteFrameRecords( rStrm ); +} + +void XclExpChFrame::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnFormat << maData.mnFlags; +} + +namespace { + +/** Creates a CHFRAME record from the passed property set. */ +XclExpChFrameRef lclCreateFrame( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + XclExpChFrameRef xFrame = new XclExpChFrame( rRoot, eObjType ); + xFrame->Convert( rPropSet ); + if( xFrame->IsDeleteable() ) + xFrame.clear(); + return xFrame; +} + +} // namespace + +// Source links =============================================================== + +namespace { + +void lclAddDoubleRefData( + ScTokenArray& orArray, const FormulaToken& rToken, + SCTAB nScTab1, SCCOL nScCol1, SCROW nScRow1, + SCTAB nScTab2, SCCOL nScCol2, SCROW nScRow2 ) +{ + ScComplexRefData aComplexRef; + aComplexRef.InitRange(ScRange(nScCol1,nScRow1,nScTab1,nScCol2,nScRow2,nScTab2)); + aComplexRef.Ref1.SetFlag3D( true ); + + if( orArray.GetLen() > 0 ) + orArray.AddOpCode( ocUnion ); + + OSL_ENSURE( (rToken.GetType() == ::formula::svDoubleRef) || (rToken.GetType() == ::formula::svExternalDoubleRef), + "lclAddDoubleRefData - double reference token expected"); + if( rToken.GetType() == ::formula::svExternalDoubleRef ) + orArray.AddExternalDoubleReference( + rToken.GetIndex(), rToken.GetString(), aComplexRef); + else + orArray.AddDoubleReference( aComplexRef ); +} + +} // namespace + +XclExpChSourceLink::XclExpChSourceLink( const XclExpChRoot& rRoot, sal_uInt8 nDestType ) : + XclExpRecord( EXC_ID_CHSOURCELINK ), + XclExpChRoot( rRoot ) +{ + maData.mnDestType = nDestType; + maData.mnLinkType = EXC_CHSRCLINK_DIRECTLY; +} + +sal_uInt16 XclExpChSourceLink::ConvertDataSequence( Reference< XDataSequence > const & xDataSeq, bool bSplitToColumns, sal_uInt16 nDefCount ) +{ + mxLinkFmla.reset(); + maData.mnLinkType = EXC_CHSRCLINK_DEFAULT; + + if( !xDataSeq.is() ) + return nDefCount; + + // Compile the range representation string into token array. Note that the + // source range text depends on the current grammar. + OUString aRangeRepr = xDataSeq->getSourceRangeRepresentation(); + ScCompiler aComp( GetDoc(), ScAddress(), GetDoc().GetGrammar() ); + std::unique_ptr<ScTokenArray> pArray(aComp.CompileString(aRangeRepr)); + if( !pArray ) + return nDefCount; + + ScTokenArray aArray(GetRoot().GetDoc()); + sal_uInt32 nValueCount = 0; + FormulaTokenArrayPlainIterator aIter(*pArray); + for( const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next() ) + { + switch( pToken->GetType() ) + { + case ::formula::svSingleRef: + case ::formula::svExternalSingleRef: + // for a single ref token, just add it to the new token array as is + if( aArray.GetLen() > 0 ) + aArray.AddOpCode( ocUnion ); + aArray.AddToken( *pToken ); + ++nValueCount; + break; + + case ::formula::svDoubleRef: + case ::formula::svExternalDoubleRef: + { + // split 3-dimensional ranges into single sheets + const ScComplexRefData& rComplexRef = *pToken->GetDoubleRef(); + ScAddress aAbs1 = rComplexRef.Ref1.toAbs(GetRoot().GetDoc(), ScAddress()); + ScAddress aAbs2 = rComplexRef.Ref2.toAbs(GetRoot().GetDoc(), ScAddress()); + for (SCTAB nScTab = aAbs1.Tab(); nScTab <= aAbs2.Tab(); ++nScTab) + { + // split 2-dimensional ranges into single columns + if (bSplitToColumns && (aAbs1.Col() < aAbs2.Col()) && (aAbs1.Row() < aAbs2.Row())) + for (SCCOL nScCol = aAbs1.Col(); nScCol <= aAbs2.Col(); ++nScCol) + lclAddDoubleRefData(aArray, *pToken, nScTab, nScCol, aAbs1.Row(), nScTab, nScCol, aAbs2.Row()); + else + lclAddDoubleRefData(aArray, *pToken, nScTab, aAbs1.Col(), aAbs1.Row(), nScTab, aAbs2.Col(), aAbs2.Row()); + } + sal_uInt32 nTabs = static_cast<sal_uInt32>(aAbs2.Tab() - aAbs1.Tab() + 1); + sal_uInt32 nCols = static_cast<sal_uInt32>(aAbs2.Col() - aAbs1.Col() + 1); + sal_uInt32 nRows = static_cast<sal_uInt32>(aAbs2.Row() - aAbs1.Row() + 1); + nValueCount += nCols * nRows * nTabs; + } + break; + + default:; + } + } + + const ScAddress aBaseCell; + mxLinkFmla = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CHART, aArray, &aBaseCell ); + maData.mnLinkType = EXC_CHSRCLINK_WORKSHEET; + return ulimit_cast< sal_uInt16 >( nValueCount, EXC_CHDATAFORMAT_MAXPOINTCOUNT ); +} + +void XclExpChSourceLink::ConvertString( const OUString& aString ) +{ + mxString = XclExpStringHelper::CreateString( GetRoot(), aString, XclStrFlags::ForceUnicode | XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); +} + +sal_uInt16 XclExpChSourceLink::ConvertStringSequence( const Sequence< Reference< XFormattedString > >& rStringSeq ) +{ + mxString.reset(); + sal_uInt16 nFontIdx = EXC_FONT_APP; + if( rStringSeq.hasElements() ) + { + mxString = XclExpStringHelper::CreateString( GetRoot(), OUString(), XclStrFlags::ForceUnicode | XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); + Reference< XBreakIterator > xBreakIt = GetDoc().GetBreakIterator(); + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + + // convert all formatted string entries from the sequence + for( const Reference< XFormattedString >& rString : rStringSeq ) + { + if( rString.is() ) + { + sal_uInt16 nWstrnFontIdx = EXC_FONT_NOTFOUND; + sal_uInt16 nAsianFontIdx = EXC_FONT_NOTFOUND; + sal_uInt16 nCmplxFontIdx = EXC_FONT_NOTFOUND; + OUString aText = rString->getString(); + ScfPropertySet aStrProp( rString ); + + // #i63255# get script type for leading weak characters + sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( GetRoot(), aText ); + + // process all script portions + sal_Int32 nPortionPos = 0; + sal_Int32 nTextLen = aText.getLength(); + while( nPortionPos < nTextLen ) + { + // get script type and end position of next script portion + sal_Int16 nScript = xBreakIt->getScriptType( aText, nPortionPos ); + sal_Int32 nPortionEnd = xBreakIt->endOfScript( aText, nPortionPos, nScript ); + + // reuse previous script for following weak portions + if( nScript == ApiScriptType::WEAK ) + nScript = nLastScript; + + // Excel start position of this portion + sal_uInt16 nXclPortionStart = mxString->Len(); + // add portion text to Excel string + XclExpStringHelper::AppendString( *mxString, GetRoot(), aText.subView( nPortionPos, nPortionEnd - nPortionPos ) ); + if( nXclPortionStart < mxString->Len() ) + { + // find font index variable dependent on script type + sal_uInt16& rnFontIdx = (nScript == ApiScriptType::COMPLEX) ? nCmplxFontIdx : + ((nScript == ApiScriptType::ASIAN) ? nAsianFontIdx : nWstrnFontIdx); + + // insert font into buffer (if not yet done) + if( rnFontIdx == EXC_FONT_NOTFOUND ) + rnFontIdx = ConvertFont( aStrProp, nScript ); + + // insert font index into format run vector + mxString->AppendFormat( nXclPortionStart, rnFontIdx ); + } + + // go to next script portion + nLastScript = nScript; + nPortionPos = nPortionEnd; + } + } + } + if( !mxString->IsEmpty() ) + { + // get leading font index + const XclFormatRunVec& rFormats = mxString->GetFormats(); + OSL_ENSURE( !rFormats.empty() && (rFormats.front().mnChar == 0), + "XclExpChSourceLink::ConvertStringSequenc - missing leading format" ); + // remove leading format run, if entire string is equally formatted + if( rFormats.size() == 1 ) + nFontIdx = mxString->RemoveLeadingFont(); + else if( !rFormats.empty() ) + nFontIdx = rFormats.front().mnFontIdx; + // add trailing format run, if string is rich-formatted + if( mxString->IsRich() ) + mxString->AppendTrailingFormat( EXC_FONT_APP ); + } + } + return nFontIdx; +} + +void XclExpChSourceLink::ConvertNumFmt( const ScfPropertySet& rPropSet, bool bPercent ) +{ + sal_Int32 nApiNumFmt = 0; + if( bPercent ? rPropSet.GetProperty( nApiNumFmt, EXC_CHPROP_PERCENTAGENUMFMT ) : rPropSet.GetProperty( nApiNumFmt, EXC_CHPROP_NUMBERFORMAT ) ) + { + ::set_flag( maData.mnFlags, EXC_CHSRCLINK_NUMFMT ); + maData.mnNumFmtIdx = GetNumFmtBuffer().Insert( static_cast< sal_uInt32 >( nApiNumFmt ) ); + } +} + +void XclExpChSourceLink::AppendString( std::u16string_view rStr ) +{ + if (!mxString) + return; + XclExpStringHelper::AppendString( *mxString, GetRoot(), rStr ); +} + +void XclExpChSourceLink::Save( XclExpStream& rStrm ) +{ + // CHFORMATRUNS record + if( mxString && mxString->IsRich() ) + { + std::size_t nRecSize = (1 + mxString->GetFormatsCount()) * ((GetBiff() == EXC_BIFF8) ? 2 : 1); + rStrm.StartRecord( EXC_ID_CHFORMATRUNS, nRecSize ); + mxString->WriteFormats( rStrm, true ); + rStrm.EndRecord(); + } + // CHSOURCELINK record + XclExpRecord::Save( rStrm ); + // CHSTRING record + if( mxString && !mxString->IsEmpty() ) + { + rStrm.StartRecord( EXC_ID_CHSTRING, 2 + mxString->GetSize() ); + rStrm << sal_uInt16( 0 ) << *mxString; + rStrm.EndRecord(); + } +} + +void XclExpChSourceLink::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnDestType + << maData.mnLinkType + << maData.mnFlags + << maData.mnNumFmtIdx + << mxLinkFmla; +} + +// Text ======================================================================= + +XclExpChFont::XclExpChFont( sal_uInt16 nFontIdx ) : + XclExpUInt16Record( EXC_ID_CHFONT, nFontIdx ) +{ +} + +XclExpChObjectLink::XclExpChObjectLink( sal_uInt16 nLinkTarget, const XclChDataPointPos& rPointPos ) : + XclExpRecord( EXC_ID_CHOBJECTLINK, 6 ) +{ + maData.mnTarget = nLinkTarget; + maData.maPointPos = rPointPos; +} + +void XclExpChObjectLink::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnTarget << maData.maPointPos.mnSeriesIdx << maData.maPointPos.mnPointIdx; +} + +XclExpChFrLabelProps::XclExpChFrLabelProps( const XclExpChRoot& rRoot ) : + XclExpChFutureRecordBase( rRoot, EXC_FUTUREREC_UNUSEDREF, EXC_ID_CHFRLABELPROPS, 4 ) +{ +} + +void XclExpChFrLabelProps::Convert( const ScfPropertySet& rPropSet, + bool bShowCateg, bool bShowValue, bool bShowPercent, bool bShowBubble ) +{ + // label value flags + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWSERIES, false ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWCATEG, bShowCateg ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWVALUE, bShowValue ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWPERCENT, bShowPercent ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWBUBBLE, bShowBubble ); + + // label value separator + maData.maSeparator = rPropSet.GetStringProperty( EXC_CHPROP_LABELSEPARATOR ); + if( maData.maSeparator.isEmpty() ) + maData.maSeparator = " "; +} + +void XclExpChFrLabelProps::WriteBody( XclExpStream& rStrm ) +{ + XclExpString aXclSep( maData.maSeparator, XclStrFlags::ForceUnicode | XclStrFlags::SmartFlags ); + rStrm << maData.mnFlags << aXclSep; +} + +XclExpChFontBase::~XclExpChFontBase() +{ +} + +void XclExpChFontBase::ConvertFontBase( const XclExpChRoot& rRoot, sal_uInt16 nFontIdx ) +{ + if( const XclExpFont* pFont = rRoot.GetFontBuffer().GetFont( nFontIdx ) ) + { + XclExpChFontRef xFont = new XclExpChFont( nFontIdx ); + SetFont( xFont, pFont->GetFontData().maColor, pFont->GetFontColorId() ); + } +} + +void XclExpChFontBase::ConvertFontBase( const XclExpChRoot& rRoot, const ScfPropertySet& rPropSet ) +{ + ConvertFontBase( rRoot, rRoot.ConvertFont( rPropSet, rRoot.GetDefApiScript() ) ); +} + +void XclExpChFontBase::ConvertRotationBase(const ScfPropertySet& rPropSet, bool bSupportsStacked ) +{ + sal_uInt16 nRotation = XclChPropSetHelper::ReadRotationProperties( rPropSet, bSupportsStacked ); + SetRotation( nRotation ); +} + +XclExpChText::XclExpChText( const XclExpChRoot& rRoot ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_TEXT, EXC_ID_CHTEXT, (rRoot.GetBiff() == EXC_BIFF8) ? 32 : 26 ), + mnTextColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +void XclExpChText::SetFont( XclExpChFontRef xFont, const Color& rColor, sal_uInt32 nColorId ) +{ + mxFont = xFont; + maData.maTextColor = rColor; + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR, rColor == COL_AUTO ); + mnTextColorId = nColorId; +} + +void XclExpChText::SetRotation( sal_uInt16 nRotation ) +{ + maData.mnRotation = nRotation; + ::insert_value( maData.mnFlags, XclTools::GetXclOrientFromRot( nRotation ), 8, 3 ); +} + +void XclExpChText::ConvertTitle( Reference< XTitle > const & xTitle, sal_uInt16 nTarget, const OUString* pSubTitle ) +{ + switch( nTarget ) + { + case EXC_CHOBJLINK_TITLE: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_TITLE ); break; + case EXC_CHOBJLINK_YAXIS: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_AXISTITLE, 1 ); break; + case EXC_CHOBJLINK_XAXIS: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_AXISTITLE ); break; + case EXC_CHOBJLINK_ZAXIS: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_AXISTITLE, 2 ); break; + } + + mxSrcLink.clear(); + mxObjLink = new XclExpChObjectLink( nTarget, XclChDataPointPos( 0, 0 ) ); + + if( xTitle.is() ) + { + // title frame formatting + ScfPropertySet aTitleProp( xTitle ); + mxFrame = lclCreateFrame( GetChRoot(), aTitleProp, EXC_CHOBJTYPE_TEXT ); + + // string sequence + mxSrcLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + sal_uInt16 nFontIdx = mxSrcLink->ConvertStringSequence( xTitle->getText() ); + if (pSubTitle) + { + // append subtitle as the 2nd line of the title. + OUString aSubTitle = "\n" + *pSubTitle; + mxSrcLink->AppendString(aSubTitle); + } + + ConvertFontBase( GetChRoot(), nFontIdx ); + + // rotation + ConvertRotationBase( aTitleProp, true ); + + // manual text position - only for main title + mxFramePos = new XclExpChFramePos( EXC_CHFRAMEPOS_PARENT ); + if( nTarget == EXC_CHOBJLINK_TITLE ) + { + Any aRelPos; + if( aTitleProp.GetAnyProperty( aRelPos, EXC_CHPROP_RELATIVEPOSITION ) && aRelPos.has< RelativePosition >() ) try + { + // calculate absolute position for CHTEXT record + Reference< cssc::XChartDocument > xChart1Doc( GetChartDocument(), UNO_QUERY_THROW ); + Reference< XShape > xTitleShape( xChart1Doc->getTitle(), UNO_SET_THROW ); + css::awt::Point aPos = xTitleShape->getPosition(); + css::awt::Size aSize = xTitleShape->getSize(); + css::awt::Rectangle aRect( aPos.X, aPos.Y, aSize.Width, aSize.Height ); + maData.maRect = CalcChartRectFromHmm( aRect ); + ::insert_value( maData.mnFlags2, EXC_CHTEXT_POS_MOVED, 0, 4 ); + // manual title position implies manual plot area + GetChartData().SetManualPlotArea(); + // calculate the default title position in chart units + sal_Int32 nDefPosX = ::std::max< sal_Int32 >( (EXC_CHART_TOTALUNITS - maData.maRect.mnWidth) / 2, 0 ); + sal_Int32 nDefPosY = 85; + // set the position relative to the standard position + XclChRectangle& rFrameRect = mxFramePos->GetFramePosData().maRect; + rFrameRect.mnX = maData.maRect.mnX - nDefPosX; + rFrameRect.mnY = maData.maRect.mnY - nDefPosY; + } + catch( Exception& ) + { + } + } + } + else + { + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED ); + } +} + +void XclExpChText::ConvertLegend( const ScfPropertySet& rPropSet ) +{ + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOTEXT ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOGEN ); + ConvertFontBase( GetChRoot(), rPropSet ); +} + +bool XclExpChText::ConvertDataLabel( const ScfPropertySet& rPropSet, + const XclChTypeInfo& rTypeInfo, const XclChDataPointPos& rPointPos ) +{ + SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_DATALABEL, rPointPos.mnPointIdx, rPointPos.mnSeriesIdx ); + + cssc2::DataPointLabel aPointLabel; + if( !rPropSet.GetProperty( aPointLabel, EXC_CHPROP_LABEL ) ) + return false; + + // percentage only allowed in pie and donut charts + bool bIsPie = rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE; + // bubble sizes only allowed in bubble charts + bool bIsBubble = rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES; + OSL_ENSURE( (GetBiff() == EXC_BIFF8) || !bIsBubble, "XclExpChText::ConvertDataLabel - bubble charts only in BIFF8" ); + + // raw show flags + bool bShowValue = !bIsBubble && aPointLabel.ShowNumber; // Chart2 uses 'ShowNumber' for bubble size + bool bShowPercent = bIsPie && aPointLabel.ShowNumberInPercent; // percentage only in pie/donut charts + bool bShowCateg = aPointLabel.ShowCategoryName; + bool bShowBubble = bIsBubble && aPointLabel.ShowNumber; // Chart2 uses 'ShowNumber' for bubble size + bool bShowAny = bShowValue || bShowPercent || bShowCateg || bShowBubble; + + // create the CHFRLABELPROPS record for extended settings in BIFF8 + if( bShowAny && (GetBiff() == EXC_BIFF8) ) + { + mxLabelProps = new XclExpChFrLabelProps( GetChRoot() ); + mxLabelProps->Convert( rPropSet, bShowCateg, bShowValue, bShowPercent, bShowBubble ); + } + + // restrict to combinations allowed in CHTEXT + if( bShowPercent ) bShowValue = false; // percent wins over value + if( bShowValue ) bShowCateg = false; // value wins over category + if( bShowValue || bShowCateg ) bShowBubble = false; // value or category wins over bubble size + + // set all flags + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOTEXT ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE, bShowValue ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT, bShowPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG, bShowCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC, bShowPercent && bShowCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWBUBBLE, bShowBubble ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWSYMBOL, bShowAny && aPointLabel.ShowLegendSymbol ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED, !bShowAny ); + + if( bShowAny ) + { + // font settings + ConvertFontBase( GetChRoot(), rPropSet ); + ConvertRotationBase( rPropSet, false ); + // label placement + sal_Int32 nPlacement = 0; + sal_uInt16 nLabelPos = EXC_CHTEXT_POS_AUTO; + if( rPropSet.GetProperty( nPlacement, EXC_CHPROP_LABELPLACEMENT ) ) + { + using namespace cssc::DataLabelPlacement; + if( nPlacement == rTypeInfo.mnDefaultLabelPos ) + { + nLabelPos = EXC_CHTEXT_POS_DEFAULT; + } + else switch( nPlacement ) + { + case AVOID_OVERLAP: nLabelPos = EXC_CHTEXT_POS_AUTO; break; + case CENTER: nLabelPos = EXC_CHTEXT_POS_CENTER; break; + case TOP: nLabelPos = EXC_CHTEXT_POS_ABOVE; break; + case TOP_LEFT: nLabelPos = EXC_CHTEXT_POS_LEFT; break; + case LEFT: nLabelPos = EXC_CHTEXT_POS_LEFT; break; + case BOTTOM_LEFT: nLabelPos = EXC_CHTEXT_POS_LEFT; break; + case BOTTOM: nLabelPos = EXC_CHTEXT_POS_BELOW; break; + case BOTTOM_RIGHT: nLabelPos = EXC_CHTEXT_POS_RIGHT; break; + case RIGHT: nLabelPos = EXC_CHTEXT_POS_RIGHT; break; + case TOP_RIGHT: nLabelPos = EXC_CHTEXT_POS_RIGHT; break; + case INSIDE: nLabelPos = EXC_CHTEXT_POS_INSIDE; break; + case OUTSIDE: nLabelPos = EXC_CHTEXT_POS_OUTSIDE; break; + case NEAR_ORIGIN: nLabelPos = EXC_CHTEXT_POS_AXIS; break; + default: OSL_FAIL( "XclExpChText::ConvertDataLabel - unknown label placement type" ); + } + } + ::insert_value( maData.mnFlags2, nLabelPos, 0, 4 ); + // source link (contains number format) + mxSrcLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + if( bShowValue || bShowPercent ) + // percentage format wins over value format + mxSrcLink->ConvertNumFmt( rPropSet, bShowPercent ); + // object link + mxObjLink = new XclExpChObjectLink( EXC_CHOBJLINK_DATA, rPointPos ); + } + + /* Return true to indicate valid label settings: + - for existing labels at entire series + - for any settings at single data point (to be able to delete a point label) */ + return bShowAny || (rPointPos.mnPointIdx != EXC_CHDATAFORMAT_ALLPOINTS); +} + +void XclExpChText::ConvertTrendLineEquation( const ScfPropertySet& rPropSet, const XclChDataPointPos& rPointPos ) +{ + // required flags + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOTEXT ); + if( GetBiff() == EXC_BIFF8 ) + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG ); // must set this to make equation visible in Excel + // frame formatting + mxFrame = lclCreateFrame( GetChRoot(), rPropSet, EXC_CHOBJTYPE_TEXT ); + // font settings + maData.mnHAlign = EXC_CHTEXT_ALIGN_TOPLEFT; + maData.mnVAlign = EXC_CHTEXT_ALIGN_TOPLEFT; + ConvertFontBase( GetChRoot(), rPropSet ); + // source link (contains number format) + mxSrcLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + mxSrcLink->ConvertNumFmt( rPropSet, false ); + // object link + mxObjLink = new XclExpChObjectLink( EXC_CHOBJLINK_DATA, rPointPos ); +} + +sal_uInt16 XclExpChText::GetAttLabelFlags() const +{ + sal_uInt16 nFlags = 0; + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWVALUE, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE ) ); + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWPERCENT, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT ) ); + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWCATEGPERC, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC ) ); + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWCATEG, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG ) ); + return nFlags; +} + +void XclExpChText::WriteSubRecords( XclExpStream& rStrm ) +{ + // CHFRAMEPOS record + lclSaveRecord( rStrm, mxFramePos ); + // CHFONT record + lclSaveRecord( rStrm, mxFont ); + // CHSOURCELINK group + lclSaveRecord( rStrm, mxSrcLink ); + // CHFRAME group + lclSaveRecord( rStrm, mxFrame ); + // CHOBJECTLINK record + lclSaveRecord( rStrm, mxObjLink ); + // CHFRLABELPROPS record + lclSaveRecord( rStrm, mxLabelProps ); +} + +void XclExpChText::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnHAlign + << maData.mnVAlign + << maData.mnBackMode + << maData.maTextColor + << maData.maRect + << maData.mnFlags; + + if( GetBiff() == EXC_BIFF8 ) + { + rStrm << GetPalette().GetColorIndex( mnTextColorId ) + << maData.mnFlags2 + << maData.mnRotation; + } +} + +namespace { + +/** Creates and returns an Excel text object from the passed title. */ +XclExpChTextRef lclCreateTitle( const XclExpChRoot& rRoot, Reference< XTitled > const & xTitled, sal_uInt16 nTarget, + const OUString* pSubTitle = nullptr ) +{ + Reference< XTitle > xTitle; + if( xTitled.is() ) + xTitle = xTitled->getTitleObject(); + + XclExpChTextRef xText = new XclExpChText( rRoot ); + xText->ConvertTitle( xTitle, nTarget, pSubTitle ); + /* Do not delete the CHTEXT group for the main title. A missing CHTEXT + will be interpreted as auto-generated title showing the series title in + charts that contain exactly one data series. */ + if( (nTarget != EXC_CHOBJLINK_TITLE) && !xText->HasString() ) + xText.clear(); + + return xText; +} + +} + +// Data series ================================================================ + +XclExpChMarkerFormat::XclExpChMarkerFormat( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHMARKERFORMAT, (rRoot.GetBiff() == EXC_BIFF8) ? 20 : 12 ), + mnLineColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ), + mnFillColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ) +{ +} + +void XclExpChMarkerFormat::Convert( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) +{ + XclChPropSetHelper::ReadMarkerProperties( maData, rPropSet, nFormatIdx ); + /* Set marker line/fill color to series line color. + TODO: remove this if OOChart supports own colors in markers. */ + Color aLineColor; + if( rPropSet.GetColorProperty( aLineColor, EXC_CHPROP_COLOR ) ) + maData.maLineColor = maData.maFillColor = aLineColor; + // register colors in palette + RegisterColors( rRoot ); +} + +void XclExpChMarkerFormat::ConvertStockSymbol( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, bool bCloseSymbol ) +{ + // clear the automatic flag + ::set_flag( maData.mnFlags, EXC_CHMARKERFORMAT_AUTO, false ); + // symbol type and color + if( bCloseSymbol ) + { + // set symbol type for the 'close' data series + maData.mnMarkerType = EXC_CHMARKERFORMAT_DOWJ; + maData.mnMarkerSize = EXC_CHMARKERFORMAT_DOUBLESIZE; + // set symbol line/fill color to series line color + Color aLineColor; + if( rPropSet.GetColorProperty( aLineColor, EXC_CHPROP_COLOR ) ) + { + maData.maLineColor = maData.maFillColor = aLineColor; + RegisterColors( rRoot ); + } + } + else + { + // set invisible symbol + maData.mnMarkerType = EXC_CHMARKERFORMAT_NOSYMBOL; + } +} + +void XclExpChMarkerFormat::RegisterColors( const XclExpChRoot& rRoot ) +{ + if( HasMarker() ) + { + if( HasLineColor() ) + mnLineColorId = rRoot.GetPalette().InsertColor( maData.maLineColor, EXC_COLOR_CHARTLINE ); + if( HasFillColor() ) + mnFillColorId = rRoot.GetPalette().InsertColor( maData.maFillColor, EXC_COLOR_CHARTAREA ); + } +} + +void XclExpChMarkerFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maLineColor << maData.maFillColor << maData.mnMarkerType << maData.mnFlags; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + const XclExpPalette& rPal = rStrm.GetRoot().GetPalette(); + rStrm << rPal.GetColorIndex( mnLineColorId ) << rPal.GetColorIndex( mnFillColorId ) << maData.mnMarkerSize; + } +} + +XclExpChPieFormat::XclExpChPieFormat() : + XclExpUInt16Record( EXC_ID_CHPIEFORMAT, 0 ) +{ +} + +void XclExpChPieFormat::Convert( const ScfPropertySet& rPropSet ) +{ + double fApiDist(0.0); + if( rPropSet.GetProperty( fApiDist, EXC_CHPROP_OFFSET ) ) + SetValue( limit_cast< sal_uInt16 >( fApiDist * 100.0, 0, 100 ) ); +} + +XclExpCh3dDataFormat::XclExpCh3dDataFormat() : + XclExpRecord( EXC_ID_CH3DDATAFORMAT, 2 ) +{ +} + +void XclExpCh3dDataFormat::Convert( const ScfPropertySet& rPropSet ) +{ + sal_Int32 nApiType(0); + if( !rPropSet.GetProperty( nApiType, EXC_CHPROP_GEOMETRY3D ) ) + return; + + using namespace cssc2::DataPointGeometry3D; + switch( nApiType ) + { + case CUBOID: + maData.mnBase = EXC_CH3DDATAFORMAT_RECT; + maData.mnTop = EXC_CH3DDATAFORMAT_STRAIGHT; + break; + case PYRAMID: + maData.mnBase = EXC_CH3DDATAFORMAT_RECT; + maData.mnTop = EXC_CH3DDATAFORMAT_SHARP; + break; + case CYLINDER: + maData.mnBase = EXC_CH3DDATAFORMAT_CIRC; + maData.mnTop = EXC_CH3DDATAFORMAT_STRAIGHT; + break; + case CONE: + maData.mnBase = EXC_CH3DDATAFORMAT_CIRC; + maData.mnTop = EXC_CH3DDATAFORMAT_SHARP; + break; + default: + OSL_FAIL( "XclExpCh3dDataFormat::Convert - unknown 3D bar format" ); + } +} + +void XclExpCh3dDataFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnBase << maData.mnTop; +} + +XclExpChAttachedLabel::XclExpChAttachedLabel( sal_uInt16 nFlags ) : + XclExpUInt16Record( EXC_ID_CHATTACHEDLABEL, nFlags ) +{ +} + +XclExpChDataFormat::XclExpChDataFormat( const XclExpChRoot& rRoot, + const XclChDataPointPos& rPointPos, sal_uInt16 nFormatIdx ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_DATAFORMAT, EXC_ID_CHDATAFORMAT, 8 ) +{ + maData.maPointPos = rPointPos; + maData.mnFormatIdx = nFormatIdx; +} + +void XclExpChDataFormat::ConvertDataSeries( const ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo ) +{ + // line and area formatting + ConvertFrameBase( GetChRoot(), rPropSet, rTypeInfo.GetSeriesObjectType() ); + + // data point symbols + bool bIsFrame = rTypeInfo.IsSeriesFrameFormat(); + if( !bIsFrame ) + { + mxMarkerFmt = new XclExpChMarkerFormat( GetChRoot() ); + mxMarkerFmt->Convert( GetChRoot(), rPropSet, maData.mnFormatIdx ); + } + + // pie segments + if( rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ) + { + mxPieFmt = new XclExpChPieFormat(); + mxPieFmt->Convert( rPropSet ); + } + + // 3D bars (only allowed for entire series in BIFF8) + if( IsSeriesFormat() && (GetBiff() == EXC_BIFF8) && rTypeInfo.mb3dChart && (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR) ) + { + mx3dDataFmt = new XclExpCh3dDataFormat(); + mx3dDataFmt->Convert( rPropSet ); + } + + // spline + if( IsSeriesFormat() && rTypeInfo.mbSpline && !bIsFrame ) + mxSeriesFmt = new XclExpUInt16Record( EXC_ID_CHSERIESFORMAT, EXC_CHSERIESFORMAT_SMOOTHED ); + + // data point labels + XclExpChTextRef xLabel = new XclExpChText( GetChRoot() ); + if( xLabel->ConvertDataLabel( rPropSet, rTypeInfo, maData.maPointPos ) ) + { + // CHTEXT groups for data labels are stored in global CHCHART group + GetChartData().SetDataLabel( xLabel ); + mxAttLabel = new XclExpChAttachedLabel( xLabel->GetAttLabelFlags() ); + } +} + +void XclExpChDataFormat::ConvertStockSeries( const ScfPropertySet& rPropSet, bool bCloseSymbol ) +{ + // set line format to invisible + SetDefaultFrameBase( GetChRoot(), EXC_CHFRAMETYPE_INVISIBLE, false ); + // set symbols to invisible or to 'close' series symbol + mxMarkerFmt = new XclExpChMarkerFormat( GetChRoot() ); + mxMarkerFmt->ConvertStockSymbol( GetChRoot(), rPropSet, bCloseSymbol ); +} + +void XclExpChDataFormat::ConvertLine( const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + ConvertFrameBase( GetChRoot(), rPropSet, eObjType ); +} + +void XclExpChDataFormat::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mx3dDataFmt ); + WriteFrameRecords( rStrm ); + lclSaveRecord( rStrm, mxPieFmt ); + lclSaveRecord( rStrm, mxMarkerFmt ); + lclSaveRecord( rStrm, mxSeriesFmt ); + lclSaveRecord( rStrm, mxAttLabel ); +} + +void XclExpChDataFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maPointPos.mnPointIdx + << maData.maPointPos.mnSeriesIdx + << maData.mnFormatIdx + << maData.mnFlags; +} + +XclExpChSerTrendLine::XclExpChSerTrendLine( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHSERTRENDLINE, 28 ), + XclExpChRoot( rRoot ) +{ +} + +bool XclExpChSerTrendLine::Convert( Reference< XRegressionCurve > const & xRegCurve, sal_uInt16 nSeriesIdx ) +{ + if( !xRegCurve.is() ) + return false; + + // trend line type + ScfPropertySet aCurveProp( xRegCurve ); + + OUString aService = aCurveProp.GetServiceName(); + if( aService == "com.sun.star.chart2.LinearRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_POLYNOMIAL; + maData.mnOrder = 1; + } + else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_EXPONENTIAL; + } + else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_LOGARITHMIC; + } + else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_POWER; + } + else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_POLYNOMIAL; + sal_Int32 aDegree; + aCurveProp.GetProperty(aDegree, EXC_CHPROP_POLYNOMIAL_DEGREE); + maData.mnOrder = static_cast<sal_uInt8> (aDegree); + } + else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_MOVING_AVG; + sal_Int32 aPeriod; + aCurveProp.GetProperty(aPeriod, EXC_CHPROP_MOVING_AVERAGE_PERIOD); + maData.mnOrder = static_cast<sal_uInt8> (aPeriod); + } + else + { + return false; + } + + aCurveProp.GetProperty(maData.mfForecastFor, EXC_CHPROP_EXTRAPOLATE_FORWARD); + aCurveProp.GetProperty(maData.mfForecastBack, EXC_CHPROP_EXTRAPOLATE_BACKWARD); + bool bIsForceIntercept = false; + aCurveProp.GetProperty(bIsForceIntercept, EXC_CHPROP_FORCE_INTERCEPT); + if (bIsForceIntercept) + aCurveProp.GetProperty(maData.mfIntercept, EXC_CHPROP_INTERCEPT_VALUE); + + // line formatting + XclChDataPointPos aPointPos( nSeriesIdx ); + mxDataFmt = new XclExpChDataFormat( GetChRoot(), aPointPos, 0 ); + mxDataFmt->ConvertLine( aCurveProp, EXC_CHOBJTYPE_TRENDLINE ); + + // #i83100# show equation and correlation coefficient + ScfPropertySet aEquationProp( xRegCurve->getEquationProperties() ); + maData.mnShowEquation = aEquationProp.GetBoolProperty( EXC_CHPROP_SHOWEQUATION ) ? 1 : 0; + maData.mnShowRSquared = aEquationProp.GetBoolProperty( EXC_CHPROP_SHOWCORRELATION ) ? 1 : 0; + + // #i83100# formatting of the equation text box + if( (maData.mnShowEquation != 0) || (maData.mnShowRSquared != 0) ) + { + mxLabel = new XclExpChText( GetChRoot() ); + mxLabel->ConvertTrendLineEquation( aEquationProp, aPointPos ); + } + + // missing features + // #i5085# manual trend line size + // #i34093# manual crossing point + return true; +} + +void XclExpChSerTrendLine::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnLineType + << maData.mnOrder + << maData.mfIntercept + << maData.mnShowEquation + << maData.mnShowRSquared + << maData.mfForecastFor + << maData.mfForecastBack; +} + +XclExpChSerErrorBar::XclExpChSerErrorBar( const XclExpChRoot& rRoot, sal_uInt8 nBarType ) : + XclExpRecord( EXC_ID_CHSERERRORBAR, 14 ), + XclExpChRoot( rRoot ) +{ + maData.mnBarType = nBarType; +} + +bool XclExpChSerErrorBar::Convert( XclExpChSourceLink& rValueLink, sal_uInt16& rnValueCount, const ScfPropertySet& rPropSet ) +{ + sal_Int32 nBarStyle = 0; + bool bOk = rPropSet.GetProperty( nBarStyle, EXC_CHPROP_ERRORBARSTYLE ); + if( bOk ) + { + switch( nBarStyle ) + { + case cssc::ErrorBarStyle::ABSOLUTE: + maData.mnSourceType = EXC_CHSERERR_FIXED; + rPropSet.GetProperty( maData.mfValue, EXC_CHPROP_POSITIVEERROR ); + break; + case cssc::ErrorBarStyle::RELATIVE: + maData.mnSourceType = EXC_CHSERERR_PERCENT; + rPropSet.GetProperty( maData.mfValue, EXC_CHPROP_POSITIVEERROR ); + break; + case cssc::ErrorBarStyle::STANDARD_DEVIATION: + maData.mnSourceType = EXC_CHSERERR_STDDEV; + rPropSet.GetProperty( maData.mfValue, EXC_CHPROP_WEIGHT ); + break; + case cssc::ErrorBarStyle::STANDARD_ERROR: + maData.mnSourceType = EXC_CHSERERR_STDERR; + break; + case cssc::ErrorBarStyle::FROM_DATA: + { + bOk = false; + maData.mnSourceType = EXC_CHSERERR_CUSTOM; + Reference< XDataSource > xDataSource( rPropSet.GetApiPropertySet(), UNO_QUERY ); + if( xDataSource.is() ) + { + // find first sequence with current role + OUString aRole = XclChartHelper::GetErrorBarValuesRole( maData.mnBarType ); + Reference< XDataSequence > xValueSeq; + + const Sequence< Reference< XLabeledDataSequence > > aLabeledSeqVec = xDataSource->getDataSequences(); + for( const Reference< XLabeledDataSequence >& rLabeledSeq : aLabeledSeqVec ) + { + Reference< XDataSequence > xTmpValueSeq = rLabeledSeq->getValues(); + ScfPropertySet aValueProp( xTmpValueSeq ); + OUString aCurrRole; + if( aValueProp.GetProperty( aCurrRole, EXC_CHPROP_ROLE ) && (aCurrRole == aRole) ) + { + xValueSeq = xTmpValueSeq; + break; + } + } + if( xValueSeq.is() ) + { + // #i86465# pass value count back to series + rnValueCount = maData.mnValueCount = rValueLink.ConvertDataSequence( xValueSeq, true ); + bOk = maData.mnValueCount > 0; + } + } + } + break; + default: + bOk = false; + } + } + return bOk; +} + +void XclExpChSerErrorBar::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnBarType + << maData.mnSourceType + << maData.mnLineEnd + << sal_uInt8( 1 ) // must be 1 to make line visible + << maData.mfValue + << maData.mnValueCount; +} + +namespace { + +/** Returns the property set of the specified data point. */ +ScfPropertySet lclGetPointPropSet( Reference< XDataSeries > const & xDataSeries, sal_Int32 nPointIdx ) +{ + ScfPropertySet aPropSet; + try + { + aPropSet.Set( xDataSeries->getDataPointByIndex( nPointIdx ) ); + } + catch( Exception& ) + { + OSL_FAIL( "lclGetPointPropSet - no data point property set" ); + } + return aPropSet; +} + +} // namespace + +XclExpChSeries::XclExpChSeries( const XclExpChRoot& rRoot, sal_uInt16 nSeriesIdx ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_SERIES, EXC_ID_CHSERIES, (rRoot.GetBiff() == EXC_BIFF8) ? 12 : 8 ), + mnGroupIdx( EXC_CHSERGROUP_NONE ), + mnSeriesIdx( nSeriesIdx ), + mnParentIdx( EXC_CHSERIES_INVALID ) +{ + // CHSOURCELINK records are always required, even if unused + mxTitleLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + mxValueLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_VALUES ); + mxCategLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_CATEGORY ); + if( GetBiff() == EXC_BIFF8 ) + mxBubbleLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_BUBBLES ); +} + +bool XclExpChSeries::ConvertDataSeries( + Reference< XDiagram > const & xDiagram, Reference< XDataSeries > const & xDataSeries, + const XclChExtTypeInfo& rTypeInfo, sal_uInt16 nGroupIdx, sal_uInt16 nFormatIdx ) +{ + bool bOk = false; + Reference< XDataSource > xDataSource( xDataSeries, UNO_QUERY ); + if( xDataSource.is() ) + { + Reference< XDataSequence > xYValueSeq, xTitleSeq, xXValueSeq, xBubbleSeq; + + // find first sequence with role 'values-y' + const Sequence< Reference< XLabeledDataSequence > > aLabeledSeqVec = xDataSource->getDataSequences(); + for( const Reference< XLabeledDataSequence >& rLabeledSeq : aLabeledSeqVec ) + { + Reference< XDataSequence > xTmpValueSeq = rLabeledSeq->getValues(); + ScfPropertySet aValueProp( xTmpValueSeq ); + OUString aRole; + if( aValueProp.GetProperty( aRole, EXC_CHPROP_ROLE ) ) + { + if( !xYValueSeq.is() && (aRole == EXC_CHPROP_ROLE_YVALUES) ) + { + xYValueSeq = xTmpValueSeq; + if( !xTitleSeq.is() ) + xTitleSeq = rLabeledSeq->getLabel(); // ignore role of label sequence + } + else if( !xXValueSeq.is() && !rTypeInfo.mbCategoryAxis && (aRole == EXC_CHPROP_ROLE_XVALUES) ) + { + xXValueSeq = xTmpValueSeq; + } + else if( !xBubbleSeq.is() && (rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES) && (aRole == EXC_CHPROP_ROLE_SIZEVALUES) ) + { + xBubbleSeq = xTmpValueSeq; + xTitleSeq = rLabeledSeq->getLabel(); // ignore role of label sequence + } + } + } + + bOk = xYValueSeq.is(); + if( bOk ) + { + // chart type group index + mnGroupIdx = nGroupIdx; + + // convert source links + maData.mnValueCount = mxValueLink->ConvertDataSequence( xYValueSeq, true ); + mxTitleLink->ConvertDataSequence( xTitleSeq, true ); + + // X values of XY charts + maData.mnCategCount = mxCategLink->ConvertDataSequence( xXValueSeq, false, maData.mnValueCount ); + + // size values of bubble charts + if( mxBubbleLink ) + mxBubbleLink->ConvertDataSequence( xBubbleSeq, false, maData.mnValueCount ); + + // series formatting + XclChDataPointPos aPointPos( mnSeriesIdx ); + ScfPropertySet aSeriesProp( xDataSeries ); + mxSeriesFmt = new XclExpChDataFormat( GetChRoot(), aPointPos, nFormatIdx ); + mxSeriesFmt->ConvertDataSeries( aSeriesProp, rTypeInfo ); + + // trend lines + CreateTrendLines( xDataSeries ); + + // error bars + CreateErrorBars( aSeriesProp, EXC_CHPROP_ERRORBARX, EXC_CHSERERR_XPLUS, EXC_CHSERERR_XMINUS ); + CreateErrorBars( aSeriesProp, EXC_CHPROP_ERRORBARY, EXC_CHSERERR_YPLUS, EXC_CHSERERR_YMINUS ); + + if( maData.mnValueCount > 0 ) + { + const sal_Int32 nMaxPointCount = maData.mnValueCount; + + /* #i91063# Create missing fill properties in pie/doughnut charts. + If freshly created (never saved to ODF), these charts show + varying point colors but do not return these points via API. */ + if( xDiagram.is() && (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE) ) + { + Reference< XColorScheme > xColorScheme = xDiagram->getDefaultColorScheme(); + if( xColorScheme.is() ) + { + static const OUStringLiteral aFillStyleName = u"FillStyle"; + static const OUStringLiteral aColorName = u"Color"; + namespace cssd = ::com::sun::star::drawing; + for( sal_Int32 nPointIdx = 0; nPointIdx < nMaxPointCount; ++nPointIdx ) + { + aPointPos.mnPointIdx = static_cast< sal_uInt16 >( nPointIdx ); + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIdx ); + // test that the point fill style is solid, but no color is set + cssd::FillStyle eFillStyle = cssd::FillStyle_NONE; + if( aPointProp.GetProperty( eFillStyle, aFillStyleName ) && + (eFillStyle == cssd::FillStyle_SOLID) && + !aPointProp.HasProperty( aColorName ) ) + { + aPointProp.SetProperty( aColorName, xColorScheme->getColorByIndex( nPointIdx ) ); + } + } + } + } + + // data point formatting + Sequence< sal_Int32 > aPointIndexes; + if( aSeriesProp.GetProperty( aPointIndexes, EXC_CHPROP_ATTRIBDATAPOINTS ) && aPointIndexes.hasElements() ) + { + for( const sal_Int32 nPointIndex : std::as_const(aPointIndexes) ) + { + if (nPointIndex >= nMaxPointCount) + break; + aPointPos.mnPointIdx = static_cast< sal_uInt16 >( nPointIndex ); + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIndex ); + XclExpChDataFormatRef xPointFmt = new XclExpChDataFormat( GetChRoot(), aPointPos, nFormatIdx ); + xPointFmt->ConvertDataSeries( aPointProp, rTypeInfo ); + maPointFmts.AppendRecord( xPointFmt ); + } + } + } + } + } + return bOk; +} + +bool XclExpChSeries::ConvertStockSeries( css::uno::Reference< css::chart2::XDataSeries > const & xDataSeries, + std::u16string_view rValueRole, sal_uInt16 nGroupIdx, sal_uInt16 nFormatIdx, bool bCloseSymbol ) +{ + bool bOk = false; + Reference< XDataSource > xDataSource( xDataSeries, UNO_QUERY ); + if( xDataSource.is() ) + { + Reference< XDataSequence > xYValueSeq, xTitleSeq; + + // find first sequence with passed role + const Sequence< Reference< XLabeledDataSequence > > aLabeledSeqVec = xDataSource->getDataSequences(); + for( const Reference< XLabeledDataSequence >& rLabeledSeq : aLabeledSeqVec ) + { + Reference< XDataSequence > xTmpValueSeq = rLabeledSeq->getValues(); + ScfPropertySet aValueProp( xTmpValueSeq ); + OUString aRole; + if( aValueProp.GetProperty( aRole, EXC_CHPROP_ROLE ) && (aRole == rValueRole) ) + { + xYValueSeq = xTmpValueSeq; + xTitleSeq = rLabeledSeq->getLabel(); // ignore role of label sequence + break; + } + } + + bOk = xYValueSeq.is(); + if( bOk ) + { + // chart type group index + mnGroupIdx = nGroupIdx; + // convert source links + maData.mnValueCount = mxValueLink->ConvertDataSequence( xYValueSeq, true ); + mxTitleLink->ConvertDataSequence( xTitleSeq, true ); + // series formatting + ScfPropertySet aSeriesProp( xDataSeries ); + mxSeriesFmt = new XclExpChDataFormat( GetChRoot(), XclChDataPointPos( mnSeriesIdx ), nFormatIdx ); + mxSeriesFmt->ConvertStockSeries( aSeriesProp, bCloseSymbol ); + } + } + return bOk; +} + +bool XclExpChSeries::ConvertTrendLine( const XclExpChSeries& rParent, Reference< XRegressionCurve > const & xRegCurve ) +{ + InitFromParent( rParent ); + + mxTrendLine = new XclExpChSerTrendLine( GetChRoot() ); + bool bOk = mxTrendLine->Convert( xRegCurve, mnSeriesIdx ); + if( bOk ) + { + OUString aName; + ScfPropertySet aProperties( xRegCurve ); + aProperties.GetProperty(aName, EXC_CHPROP_CURVENAME); + mxTitleLink->ConvertString(aName); + + mxSeriesFmt = mxTrendLine->GetDataFormat(); + GetChartData().SetDataLabel( mxTrendLine->GetDataLabel() ); + } + return bOk; +} + +bool XclExpChSeries::ConvertErrorBar( const XclExpChSeries& rParent, const ScfPropertySet& rPropSet, sal_uInt8 nBarId ) +{ + InitFromParent( rParent ); + // error bar settings + mxErrorBar = new XclExpChSerErrorBar( GetChRoot(), nBarId ); + bool bOk = mxErrorBar->Convert( *mxValueLink, maData.mnValueCount, rPropSet ); + if( bOk ) + { + // error bar formatting + mxSeriesFmt = new XclExpChDataFormat( GetChRoot(), XclChDataPointPos( mnSeriesIdx ), 0 ); + mxSeriesFmt->ConvertLine( rPropSet, EXC_CHOBJTYPE_ERRORBAR ); + } + return bOk; +} + +void XclExpChSeries::ConvertCategSequence( Reference< XLabeledDataSequence > const & xCategSeq ) +{ + if( xCategSeq.is() ) + maData.mnCategCount = mxCategLink->ConvertDataSequence( xCategSeq->getValues(), false ); +} + +void XclExpChSeries::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxTitleLink ); + lclSaveRecord( rStrm, mxValueLink ); + lclSaveRecord( rStrm, mxCategLink ); + lclSaveRecord( rStrm, mxBubbleLink ); + lclSaveRecord( rStrm, mxSeriesFmt ); + maPointFmts.Save( rStrm ); + if( mnGroupIdx != EXC_CHSERGROUP_NONE ) + XclExpUInt16Record( EXC_ID_CHSERGROUP, mnGroupIdx ).Save( rStrm ); + if( mnParentIdx != EXC_CHSERIES_INVALID ) + XclExpUInt16Record( EXC_ID_CHSERPARENT, mnParentIdx ).Save( rStrm ); + lclSaveRecord( rStrm, mxTrendLine ); + lclSaveRecord( rStrm, mxErrorBar ); +} + +void XclExpChSeries::InitFromParent( const XclExpChSeries& rParent ) +{ + // index to parent series is stored 1-based + mnParentIdx = rParent.mnSeriesIdx + 1; + /* #i86465# MSO2007 SP1 expects correct point counts in child series + (there was no problem in Excel2003 or Excel2007 without SP1...) */ + maData.mnCategCount = rParent.maData.mnCategCount; + maData.mnValueCount = rParent.maData.mnValueCount; +} + +void XclExpChSeries::CreateTrendLines( css::uno::Reference< css::chart2::XDataSeries > const & xDataSeries ) +{ + Reference< XRegressionCurveContainer > xRegCurveCont( xDataSeries, UNO_QUERY ); + if( xRegCurveCont.is() ) + { + const Sequence< Reference< XRegressionCurve > > aRegCurveSeq = xRegCurveCont->getRegressionCurves(); + for( const Reference< XRegressionCurve >& rRegCurve : aRegCurveSeq ) + { + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries && !xSeries->ConvertTrendLine( *this, rRegCurve ) ) + GetChartData().RemoveLastSeries(); + } + } +} + +void XclExpChSeries::CreateErrorBars( const ScfPropertySet& rPropSet, + const OUString& rBarPropName, sal_uInt8 nPosBarId, sal_uInt8 nNegBarId ) +{ + Reference< XPropertySet > xErrorBar; + if( rPropSet.GetProperty( xErrorBar, rBarPropName ) && xErrorBar.is() ) + { + ScfPropertySet aErrorProp( xErrorBar ); + CreateErrorBar( aErrorProp, EXC_CHPROP_SHOWPOSITIVEERROR, nPosBarId ); + CreateErrorBar( aErrorProp, EXC_CHPROP_SHOWNEGATIVEERROR, nNegBarId ); + } +} + +void XclExpChSeries::CreateErrorBar( const ScfPropertySet& rPropSet, + const OUString& rShowPropName, sal_uInt8 nBarId ) +{ + if( rPropSet.GetBoolProperty( rShowPropName ) ) + { + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries && !xSeries->ConvertErrorBar( *this, rPropSet, nBarId ) ) + GetChartData().RemoveLastSeries(); + } +} + +void XclExpChSeries::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnCategType << maData.mnValueType << maData.mnCategCount << maData.mnValueCount; + if( GetBiff() == EXC_BIFF8 ) + rStrm << maData.mnBubbleType << maData.mnBubbleCount; +} + +// Chart type groups ========================================================== + +XclExpChType::XclExpChType( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHUNKNOWN ), + XclExpChRoot( rRoot ), + maTypeInfo( rRoot.GetChartTypeInfo( EXC_CHTYPEID_UNKNOWN ) ) +{ +} + +void XclExpChType::Convert( Reference< XDiagram > const & xDiagram, Reference< XChartType > const & xChartType, + sal_Int32 nApiAxesSetIdx, bool bSwappedAxesSet, bool bHasXLabels ) +{ + if( !xChartType.is() ) + return; + + maTypeInfo = GetChartTypeInfo( xChartType->getChartType() ); + // special handling for some chart types + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_BAR: + { + maTypeInfo = GetChartTypeInfo( bSwappedAxesSet ? EXC_CHTYPEID_HORBAR : EXC_CHTYPEID_BAR ); + ::set_flag( maData.mnFlags, EXC_CHBAR_HORIZONTAL, bSwappedAxesSet ); + ScfPropertySet aTypeProp( xChartType ); + Sequence< sal_Int32 > aInt32Seq; + maData.mnOverlap = 0; + if( aTypeProp.GetProperty( aInt32Seq, EXC_CHPROP_OVERLAPSEQ ) && (nApiAxesSetIdx < aInt32Seq.getLength()) ) + maData.mnOverlap = limit_cast< sal_Int16 >( -aInt32Seq[ nApiAxesSetIdx ], -100, 100 ); + maData.mnGap = 150; + if( aTypeProp.GetProperty( aInt32Seq, EXC_CHPROP_GAPWIDTHSEQ ) && (nApiAxesSetIdx < aInt32Seq.getLength()) ) + maData.mnGap = limit_cast< sal_uInt16 >( aInt32Seq[ nApiAxesSetIdx ], 0, 500 ); + } + break; + case EXC_CHTYPECATEG_RADAR: + ::set_flag( maData.mnFlags, EXC_CHRADAR_AXISLABELS, bHasXLabels ); + break; + case EXC_CHTYPECATEG_PIE: + { + ScfPropertySet aTypeProp( xChartType ); + bool bDonut = aTypeProp.GetBoolProperty( EXC_CHPROP_USERINGS ); + maTypeInfo = GetChartTypeInfo( bDonut ? EXC_CHTYPEID_DONUT : EXC_CHTYPEID_PIE ); + maData.mnPieHole = bDonut ? 50 : 0; + // #i85166# starting angle of first pie slice + ScfPropertySet aDiaProp( xDiagram ); + maData.mnRotation = XclExpChRoot::ConvertPieRotation( aDiaProp ); + } + break; + case EXC_CHTYPECATEG_SCATTER: + if( GetBiff() == EXC_BIFF8 ) + ::set_flag( maData.mnFlags, EXC_CHSCATTER_BUBBLES, maTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ); + break; + default:; + } + SetRecId( maTypeInfo.mnRecId ); +} + +void XclExpChType::SetStacked( bool bPercent ) +{ + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + ::set_flag( maData.mnFlags, EXC_CHLINE_STACKED ); + ::set_flag( maData.mnFlags, EXC_CHLINE_PERCENT, bPercent ); + break; + case EXC_CHTYPECATEG_BAR: + ::set_flag( maData.mnFlags, EXC_CHBAR_STACKED ); + ::set_flag( maData.mnFlags, EXC_CHBAR_PERCENT, bPercent ); + maData.mnOverlap = -100; + break; + default:; + } +} + +void XclExpChType::WriteBody( XclExpStream& rStrm ) +{ + switch( GetRecId() ) + { + case EXC_ID_CHBAR: + rStrm << maData.mnOverlap << maData.mnGap << maData.mnFlags; + break; + + case EXC_ID_CHLINE: + case EXC_ID_CHAREA: + case EXC_ID_CHRADARLINE: + case EXC_ID_CHRADARAREA: + rStrm << maData.mnFlags; + break; + + case EXC_ID_CHPIE: + rStrm << maData.mnRotation << maData.mnPieHole; + if( GetBiff() == EXC_BIFF8 ) + rStrm << maData.mnFlags; + break; + + case EXC_ID_CHSCATTER: + if( GetBiff() == EXC_BIFF8 ) + rStrm << maData.mnBubbleSize << maData.mnBubbleType << maData.mnFlags; + break; + + default: + OSL_FAIL( "XclExpChType::WriteBody - unknown chart type" ); + } +} + +XclExpChChart3d::XclExpChChart3d() : + XclExpRecord( EXC_ID_CHCHART3D, 14 ) +{ +} + +void XclExpChChart3d::Convert( const ScfPropertySet& rPropSet, bool b3dWallChart ) +{ + sal_Int32 nRotationY = 0; + rPropSet.GetProperty( nRotationY, EXC_CHPROP_ROTATIONVERTICAL ); + sal_Int32 nRotationX = 0; + rPropSet.GetProperty( nRotationX, EXC_CHPROP_ROTATIONHORIZONTAL ); + sal_Int32 nPerspective = 15; + rPropSet.GetProperty( nPerspective, EXC_CHPROP_PERSPECTIVE ); + + if( b3dWallChart ) + { + // Y rotation (Excel [0..359], Chart2 [-179,180]) + if( nRotationY < 0 ) nRotationY += 360; + maData.mnRotation = static_cast< sal_uInt16 >( nRotationY ); + // X rotation a.k.a. elevation (Excel [-90..90], Chart2 [-179,180]) + maData.mnElevation = limit_cast< sal_Int16 >( nRotationX, -90, 90 ); + // perspective (Excel and Chart2 [0,100]) + maData.mnEyeDist = limit_cast< sal_uInt16 >( nPerspective, 0, 100 ); + // flags + maData.mnFlags = 0; + ::set_flag( maData.mnFlags, EXC_CHCHART3D_REAL3D, !rPropSet.GetBoolProperty( EXC_CHPROP_RIGHTANGLEDAXES ) ); + ::set_flag( maData.mnFlags, EXC_CHCHART3D_AUTOHEIGHT ); + ::set_flag( maData.mnFlags, EXC_CHCHART3D_HASWALLS ); + } + else + { + // Y rotation not used in pie charts, but 'first pie slice angle' + maData.mnRotation = XclExpChRoot::ConvertPieRotation( rPropSet ); + // X rotation a.k.a. elevation (map Chart2 [-80,-10] to Excel [10..80]) + maData.mnElevation = limit_cast< sal_Int16 >( (nRotationX + 270) % 180, 10, 80 ); + // perspective (Excel and Chart2 [0,100]) + maData.mnEyeDist = limit_cast< sal_uInt16 >( nPerspective, 0, 100 ); + // flags + maData.mnFlags = 0; + } +} + +void XclExpChChart3d::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnRotation + << maData.mnElevation + << maData.mnEyeDist + << maData.mnRelHeight + << maData.mnRelDepth + << maData.mnDepthGap + << maData.mnFlags; +} + +XclExpChLegend::XclExpChLegend( const XclExpChRoot& rRoot ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_LEGEND, EXC_ID_CHLEGEND, 20 ) +{ +} + +void XclExpChLegend::Convert( const ScfPropertySet& rPropSet ) +{ + // frame properties + mxFrame = lclCreateFrame( GetChRoot(), rPropSet, EXC_CHOBJTYPE_LEGEND ); + // text properties + mxText = new XclExpChText( GetChRoot() ); + mxText->ConvertLegend( rPropSet ); + + // legend position and size + Any aRelPosAny, aRelSizeAny; + rPropSet.GetAnyProperty( aRelPosAny, EXC_CHPROP_RELATIVEPOSITION ); + rPropSet.GetAnyProperty( aRelSizeAny, EXC_CHPROP_RELATIVESIZE ); + cssc::ChartLegendExpansion eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + rPropSet.GetProperty( eApiExpand, EXC_CHPROP_EXPANSION ); + if( aRelPosAny.has< RelativePosition >() || ((eApiExpand == cssc::ChartLegendExpansion_CUSTOM) && aRelSizeAny.has< RelativeSize >()) ) + { + try + { + /* The 'RelativePosition' or 'RelativeSize' properties are used as + indicator of manually changed legend position/size, but due to + the different anchor modes used by this property (in the + RelativePosition.Anchor member) it cannot be used to calculate + the position easily. For this, the Chart1 API will be used + instead. */ + Reference< cssc::XChartDocument > xChart1Doc( GetChartDocument(), UNO_QUERY_THROW ); + Reference< XShape > xChart1Legend( xChart1Doc->getLegend(), UNO_SET_THROW ); + // coordinates in CHLEGEND record written but not used by Excel + mxFramePos = new XclExpChFramePos( EXC_CHFRAMEPOS_CHARTSIZE ); + XclChFramePos& rFramePos = mxFramePos->GetFramePosData(); + rFramePos.mnTLMode = EXC_CHFRAMEPOS_CHARTSIZE; + css::awt::Point aLegendPos = xChart1Legend->getPosition(); + rFramePos.maRect.mnX = maData.maRect.mnX = CalcChartXFromHmm( aLegendPos.X ); + rFramePos.maRect.mnY = maData.maRect.mnY = CalcChartYFromHmm( aLegendPos.Y ); + // legend size, Excel expects points in CHFRAMEPOS record + rFramePos.mnBRMode = EXC_CHFRAMEPOS_ABSSIZE_POINTS; + css::awt::Size aLegendSize = xChart1Legend->getSize(); + rFramePos.maRect.mnWidth = o3tl::convert(aLegendSize.Width, o3tl::Length::mm100, o3tl::Length::pt); + rFramePos.maRect.mnHeight = o3tl::convert(aLegendSize.Height, o3tl::Length::mm100, o3tl::Length::pt); + maData.maRect.mnWidth = CalcChartXFromHmm( aLegendSize.Width ); + maData.maRect.mnHeight = CalcChartYFromHmm( aLegendSize.Height ); + eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + // manual legend position implies manual plot area + GetChartData().SetManualPlotArea(); + maData.mnDockMode = EXC_CHLEGEND_NOTDOCKED; + // a CHFRAME record with cleared auto flags is needed + if( !mxFrame ) + mxFrame = new XclExpChFrame( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + mxFrame->SetAutoFlags( false, false ); + } + catch( Exception& ) + { + OSL_FAIL( "XclExpChLegend::Convert - cannot get legend shape" ); + maData.mnDockMode = EXC_CHLEGEND_RIGHT; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + } + } + else + { + cssc2::LegendPosition eApiPos = cssc2::LegendPosition_LINE_END; + rPropSet.GetProperty( eApiPos, EXC_CHPROP_ANCHORPOSITION ); + switch( eApiPos ) + { + case cssc2::LegendPosition_LINE_START: maData.mnDockMode = EXC_CHLEGEND_LEFT; break; + case cssc2::LegendPosition_LINE_END: maData.mnDockMode = EXC_CHLEGEND_RIGHT; break; + case cssc2::LegendPosition_PAGE_START: maData.mnDockMode = EXC_CHLEGEND_TOP; break; + case cssc2::LegendPosition_PAGE_END: maData.mnDockMode = EXC_CHLEGEND_BOTTOM; break; + default: + OSL_FAIL( "XclExpChLegend::Convert - unrecognized legend position" ); + maData.mnDockMode = EXC_CHLEGEND_RIGHT; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + } + } + ::set_flag( maData.mnFlags, EXC_CHLEGEND_STACKED, eApiExpand == cssc::ChartLegendExpansion_HIGH ); + + // other flags + ::set_flag( maData.mnFlags, EXC_CHLEGEND_AUTOSERIES ); + const sal_uInt16 nAutoFlags = EXC_CHLEGEND_DOCKED | EXC_CHLEGEND_AUTOPOSX | EXC_CHLEGEND_AUTOPOSY; + ::set_flag( maData.mnFlags, nAutoFlags, maData.mnDockMode != EXC_CHLEGEND_NOTDOCKED ); +} + +void XclExpChLegend::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxFramePos ); + lclSaveRecord( rStrm, mxText ); + lclSaveRecord( rStrm, mxFrame ); +} + +void XclExpChLegend::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maRect << maData.mnDockMode << maData.mnSpacing << maData.mnFlags; +} + +XclExpChDropBar::XclExpChDropBar( const XclExpChRoot& rRoot, XclChObjectType eObjType ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_DROPBAR, EXC_ID_CHDROPBAR, 2 ), + meObjType( eObjType ) +{ +} + +void XclExpChDropBar::Convert( const ScfPropertySet& rPropSet ) +{ + if( rPropSet.Is() ) + ConvertFrameBase( GetChRoot(), rPropSet, meObjType ); + else + SetDefaultFrameBase( GetChRoot(), EXC_CHFRAMETYPE_INVISIBLE, true ); +} + +void XclExpChDropBar::WriteSubRecords( XclExpStream& rStrm ) +{ + WriteFrameRecords( rStrm ); +} + +void XclExpChDropBar::WriteBody( XclExpStream& rStrm ) +{ + rStrm << sal_uInt16(100); // Distance between bars (CHDROPBAR record). +} + +XclExpChTypeGroup::XclExpChTypeGroup( const XclExpChRoot& rRoot, sal_uInt16 nGroupIdx ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_TYPEGROUP, EXC_ID_CHTYPEGROUP, 20 ), + maType( rRoot ), + maTypeInfo( maType.GetTypeInfo() ) +{ + maData.mnGroupIdx = nGroupIdx; +} + +void XclExpChTypeGroup::ConvertType( + Reference< XDiagram > const & xDiagram, Reference< XChartType > const & xChartType, + sal_Int32 nApiAxesSetIdx, bool b3dChart, bool bSwappedAxesSet, bool bHasXLabels ) +{ + // chart type settings + maType.Convert( xDiagram, xChartType, nApiAxesSetIdx, bSwappedAxesSet, bHasXLabels ); + + // spline - TODO: get from single series (#i66858#) + ScfPropertySet aTypeProp( xChartType ); + cssc2::CurveStyle eCurveStyle; + bool bSpline = aTypeProp.GetProperty( eCurveStyle, EXC_CHPROP_CURVESTYLE ) && + (eCurveStyle != cssc2::CurveStyle_LINES); + + // extended type info + maTypeInfo.Set( maType.GetTypeInfo(), b3dChart, bSpline ); + + // 3d chart settings + if( maTypeInfo.mb3dChart ) // only true, if Excel chart supports 3d mode + { + mxChart3d = new XclExpChChart3d(); + ScfPropertySet aDiaProp( xDiagram ); + mxChart3d->Convert( aDiaProp, Is3dWallChart() ); + } +} + +void XclExpChTypeGroup::ConvertSeries( + Reference< XDiagram > const & xDiagram, Reference< XChartType > const & xChartType, + sal_Int32 nGroupAxesSetIdx, bool bPercent, bool bConnectBars ) +{ + Reference< XDataSeriesContainer > xSeriesCont( xChartType, UNO_QUERY ); + if( !xSeriesCont.is() ) + return; + + std::vector< Reference< XDataSeries > > aSeriesVec; + + // copy data series attached to the current axes set to the vector + const Sequence< Reference< XDataSeries > > aSeriesSeq = xSeriesCont->getDataSeries(); + for( const Reference< XDataSeries >& rSeries : aSeriesSeq ) + { + ScfPropertySet aSeriesProp( rSeries ); + sal_Int32 nSeriesAxesSetIdx(0); + if( aSeriesProp.GetProperty( nSeriesAxesSetIdx, EXC_CHPROP_ATTAXISINDEX ) && (nSeriesAxesSetIdx == nGroupAxesSetIdx) ) + aSeriesVec.push_back( rSeries ); + } + + // Are there any series in the current axes set? + if( aSeriesVec.empty() ) + return; + + // stacking direction (stacked/percent/deep 3d) from first series + ScfPropertySet aSeriesProp( aSeriesVec.front() ); + cssc2::StackingDirection eStacking; + if( !aSeriesProp.GetProperty( eStacking, EXC_CHPROP_STACKINGDIR ) ) + eStacking = cssc2::StackingDirection_NO_STACKING; + + // stacked or percent chart + if( maTypeInfo.mbSupportsStacking && (eStacking == cssc2::StackingDirection_Y_STACKING) ) + { + // percent overrides simple stacking + maType.SetStacked( bPercent ); + + // connected data points (only in stacked bar charts) + if (bConnectBars && (maTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR)) + { + sal_uInt16 nKey = EXC_CHCHARTLINE_CONNECT; + m_ChartLines.insert(std::make_pair(nKey, std::make_unique<XclExpChLineFormat>(GetChRoot()))); + } + } + else + { + // reverse series order for some unstacked 2D chart types + if( maTypeInfo.mbReverseSeries && !Is3dChart() ) + ::std::reverse( aSeriesVec.begin(), aSeriesVec.end() ); + } + + // deep 3d chart or clustered 3d chart (stacked is not clustered) + if( (eStacking == cssc2::StackingDirection_NO_STACKING) && Is3dWallChart() ) + mxChart3d->SetClustered(); + + // varied point colors + ::set_flag( maData.mnFlags, EXC_CHTYPEGROUP_VARIEDCOLORS, aSeriesProp.GetBoolProperty( EXC_CHPROP_VARYCOLORSBY ) ); + + // process all series + for( const auto& rxSeries : aSeriesVec ) + { + // create Excel series object, stock charts need special processing + if( maTypeInfo.meTypeId == EXC_CHTYPEID_STOCK ) + CreateAllStockSeries( xChartType, rxSeries ); + else + CreateDataSeries( xDiagram, rxSeries ); + } +} + +void XclExpChTypeGroup::ConvertCategSequence( Reference< XLabeledDataSequence > const & xCategSeq ) +{ + for( size_t nIdx = 0, nSize = maSeries.GetSize(); nIdx < nSize; ++nIdx ) + maSeries.GetRecord( nIdx )->ConvertCategSequence( xCategSeq ); +} + +void XclExpChTypeGroup::ConvertLegend( const ScfPropertySet& rPropSet ) +{ + if( rPropSet.GetBoolProperty( EXC_CHPROP_SHOW ) ) + { + mxLegend = new XclExpChLegend( GetChRoot() ); + mxLegend->Convert( rPropSet ); + } +} + +void XclExpChTypeGroup::WriteSubRecords( XclExpStream& rStrm ) +{ + maType.Save( rStrm ); + lclSaveRecord( rStrm, mxChart3d ); + lclSaveRecord( rStrm, mxLegend ); + lclSaveRecord( rStrm, mxUpBar ); + lclSaveRecord( rStrm, mxDownBar ); + for (auto const& it : m_ChartLines) + { + lclSaveRecord( rStrm, it.second.get(), EXC_ID_CHCHARTLINE, it.first ); + } +} + +sal_uInt16 XclExpChTypeGroup::GetFreeFormatIdx() const +{ + return static_cast< sal_uInt16 >( maSeries.GetSize() ); +} + +void XclExpChTypeGroup::CreateDataSeries( + Reference< XDiagram > const & xDiagram, Reference< XDataSeries > const & xDataSeries ) +{ + // let chart create series object with correct series index + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries ) + { + if( xSeries->ConvertDataSeries( xDiagram, xDataSeries, maTypeInfo, GetGroupIdx(), GetFreeFormatIdx() ) ) + maSeries.AppendRecord( xSeries ); + else + GetChartData().RemoveLastSeries(); + } +} + +void XclExpChTypeGroup::CreateAllStockSeries( + Reference< XChartType > const & xChartType, Reference< XDataSeries > const & xDataSeries ) +{ + // create existing series objects + bool bHasOpen = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_OPENVALUES, false ); + bool bHasHigh = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_HIGHVALUES, false ); + bool bHasLow = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_LOWVALUES, false ); + bool bHasClose = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_CLOSEVALUES, !bHasOpen ); + + // formatting of special stock chart elements + ScfPropertySet aTypeProp( xChartType ); + // hi-lo lines + if( bHasHigh && bHasLow && aTypeProp.GetBoolProperty( EXC_CHPROP_SHOWHIGHLOW ) ) + { + ScfPropertySet aSeriesProp( xDataSeries ); + XclExpChLineFormatRef xLineFmt = new XclExpChLineFormat( GetChRoot() ); + xLineFmt->Convert( GetChRoot(), aSeriesProp, EXC_CHOBJTYPE_HILOLINE ); + sal_uInt16 nKey = EXC_CHCHARTLINE_HILO; + m_ChartLines.insert(std::make_pair(nKey, std::make_unique<XclExpChLineFormat>(GetChRoot()))); + } + // dropbars + if( !(bHasOpen && bHasClose) ) + return; + + // dropbar type is dependent on position in the file - always create both + Reference< XPropertySet > xWhitePropSet, xBlackPropSet; + // white dropbar format + aTypeProp.GetProperty( xWhitePropSet, EXC_CHPROP_WHITEDAY ); + ScfPropertySet aWhiteProp( xWhitePropSet ); + mxUpBar = new XclExpChDropBar( GetChRoot(), EXC_CHOBJTYPE_WHITEDROPBAR ); + mxUpBar->Convert( aWhiteProp ); + // black dropbar format + aTypeProp.GetProperty( xBlackPropSet, EXC_CHPROP_BLACKDAY ); + ScfPropertySet aBlackProp( xBlackPropSet ); + mxDownBar = new XclExpChDropBar( GetChRoot(), EXC_CHOBJTYPE_BLACKDROPBAR ); + mxDownBar->Convert( aBlackProp ); +} + +bool XclExpChTypeGroup::CreateStockSeries( Reference< XDataSeries > const & xDataSeries, + std::u16string_view rValueRole, bool bCloseSymbol ) +{ + bool bOk = false; + // let chart create series object with correct series index + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries ) + { + bOk = xSeries->ConvertStockSeries( xDataSeries, + rValueRole, GetGroupIdx(), GetFreeFormatIdx(), bCloseSymbol ); + if( bOk ) + maSeries.AppendRecord( xSeries ); + else + GetChartData().RemoveLastSeries(); + } + return bOk; +} + +void XclExpChTypeGroup::WriteBody( XclExpStream& rStrm ) +{ + rStrm.WriteZeroBytes( 16 ); + rStrm << maData.mnFlags << maData.mnGroupIdx; +} + +// Axes ======================================================================= + +XclExpChLabelRange::XclExpChLabelRange( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHLABELRANGE, 8 ), + XclExpChRoot( rRoot ) +{ +} + +void XclExpChLabelRange::Convert( const ScaleData& rScaleData, const ScfPropertySet& rChart1Axis, bool bMirrorOrient ) +{ + /* Base time unit (using the property 'ExplicitTimeIncrement' from the old + chart API allows to detect axis type (date axis, if property exists), + and to receive the base time unit currently used in case the base time + unit is set to 'automatic'. */ + cssc::TimeIncrement aTimeIncrement; + if( rChart1Axis.GetProperty( aTimeIncrement, EXC_CHPROP_EXPTIMEINCREMENT ) ) + { + // property exists -> this is a date axis currently + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ); + + // automatic base time unit, if the UNO Any 'rScaleData.TimeIncrement.TimeResolution' does not contain a valid value... + bool bAutoBase = !rScaleData.TimeIncrement.TimeResolution.has< cssc::TimeIncrement >(); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOBASE, bAutoBase ); + + // ...but get the current base time unit from the property of the old chart API + sal_Int32 nApiTimeUnit = 0; + bool bValidBaseUnit = aTimeIncrement.TimeResolution >>= nApiTimeUnit; + OSL_ENSURE( bValidBaseUnit, "XclExpChLabelRange::Convert - cannot get base time unit" ); + maDateData.mnBaseUnit = bValidBaseUnit ? lclGetTimeUnit( nApiTimeUnit ) : EXC_CHDATERANGE_DAYS; + + /* Min/max values depend on base time unit, they specify the number of + days, months, or years starting from null date. */ + bool bAutoMin = lclConvertTimeValue( GetRoot(), maDateData.mnMinDate, rScaleData.Minimum, maDateData.mnBaseUnit ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMIN, bAutoMin ); + bool bAutoMax = lclConvertTimeValue( GetRoot(), maDateData.mnMaxDate, rScaleData.Maximum, maDateData.mnBaseUnit ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAX, bAutoMax ); + } + + // automatic axis type detection + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTODATE, rScaleData.AutoDateAxis ); + + // increment + bool bAutoMajor = lclConvertTimeInterval( maDateData.mnMajorStep, maDateData.mnMajorUnit, rScaleData.TimeIncrement.MajorTimeInterval ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAJOR, bAutoMajor ); + bool bAutoMinor = lclConvertTimeInterval( maDateData.mnMinorStep, maDateData.mnMinorUnit, rScaleData.TimeIncrement.MinorTimeInterval ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMINOR, bAutoMinor ); + + // origin + double fOrigin = 0.0; + if( !lclIsAutoAnyOrGetValue( fOrigin, rScaleData.Origin ) ) + maLabelData.mnCross = limit_cast< sal_uInt16 >( fOrigin, 1, 31999 ); + + // reverse order + if( (rScaleData.Orientation == cssc2::AxisOrientation_REVERSE) != bMirrorOrient ) + ::set_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_REVERSE ); +} + +void XclExpChLabelRange::ConvertAxisPosition( const ScfPropertySet& rPropSet ) +{ + cssc::ChartAxisPosition eAxisPos = cssc::ChartAxisPosition_VALUE; + rPropSet.GetProperty( eAxisPos, EXC_CHPROP_CROSSOVERPOSITION ); + double fCrossingPos = 1.0; + rPropSet.GetProperty( fCrossingPos, EXC_CHPROP_CROSSOVERVALUE ); + + bool bDateAxis = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ); + switch( eAxisPos ) + { + case cssc::ChartAxisPosition_ZERO: + case cssc::ChartAxisPosition_START: + maLabelData.mnCross = 1; + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + break; + case cssc::ChartAxisPosition_END: + ::set_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_MAXCROSS ); + break; + case cssc::ChartAxisPosition_VALUE: + maLabelData.mnCross = limit_cast< sal_uInt16 >( fCrossingPos, 1, 31999 ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS, false ); + if( bDateAxis ) + maDateData.mnCross = lclGetTimeValue( GetRoot(), fCrossingPos, maDateData.mnBaseUnit ); + break; + default: + maLabelData.mnCross = 1; + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + } +} + +void XclExpChLabelRange::Save( XclExpStream& rStrm ) +{ + // the CHLABELRANGE record + XclExpRecord::Save( rStrm ); + + // the CHDATERANGE record with date axis settings (BIFF8 only) + if( GetBiff() != EXC_BIFF8 ) + return; + + rStrm.StartRecord( EXC_ID_CHDATERANGE, 18 ); + rStrm << maDateData.mnMinDate + << maDateData.mnMaxDate + << maDateData.mnMajorStep + << maDateData.mnMajorUnit + << maDateData.mnMinorStep + << maDateData.mnMinorUnit + << maDateData.mnBaseUnit + << maDateData.mnCross + << maDateData.mnFlags; + rStrm.EndRecord(); +} + +void XclExpChLabelRange::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maLabelData.mnCross << maLabelData.mnLabelFreq << maLabelData.mnTickFreq << maLabelData.mnFlags; +} + +XclExpChValueRange::XclExpChValueRange( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHVALUERANGE, 42 ), + XclExpChRoot( rRoot ) +{ +} + +void XclExpChValueRange::Convert( const ScaleData& rScaleData ) +{ + // scaling algorithm + bool bLogScale = ScfApiHelper::GetServiceName( rScaleData.Scaling ) == "com.sun.star.chart2.LogarithmicScaling"; + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE, bLogScale ); + + // min/max + bool bAutoMin = lclIsAutoAnyOrGetScaledValue( maData.mfMin, rScaleData.Minimum, bLogScale ); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMIN, bAutoMin ); + bool bAutoMax = lclIsAutoAnyOrGetScaledValue( maData.mfMax, rScaleData.Maximum, bLogScale ); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAX, bAutoMax ); + + // origin + bool bAutoCross = lclIsAutoAnyOrGetScaledValue( maData.mfCross, rScaleData.Origin, bLogScale ); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS, bAutoCross ); + + // major increment + const IncrementData& rIncrementData = rScaleData.IncrementData; + const bool bAutoMajor = lclIsAutoAnyOrGetValue( maData.mfMajorStep, rIncrementData.Distance ) || (maData.mfMajorStep <= 0.0); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAJOR, bAutoMajor ); + // minor increment + const Sequence< SubIncrement >& rSubIncrementSeq = rIncrementData.SubIncrements; + sal_Int32 nCount = 0; + + // tdf#114168 If IntervalCount is 5, then enable automatic minor calculation. + // During import, if minorUnit is set and majorUnit not, then it is impossible + // to calculate IntervalCount. + const bool bAutoMinor = bLogScale || bAutoMajor || !rSubIncrementSeq.hasElements() || + lclIsAutoAnyOrGetValue( nCount, rSubIncrementSeq[ 0 ].IntervalCount ) || (nCount < 1) || (nCount == 5); + + if( maData.mfMajorStep && !bAutoMinor ) + maData.mfMinorStep = maData.mfMajorStep / nCount; + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMINOR, bAutoMinor ); + + // reverse order + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_REVERSE, rScaleData.Orientation == cssc2::AxisOrientation_REVERSE ); +} + +void XclExpChValueRange::ConvertAxisPosition( const ScfPropertySet& rPropSet ) +{ + cssc::ChartAxisPosition eAxisPos = cssc::ChartAxisPosition_VALUE; + double fCrossingPos = 0.0; + if( !(rPropSet.GetProperty( eAxisPos, EXC_CHPROP_CROSSOVERPOSITION ) && rPropSet.GetProperty( fCrossingPos, EXC_CHPROP_CROSSOVERVALUE )) ) + return; + + switch( eAxisPos ) + { + case cssc::ChartAxisPosition_ZERO: + case cssc::ChartAxisPosition_START: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + break; + case cssc::ChartAxisPosition_END: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_MAXCROSS ); + break; + case cssc::ChartAxisPosition_VALUE: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS, false ); + maData.mfCross = ::get_flagvalue< double >( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE, log( fCrossingPos ) / log( 10.0 ), fCrossingPos ); + break; + default: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + } +} + +void XclExpChValueRange::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mfMin + << maData.mfMax + << maData.mfMajorStep + << maData.mfMinorStep + << maData.mfCross + << maData.mnFlags; +} + +namespace { + +sal_uInt8 lclGetXclTickPos( sal_Int32 nApiTickmarks ) +{ + using namespace cssc2::TickmarkStyle; + sal_uInt8 nXclTickPos = 0; + ::set_flag( nXclTickPos, EXC_CHTICK_INSIDE, ::get_flag( nApiTickmarks, INNER ) ); + ::set_flag( nXclTickPos, EXC_CHTICK_OUTSIDE, ::get_flag( nApiTickmarks, OUTER ) ); + return nXclTickPos; +} + +} // namespace + +XclExpChTick::XclExpChTick( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHTICK, (rRoot.GetBiff() == EXC_BIFF8) ? 30 : 26 ), + XclExpChRoot( rRoot ), + mnTextColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +void XclExpChTick::Convert( const ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo, sal_uInt16 nAxisType ) +{ + // tick mark style + sal_Int32 nApiTickmarks = 0; + if( rPropSet.GetProperty( nApiTickmarks, EXC_CHPROP_MAJORTICKS ) ) + maData.mnMajor = lclGetXclTickPos( nApiTickmarks ); + if( rPropSet.GetProperty( nApiTickmarks, EXC_CHPROP_MINORTICKS ) ) + maData.mnMinor = lclGetXclTickPos( nApiTickmarks ); + + // axis labels + if( (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR) && (nAxisType == EXC_CHAXIS_X) ) + { + /* Radar charts disable their category labels via chart type, not via + axis, and axis labels are always 'near axis'. */ + maData.mnLabelPos = EXC_CHTICK_NEXT; + } + else if( !rPropSet.GetBoolProperty( EXC_CHPROP_DISPLAYLABELS ) ) + { + // no labels + maData.mnLabelPos = EXC_CHTICK_NOLABEL; + } + else if( rTypeInfo.mb3dChart && (nAxisType == EXC_CHAXIS_Y) ) + { + // Excel expects 'near axis' at Y axes in 3D charts + maData.mnLabelPos = EXC_CHTICK_NEXT; + } + else + { + cssc::ChartAxisLabelPosition eApiLabelPos = cssc::ChartAxisLabelPosition_NEAR_AXIS; + rPropSet.GetProperty( eApiLabelPos, EXC_CHPROP_LABELPOSITION ); + switch( eApiLabelPos ) + { + case cssc::ChartAxisLabelPosition_NEAR_AXIS: + case cssc::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE: maData.mnLabelPos = EXC_CHTICK_NEXT; break; + case cssc::ChartAxisLabelPosition_OUTSIDE_START: maData.mnLabelPos = EXC_CHTICK_LOW; break; + case cssc::ChartAxisLabelPosition_OUTSIDE_END: maData.mnLabelPos = EXC_CHTICK_HIGH; break; + default: maData.mnLabelPos = EXC_CHTICK_NEXT; + } + } +} + +void XclExpChTick::SetFontColor( const Color& rColor, sal_uInt32 nColorId ) +{ + maData.maTextColor = rColor; + ::set_flag( maData.mnFlags, EXC_CHTICK_AUTOCOLOR, rColor == COL_AUTO ); + mnTextColorId = nColorId; +} + +void XclExpChTick::SetRotation( sal_uInt16 nRotation ) +{ + maData.mnRotation = nRotation; + ::set_flag( maData.mnFlags, EXC_CHTICK_AUTOROT, false ); + ::insert_value( maData.mnFlags, XclTools::GetXclOrientFromRot( nRotation ), 2, 3 ); +} + +void XclExpChTick::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnMajor + << maData.mnMinor + << maData.mnLabelPos + << maData.mnBackMode; + rStrm.WriteZeroBytes( 16 ); + rStrm << maData.maTextColor + << maData.mnFlags; + if( GetBiff() == EXC_BIFF8 ) + rStrm << GetPalette().GetColorIndex( mnTextColorId ) << maData.mnRotation; +} + +namespace { + +/** Returns an API axis object from the passed coordinate system. */ +Reference< XAxis > lclGetApiAxis( Reference< XCoordinateSystem > const & xCoordSystem, + sal_Int32 nApiAxisDim, sal_Int32 nApiAxesSetIdx ) +{ + Reference< XAxis > xAxis; + if( (nApiAxisDim >= 0) && xCoordSystem.is() ) try + { + xAxis = xCoordSystem->getAxisByDimension( nApiAxisDim, nApiAxesSetIdx ); + } + catch( Exception& ) + { + } + return xAxis; +} + +Reference< cssc::XAxis > lclGetApiChart1Axis( Reference< XChartDocument > const & xChartDoc, + sal_Int32 nApiAxisDim, sal_Int32 nApiAxesSetIdx ) +{ + Reference< cssc::XAxis > xChart1Axis; + try + { + Reference< cssc::XChartDocument > xChart1Doc( xChartDoc, UNO_QUERY_THROW ); + Reference< cssc::XAxisSupplier > xChart1AxisSupp( xChart1Doc->getDiagram(), UNO_QUERY_THROW ); + switch( nApiAxesSetIdx ) + { + case EXC_CHART_AXESSET_PRIMARY: + xChart1Axis = xChart1AxisSupp->getAxis( nApiAxisDim ); + break; + case EXC_CHART_AXESSET_SECONDARY: + xChart1Axis = xChart1AxisSupp->getSecondaryAxis( nApiAxisDim ); + break; + } + } + catch( Exception& ) + { + } + return xChart1Axis; +} + +} // namespace + +XclExpChAxis::XclExpChAxis( const XclExpChRoot& rRoot, sal_uInt16 nAxisType ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_AXIS, EXC_ID_CHAXIS, 18 ), + mnNumFmtIdx( EXC_FORMAT_NOTFOUND ) +{ + maData.mnType = nAxisType; +} + +void XclExpChAxis::SetFont( XclExpChFontRef xFont, const Color& rColor, sal_uInt32 nColorId ) +{ + mxFont = xFont; + if( mxTick ) + mxTick->SetFontColor( rColor, nColorId ); +} + +void XclExpChAxis::SetRotation( sal_uInt16 nRotation ) +{ + if( mxTick ) + mxTick->SetRotation( nRotation ); +} + +void XclExpChAxis::Convert( Reference< XAxis > const & xAxis, Reference< XAxis > const & xCrossingAxis, + Reference< cssc::XAxis > const & xChart1Axis, const XclChExtTypeInfo& rTypeInfo ) +{ + ScfPropertySet aAxisProp( xAxis ); + bool bCategoryAxis = ((GetAxisType() == EXC_CHAXIS_X) && rTypeInfo.mbCategoryAxis) || (GetAxisType() == EXC_CHAXIS_Z); + + // axis line format ------------------------------------------------------- + + mxAxisLine = new XclExpChLineFormat( GetChRoot() ); + mxAxisLine->Convert( GetChRoot(), aAxisProp, EXC_CHOBJTYPE_AXISLINE ); + // #i58688# axis enabled + mxAxisLine->SetShowAxis( aAxisProp.GetBoolProperty( EXC_CHPROP_SHOW ) ); + + // axis scaling and increment --------------------------------------------- + + ScfPropertySet aCrossingProp( xCrossingAxis ); + if( bCategoryAxis ) + { + mxLabelRange = new XclExpChLabelRange( GetChRoot() ); + mxLabelRange->SetTicksBetweenCateg( rTypeInfo.mbTicksBetweenCateg ); + if( xAxis.is() ) + { + ScfPropertySet aChart1AxisProp( xChart1Axis ); + // #i71684# radar charts have reversed rotation direction + mxLabelRange->Convert( xAxis->getScaleData(), aChart1AxisProp, (GetAxisType() == EXC_CHAXIS_X) && (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR) ); + } + // get position of crossing axis on this axis from passed axis object + if( aCrossingProp.Is() ) + mxLabelRange->ConvertAxisPosition( aCrossingProp ); + } + else + { + mxValueRange = new XclExpChValueRange( GetChRoot() ); + if( xAxis.is() ) + mxValueRange->Convert( xAxis->getScaleData() ); + // get position of crossing axis on this axis from passed axis object + if( aCrossingProp.Is() ) + mxValueRange->ConvertAxisPosition( aCrossingProp ); + } + + // axis caption text ------------------------------------------------------ + + // axis ticks properties + mxTick = new XclExpChTick( GetChRoot() ); + mxTick->Convert( aAxisProp, rTypeInfo, GetAxisType() ); + + // axis label formatting and rotation + ConvertFontBase( GetChRoot(), aAxisProp ); + ConvertRotationBase( aAxisProp, true ); + + // axis number format + sal_Int32 nApiNumFmt = 0; + if( !bCategoryAxis && aAxisProp.GetProperty( nApiNumFmt, EXC_CHPROP_NUMBERFORMAT ) ) + { + bool bLinkNumberFmtToSource = false; + if ( !aAxisProp.GetProperty( bLinkNumberFmtToSource, EXC_CHPROP_NUMBERFORMAT_LINKSRC ) || !bLinkNumberFmtToSource ) + mnNumFmtIdx = GetNumFmtBuffer().Insert( static_cast< sal_uInt32 >( nApiNumFmt ) ); + } + + // grid ------------------------------------------------------------------- + + if( !xAxis.is() ) + return; + + // main grid + ScfPropertySet aGridProp( xAxis->getGridProperties() ); + if( aGridProp.GetBoolProperty( EXC_CHPROP_SHOW ) ) + mxMajorGrid = lclCreateLineFormat( GetChRoot(), aGridProp, EXC_CHOBJTYPE_GRIDLINE ); + // sub grid + Sequence< Reference< XPropertySet > > aSubGridPropSeq = xAxis->getSubGridProperties(); + if( aSubGridPropSeq.hasElements() ) + { + ScfPropertySet aSubGridProp( aSubGridPropSeq[ 0 ] ); + if( aSubGridProp.GetBoolProperty( EXC_CHPROP_SHOW ) ) + mxMinorGrid = lclCreateLineFormat( GetChRoot(), aSubGridProp, EXC_CHOBJTYPE_GRIDLINE ); + } +} + +void XclExpChAxis::ConvertWall( css::uno::Reference< css::chart2::XDiagram > const & xDiagram ) +{ + if( !xDiagram.is() ) + return; + + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + { + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxWallFrame = lclCreateFrame( GetChRoot(), aWallProp, EXC_CHOBJTYPE_WALL3D ); + } + break; + case EXC_CHAXIS_Y: + { + ScfPropertySet aFloorProp( xDiagram->getFloor() ); + mxWallFrame = lclCreateFrame( GetChRoot(), aFloorProp, EXC_CHOBJTYPE_FLOOR3D ); + } + break; + default: + mxWallFrame.clear(); + } +} + +void XclExpChAxis::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxLabelRange ); + lclSaveRecord( rStrm, mxValueRange ); + if( mnNumFmtIdx != EXC_FORMAT_NOTFOUND ) + XclExpUInt16Record( EXC_ID_CHFORMAT, mnNumFmtIdx ).Save( rStrm ); + lclSaveRecord( rStrm, mxTick ); + lclSaveRecord( rStrm, mxFont ); + lclSaveRecord( rStrm, mxAxisLine, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_AXISLINE ); + lclSaveRecord( rStrm, mxMajorGrid, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_MAJORGRID ); + lclSaveRecord( rStrm, mxMinorGrid, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_MINORGRID ); + lclSaveRecord( rStrm, mxWallFrame, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_WALLS ); +} + +void XclExpChAxis::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnType; + rStrm.WriteZeroBytes( 16 ); +} + +XclExpChAxesSet::XclExpChAxesSet( const XclExpChRoot& rRoot, sal_uInt16 nAxesSetId ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_AXESSET, EXC_ID_CHAXESSET, 18 ) +{ + maData.mnAxesSetId = nAxesSetId; + SetFutureRecordContext( 0, nAxesSetId ); + + /* Need to set a reasonable size for the plot area, otherwise Excel will + move away embedded shapes while auto-sizing the plot area. This is just + a wild guess, but will be fixed with implementing manual positioning of + chart elements. */ + maData.maRect.mnX = 262; + maData.maRect.mnY = 626; + maData.maRect.mnWidth = 3187; + maData.maRect.mnHeight = 2633; +} + +sal_uInt16 XclExpChAxesSet::Convert( Reference< XDiagram > const & xDiagram, sal_uInt16 nFirstGroupIdx ) +{ + /* First unused chart type group index is passed to be able to continue + counting of chart type groups for secondary axes set. */ + sal_uInt16 nGroupIdx = nFirstGroupIdx; + Reference< XCoordinateSystemContainer > xCoordSysCont( xDiagram, UNO_QUERY ); + if( xCoordSysCont.is() ) + { + Sequence< Reference< XCoordinateSystem > > aCoordSysSeq = xCoordSysCont->getCoordinateSystems(); + if( aCoordSysSeq.hasElements() ) + { + /* Process first coordinate system only. Import filter puts all + chart types into one coordinate system. */ + Reference< XCoordinateSystem > xCoordSystem = aCoordSysSeq[ 0 ]; + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + + // 3d mode + bool b3dChart = xCoordSystem.is() && (xCoordSystem->getDimension() == 3); + + // percent charts + namespace ApiAxisType = cssc2::AxisType; + Reference< XAxis > xApiYAxis = lclGetApiAxis( xCoordSystem, EXC_CHART_AXIS_Y, nApiAxesSetIdx ); + bool bPercent = xApiYAxis.is() && (xApiYAxis->getScaleData().AxisType == ApiAxisType::PERCENT); + + // connector lines in bar charts + ScfPropertySet aDiaProp( xDiagram ); + bool bConnectBars = aDiaProp.GetBoolProperty( EXC_CHPROP_CONNECTBARS ); + + // swapped axes sets + ScfPropertySet aCoordSysProp( xCoordSystem ); + bool bSwappedAxesSet = aCoordSysProp.GetBoolProperty( EXC_CHPROP_SWAPXANDYAXIS ); + + // X axis for later use + Reference< XAxis > xApiXAxis = lclGetApiAxis( xCoordSystem, EXC_CHART_AXIS_X, nApiAxesSetIdx ); + // X axis labels + ScfPropertySet aXAxisProp( xApiXAxis ); + bool bHasXLabels = aXAxisProp.GetBoolProperty( EXC_CHPROP_DISPLAYLABELS ); + + // process chart types + Reference< XChartTypeContainer > xChartTypeCont( xCoordSystem, UNO_QUERY ); + if( xChartTypeCont.is() ) + { + const Sequence< Reference< XChartType > > aChartTypeSeq = xChartTypeCont->getChartTypes(); + for( const Reference< XChartType >& rChartType : aChartTypeSeq ) + { + XclExpChTypeGroupRef xTypeGroup = new XclExpChTypeGroup( GetChRoot(), nGroupIdx ); + xTypeGroup->ConvertType( xDiagram, rChartType, nApiAxesSetIdx, b3dChart, bSwappedAxesSet, bHasXLabels ); + /* If new chart type group cannot be inserted into a combination + chart with existing type groups, insert all series into last + contained chart type group instead of creating a new group. */ + XclExpChTypeGroupRef xLastGroup = GetLastTypeGroup(); + if( xLastGroup && !(xTypeGroup->IsCombinable2d() && xLastGroup->IsCombinable2d()) ) + { + xLastGroup->ConvertSeries( xDiagram, rChartType, nApiAxesSetIdx, bPercent, bConnectBars ); + } + else + { + xTypeGroup->ConvertSeries( xDiagram, rChartType, nApiAxesSetIdx, bPercent, bConnectBars ); + if( xTypeGroup->IsValidGroup() ) + { + maTypeGroups.AppendRecord( xTypeGroup ); + ++nGroupIdx; + } + } + } + } + + if( XclExpChTypeGroup* pGroup = GetFirstTypeGroup().get() ) + { + const XclChExtTypeInfo& rTypeInfo = pGroup->GetTypeInfo(); + + // create axes according to chart type (no axes for pie and donut charts) + if( rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_PIE ) + { + ConvertAxis( mxXAxis, EXC_CHAXIS_X, mxXAxisTitle, EXC_CHOBJLINK_XAXIS, xCoordSystem, rTypeInfo, EXC_CHART_AXIS_Y ); + ConvertAxis( mxYAxis, EXC_CHAXIS_Y, mxYAxisTitle, EXC_CHOBJLINK_YAXIS, xCoordSystem, rTypeInfo, EXC_CHART_AXIS_X ); + if( pGroup->Is3dDeepChart() ) + ConvertAxis( mxZAxis, EXC_CHAXIS_Z, mxZAxisTitle, EXC_CHOBJLINK_ZAXIS, xCoordSystem, rTypeInfo, EXC_CHART_AXIS_NONE ); + } + + // X axis category ranges + if( rTypeInfo.mbCategoryAxis && xApiXAxis.is() ) + { + const ScaleData aScaleData = xApiXAxis->getScaleData(); + for( size_t nIdx = 0, nSize = maTypeGroups.GetSize(); nIdx < nSize; ++nIdx ) + maTypeGroups.GetRecord( nIdx )->ConvertCategSequence( aScaleData.Categories ); + } + + // legend + if( xDiagram.is() && (GetAxesSetId() == EXC_CHAXESSET_PRIMARY) ) + { + Reference< XLegend > xLegend = xDiagram->getLegend(); + if( xLegend.is() ) + { + ScfPropertySet aLegendProp( xLegend ); + pGroup->ConvertLegend( aLegendProp ); + } + } + } + } + } + + // wall/floor/diagram frame formatting + if( xDiagram.is() && (GetAxesSetId() == EXC_CHAXESSET_PRIMARY) ) + { + XclExpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup && xTypeGroup->Is3dWallChart() ) + { + // wall/floor formatting (3D charts) + if( mxXAxis ) + mxXAxis->ConvertWall( xDiagram ); + if( mxYAxis ) + mxYAxis->ConvertWall( xDiagram ); + } + else + { + // diagram background formatting + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxPlotFrame = lclCreateFrame( GetChRoot(), aWallProp, EXC_CHOBJTYPE_PLOTFRAME ); + } + } + + // inner and outer plot area position and size + try + { + Reference< cssc::XChartDocument > xChart1Doc( GetChartDocument(), UNO_QUERY_THROW ); + Reference< cssc::XDiagramPositioning > xPositioning( xChart1Doc->getDiagram(), UNO_QUERY_THROW ); + // set manual flag in chart data + if( !xPositioning->isAutomaticDiagramPositioning() ) + GetChartData().SetManualPlotArea(); + // the CHAXESSET record contains the inner plot area + maData.maRect = CalcChartRectFromHmm( xPositioning->calculateDiagramPositionExcludingAxes() ); + // the embedded CHFRAMEPOS record contains the outer plot area + mxFramePos = new XclExpChFramePos( EXC_CHFRAMEPOS_PARENT ); + // for pie charts, always use inner plot area size to exclude the data labels as Excel does + const XclExpChTypeGroup* pFirstTypeGroup = GetFirstTypeGroup().get(); + bool bPieChart = pFirstTypeGroup && (pFirstTypeGroup->GetTypeInfo().meTypeCateg == EXC_CHTYPECATEG_PIE); + mxFramePos->GetFramePosData().maRect = bPieChart ? maData.maRect : + CalcChartRectFromHmm( xPositioning->calculateDiagramPositionIncludingAxes() ); + } + catch( Exception& ) + { + } + + // return first unused chart type group index for next axes set + return nGroupIdx; +} + +bool XclExpChAxesSet::Is3dChart() const +{ + XclExpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + return xTypeGroup && xTypeGroup->Is3dChart(); +} + +void XclExpChAxesSet::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxFramePos ); + lclSaveRecord( rStrm, mxXAxis ); + lclSaveRecord( rStrm, mxYAxis ); + lclSaveRecord( rStrm, mxZAxis ); + lclSaveRecord( rStrm, mxXAxisTitle ); + lclSaveRecord( rStrm, mxYAxisTitle ); + lclSaveRecord( rStrm, mxZAxisTitle ); + if( mxPlotFrame ) + { + XclExpEmptyRecord( EXC_ID_CHPLOTFRAME ).Save( rStrm ); + mxPlotFrame->Save( rStrm ); + } + maTypeGroups.Save( rStrm ); +} + +XclExpChTypeGroupRef XclExpChAxesSet::GetFirstTypeGroup() const +{ + return maTypeGroups.GetFirstRecord(); +} + +XclExpChTypeGroupRef XclExpChAxesSet::GetLastTypeGroup() const +{ + return maTypeGroups.GetLastRecord(); +} + +void XclExpChAxesSet::ConvertAxis( + XclExpChAxisRef& rxChAxis, sal_uInt16 nAxisType, + XclExpChTextRef& rxChAxisTitle, sal_uInt16 nTitleTarget, + Reference< XCoordinateSystem > const & xCoordSystem, const XclChExtTypeInfo& rTypeInfo, + sal_Int32 nCrossingAxisDim ) +{ + // create and convert axis object + rxChAxis = new XclExpChAxis( GetChRoot(), nAxisType ); + sal_Int32 nApiAxisDim = rxChAxis->GetApiAxisDimension(); + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + Reference< XAxis > xAxis = lclGetApiAxis( xCoordSystem, nApiAxisDim, nApiAxesSetIdx ); + Reference< XAxis > xCrossingAxis = lclGetApiAxis( xCoordSystem, nCrossingAxisDim, nApiAxesSetIdx ); + Reference< cssc::XAxis > xChart1Axis = lclGetApiChart1Axis( GetChartDocument(), nApiAxisDim, nApiAxesSetIdx ); + rxChAxis->Convert( xAxis, xCrossingAxis, xChart1Axis, rTypeInfo ); + + // create and convert axis title + Reference< XTitled > xTitled( xAxis, UNO_QUERY ); + rxChAxisTitle = lclCreateTitle( GetChRoot(), xTitled, nTitleTarget ); +} + +void XclExpChAxesSet::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnAxesSetId << maData.maRect; +} + +// The chart object =========================================================== + +static void lcl_getChartSubTitle(const Reference<XChartDocument>& xChartDoc, + OUString& rSubTitle) +{ + Reference< css::chart::XChartDocument > xChartDoc1(xChartDoc, UNO_QUERY); + if (!xChartDoc1.is()) + return; + + Reference< XPropertySet > xProp(xChartDoc1->getSubTitle(), UNO_QUERY); + if (!xProp.is()) + return; + + OUString aTitle; + Any any = xProp->getPropertyValue("String"); + if (any >>= aTitle) + rSubTitle = aTitle; +} + +XclExpChChart::XclExpChChart( const XclExpRoot& rRoot, + Reference< XChartDocument > const & xChartDoc, const tools::Rectangle& rChartRect ) : + XclExpChGroupBase( XclExpChRoot( rRoot, *this ), EXC_CHFRBLOCK_TYPE_CHART, EXC_ID_CHCHART, 16 ) +{ + Size aPtSize = o3tl::convert( rChartRect.GetSize(), o3tl::Length::mm100, o3tl::Length::pt ); + // rectangle is stored in 16.16 fixed-point format + maRect.mnX = maRect.mnY = 0; + maRect.mnWidth = static_cast< sal_Int32 >( aPtSize.Width() << 16 ); + maRect.mnHeight = static_cast< sal_Int32 >( aPtSize.Height() << 16 ); + + // global chart properties (default values) + ::set_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY, false ); + ::set_flag( maProps.mnFlags, EXC_CHPROPS_MANPLOTAREA ); + maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_SKIP; + + // always create both axes set objects + mxPrimAxesSet = std::make_shared<XclExpChAxesSet>( GetChRoot(), EXC_CHAXESSET_PRIMARY ); + mxSecnAxesSet = std::make_shared<XclExpChAxesSet>( GetChRoot(), EXC_CHAXESSET_SECONDARY ); + + if( !xChartDoc.is() ) + return; + + Reference< XDiagram > xDiagram = xChartDoc->getFirstDiagram(); + + // global chart properties (only 'include hidden cells' attribute for now) + ScfPropertySet aDiagramProp( xDiagram ); + bool bIncludeHidden = aDiagramProp.GetBoolProperty( EXC_CHPROP_INCLUDEHIDDENCELLS ); + ::set_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY, !bIncludeHidden ); + + // initialize API conversion (remembers xChartDoc and rChartRect internally) + InitConversion( xChartDoc, rChartRect ); + + // chart frame + ScfPropertySet aFrameProp( xChartDoc->getPageBackground() ); + mxFrame = lclCreateFrame( GetChRoot(), aFrameProp, EXC_CHOBJTYPE_BACKGROUND ); + + // chart title + Reference< XTitled > xTitled( xChartDoc, UNO_QUERY ); + OUString aSubTitle; + lcl_getChartSubTitle(xChartDoc, aSubTitle); + mxTitle = lclCreateTitle( GetChRoot(), xTitled, EXC_CHOBJLINK_TITLE, + !aSubTitle.isEmpty() ? &aSubTitle : nullptr ); + + // diagrams (axes sets) + sal_uInt16 nFreeGroupIdx = mxPrimAxesSet->Convert( xDiagram, 0 ); + if( !mxPrimAxesSet->Is3dChart() ) + mxSecnAxesSet->Convert( xDiagram, nFreeGroupIdx ); + + // treatment of missing values + ScfPropertySet aDiaProp( xDiagram ); + sal_Int32 nMissingValues = 0; + if( aDiaProp.GetProperty( nMissingValues, EXC_CHPROP_MISSINGVALUETREATMENT ) ) + { + using namespace cssc::MissingValueTreatment; + switch( nMissingValues ) + { + case LEAVE_GAP: maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_SKIP; break; + case USE_ZERO: maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_ZERO; break; + case CONTINUE: maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_INTERPOLATE; break; + } + } + + // finish API conversion + FinishConversion(); +} + +XclExpChSeriesRef XclExpChChart::CreateSeries() +{ + XclExpChSeriesRef xSeries; + sal_uInt16 nSeriesIdx = static_cast< sal_uInt16 >( maSeries.GetSize() ); + if( nSeriesIdx <= EXC_CHSERIES_MAXSERIES ) + { + xSeries = new XclExpChSeries( GetChRoot(), nSeriesIdx ); + maSeries.AppendRecord( xSeries ); + } + return xSeries; +} + +void XclExpChChart::RemoveLastSeries() +{ + if( !maSeries.IsEmpty() ) + maSeries.RemoveRecord( maSeries.GetSize() - 1 ); +} + +void XclExpChChart::SetDataLabel( XclExpChTextRef const & xText ) +{ + if( xText ) + maLabels.AppendRecord( xText ); +} + +void XclExpChChart::SetManualPlotArea() +{ + // this flag does not exist in BIFF5 + if( GetBiff() == EXC_BIFF8 ) + ::set_flag( maProps.mnFlags, EXC_CHPROPS_USEMANPLOTAREA ); +} + +void XclExpChChart::WriteSubRecords( XclExpStream& rStrm ) +{ + // background format + lclSaveRecord( rStrm, mxFrame ); + + // data series + maSeries.Save( rStrm ); + + // CHPROPERTIES record + rStrm.StartRecord( EXC_ID_CHPROPERTIES, 4 ); + rStrm << maProps.mnFlags << maProps.mnEmptyMode << sal_uInt8( 0 ); + rStrm.EndRecord(); + + // axes sets (always save primary axes set) + sal_uInt16 nUsedAxesSets = mxSecnAxesSet->IsValidAxesSet() ? 2 : 1; + XclExpUInt16Record( EXC_ID_CHUSEDAXESSETS, nUsedAxesSets ).Save( rStrm ); + mxPrimAxesSet->Save( rStrm ); + if( mxSecnAxesSet->IsValidAxesSet() ) + mxSecnAxesSet->Save( rStrm ); + + // chart title and data labels + lclSaveRecord( rStrm, mxTitle ); + maLabels.Save( rStrm ); +} + +void XclExpChChart::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maRect; +} + +XclExpChartDrawing::XclExpChartDrawing( const XclExpRoot& rRoot, + const Reference< XModel >& rxModel, const Size& rChartSize ) : + XclExpRoot( rRoot ) +{ + if( rChartSize.IsEmpty() ) + return; + + ScfPropertySet aPropSet( rxModel ); + Reference< XShapes > xShapes; + if( !(aPropSet.GetProperty( xShapes, EXC_CHPROP_ADDITIONALSHAPES ) && xShapes.is() && (xShapes->getCount() > 0)) ) + return; + + /* Create a new independent object manager with own DFF stream for the + DGCONTAINER, pass global manager as parent for shared usage of + global DFF data (picture container etc.). */ + mxObjMgr = std::make_shared<XclExpEmbeddedObjectManager>( GetObjectManager(), rChartSize, EXC_CHART_TOTALUNITS, EXC_CHART_TOTALUNITS ); + // initialize the drawing object list + mxObjMgr->StartSheet(); + // process the draw page (convert all shapes) + mxObjRecs = mxObjMgr->ProcessDrawing( xShapes ); + // finalize the DFF stream + mxObjMgr->EndDocument(); +} + +XclExpChartDrawing::~XclExpChartDrawing() +{ +} + +void XclExpChartDrawing::Save( XclExpStream& rStrm ) +{ + if( mxObjRecs ) + mxObjRecs->Save( rStrm ); +} + +XclExpChart::XclExpChart( const XclExpRoot& rRoot, Reference< XModel > const & xModel, const tools::Rectangle& rChartRect ) : + XclExpSubStream( EXC_BOF_CHART ), + XclExpRoot( rRoot ) +{ + AppendNewRecord( new XclExpChartPageSettings( rRoot ) ); + AppendNewRecord( new XclExpBoolRecord( EXC_ID_PROTECT, false ) ); + AppendNewRecord( new XclExpChartDrawing( rRoot, xModel, rChartRect.GetSize() ) ); + AppendNewRecord( new XclExpUInt16Record( EXC_ID_CHUNITS, EXC_CHUNITS_TWIPS ) ); + + Reference< XChartDocument > xChartDoc( xModel, UNO_QUERY ); + AppendNewRecord( new XclExpChChart( rRoot, xChartDoc, rChartRect ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xecontent.cxx b/sc/source/filter/excel/xecontent.cxx new file mode 100644 index 000000000..65f8bb2f4 --- /dev/null +++ b/sc/source/filter/excel/xecontent.cxx @@ -0,0 +1,2211 @@ +/* -*- 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 <memory> +#include <xecontent.hxx> + +#include <vector> +#include <algorithm> +#include <string_view> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/sheet/XAreaLinks.hpp> +#include <com/sun/star/sheet/XAreaLink.hpp> +#include <com/sun/star/sheet/TableValidationVisibility.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <sfx2/objsh.hxx> +#include <tools/urlobj.hxx> +#include <formula/grammar.hxx> +#include <scitems.hxx> +#include <editeng/flditem.hxx> +#include <document.hxx> +#include <validat.hxx> +#include <unonames.hxx> +#include <convuno.hxx> +#include <rangenam.hxx> +#include <tokenarray.hxx> +#include <stlpool.hxx> +#include <patattr.hxx> +#include <fapihelper.hxx> +#include <xehelper.hxx> +#include <xestyle.hxx> +#include <xename.hxx> +#include <xlcontent.hxx> +#include <xltools.hxx> +#include <xeformula.hxx> +#include <rtl/uuid.h> +#include <sal/log.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/relationship.hxx> +#include <comphelper/string.hxx> +#include <o3tl/string_view.hxx> + +using namespace ::oox; + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::XAreaLinks; +using ::com::sun::star::sheet::XAreaLink; + +// Shared string table ======================================================== + +namespace { + +/** A single string entry in the hash table. */ +struct XclExpHashEntry +{ + const XclExpString* mpString; /// Pointer to the string (no ownership). + sal_uInt32 mnSstIndex; /// The SST index of this string. + explicit XclExpHashEntry( const XclExpString* pString, sal_uInt32 nSstIndex ) : + mpString( pString ), mnSstIndex( nSstIndex ) {} +}; + +/** Function object for strict weak ordering. */ +struct XclExpHashEntrySWO +{ + bool operator()( const XclExpHashEntry& rLeft, const XclExpHashEntry& rRight ) const + { return *rLeft.mpString < *rRight.mpString; } +}; + +} + +/** Implementation of the SST export. + @descr Stores all passed strings in a hash table and prevents repeated + insertion of equal strings. */ +class XclExpSstImpl +{ +public: + explicit XclExpSstImpl(); + + /** Inserts the passed string, if not already inserted, and returns the unique SST index. */ + sal_uInt32 Insert( XclExpStringRef xString ); + + /** Writes the complete SST and EXTSST records. */ + void Save( XclExpStream& rStrm ); + void SaveXml( XclExpXmlStream& rStrm ); + +private: + typedef ::std::vector< XclExpHashEntry > XclExpHashVec; + + std::vector< XclExpStringRef > maStringVector; /// List of unique strings (in SST ID order). + std::vector< XclExpHashVec > + maHashTab; /// Hashed table that manages string pointers. + sal_uInt32 mnTotal; /// Total count of strings (including doubles). + sal_uInt32 mnSize; /// Size of the SST (count of unique strings). +}; + +const sal_uInt32 EXC_SST_HASHTABLE_SIZE = 2048; + +XclExpSstImpl::XclExpSstImpl() : + maHashTab( EXC_SST_HASHTABLE_SIZE ), + mnTotal( 0 ), + mnSize( 0 ) +{ +} + +sal_uInt32 XclExpSstImpl::Insert( XclExpStringRef xString ) +{ + OSL_ENSURE( xString, "XclExpSstImpl::Insert - empty pointer not allowed" ); + if( !xString ) + xString.reset( new XclExpString ); + + ++mnTotal; + sal_uInt32 nSstIndex = 0; + + // calculate hash value in range [0,EXC_SST_HASHTABLE_SIZE) + sal_uInt16 nHash = xString->GetHash(); + nHash = (nHash ^ (nHash / EXC_SST_HASHTABLE_SIZE)) % EXC_SST_HASHTABLE_SIZE; + + XclExpHashVec& rVec = maHashTab[ nHash ]; + XclExpHashEntry aEntry( xString.get(), mnSize ); + XclExpHashVec::iterator aIt = ::std::lower_bound( rVec.begin(), rVec.end(), aEntry, XclExpHashEntrySWO() ); + if( (aIt == rVec.end()) || (*aIt->mpString != *xString) ) + { + nSstIndex = mnSize; + maStringVector.push_back( xString ); + rVec.insert( aIt, aEntry ); + ++mnSize; + } + else + { + nSstIndex = aIt->mnSstIndex; + } + + return nSstIndex; +} + +void XclExpSstImpl::Save( XclExpStream& rStrm ) +{ + if( maStringVector.empty() ) + return; + + SvMemoryStream aExtSst( 8192 ); + + sal_uInt32 nBucket = mnSize; + while( nBucket > 0x0100 ) + nBucket /= 2; + + sal_uInt16 nPerBucket = llimit_cast< sal_uInt16 >( nBucket, 8 ); + sal_uInt16 nBucketIndex = 0; + + // *** write the SST record *** + + rStrm.StartRecord( EXC_ID_SST, 8 ); + + rStrm << mnTotal << mnSize; + for (auto const& elem : maStringVector) + { + if( !nBucketIndex ) + { + // write bucket info before string to get correct record position + sal_uInt32 nStrmPos = static_cast< sal_uInt32 >( rStrm.GetSvStreamPos() ); + sal_uInt16 nRecPos = rStrm.GetRawRecPos() + 4; + aExtSst.WriteUInt32( nStrmPos ) // stream position + .WriteUInt16( nRecPos ) // position from start of SST or CONTINUE + .WriteUInt16( 0 ); // reserved + } + + rStrm << *elem; + + if( ++nBucketIndex == nPerBucket ) + nBucketIndex = 0; + } + + rStrm.EndRecord(); + + // *** write the EXTSST record *** + + rStrm.StartRecord( EXC_ID_EXTSST, 0 ); + + rStrm << nPerBucket; + rStrm.SetSliceSize( 8 ); // size of one bucket info + aExtSst.Seek( STREAM_SEEK_TO_BEGIN ); + rStrm.CopyFromStream( aExtSst ); + + rStrm.EndRecord(); +} + +void XclExpSstImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maStringVector.empty() ) + return; + + sax_fastparser::FSHelperPtr pSst = rStrm.CreateOutputStream( + "xl/sharedStrings.xml", + u"sharedStrings.xml", + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", + oox::getRelationship(Relationship::SHAREDSTRINGS)); + rStrm.PushStream( pSst ); + + pSst->startElement( XML_sst, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)), + XML_count, OString::number(mnTotal), + XML_uniqueCount, OString::number(mnSize) ); + + for (auto const& elem : maStringVector) + { + pSst->startElement(XML_si); + elem->WriteXml( rStrm ); + pSst->endElement( XML_si ); + } + + pSst->endElement( XML_sst ); + + rStrm.PopStream(); +} + +XclExpSst::XclExpSst() : + mxImpl( new XclExpSstImpl ) +{ +} + +XclExpSst::~XclExpSst() +{ +} + +sal_uInt32 XclExpSst::Insert( const XclExpStringRef& xString ) +{ + return mxImpl->Insert( xString ); +} + +void XclExpSst::Save( XclExpStream& rStrm ) +{ + mxImpl->Save( rStrm ); +} + +void XclExpSst::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +// Merged cells =============================================================== + +XclExpMergedcells::XclExpMergedcells( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpMergedcells::AppendRange( const ScRange& rRange, sal_uInt32 nBaseXFId ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + maMergedRanges.push_back( rRange ); + maBaseXFIds.push_back( nBaseXFId ); + } +} + +sal_uInt32 XclExpMergedcells::GetBaseXFId( const ScAddress& rPos ) const +{ + OSL_ENSURE( maBaseXFIds.size() == maMergedRanges.size(), "XclExpMergedcells::GetBaseXFId - invalid lists" ); + ScfUInt32Vec::const_iterator aIt = maBaseXFIds.begin(); + ScRangeList& rNCRanges = const_cast< ScRangeList& >( maMergedRanges ); + for ( size_t i = 0, nRanges = rNCRanges.size(); i < nRanges; ++i, ++aIt ) + { + const ScRange & rScRange = rNCRanges[ i ]; + if( rScRange.Contains( rPos ) ) + return *aIt; + } + return EXC_XFID_NOTFOUND; +} + +void XclExpMergedcells::Save( XclExpStream& rStrm ) +{ + if( GetBiff() != EXC_BIFF8 ) + return; + + XclRangeList aXclRanges; + GetAddressConverter().ConvertRangeList( aXclRanges, maMergedRanges, true ); + size_t nFirstRange = 0; + size_t nRemainingRanges = aXclRanges.size(); + while( nRemainingRanges > 0 ) + { + size_t nRangeCount = ::std::min< size_t >( nRemainingRanges, EXC_MERGEDCELLS_MAXCOUNT ); + rStrm.StartRecord( EXC_ID_MERGEDCELLS, 2 + 8 * nRangeCount ); + aXclRanges.WriteSubList( rStrm, nFirstRange, nRangeCount ); + rStrm.EndRecord(); + nFirstRange += nRangeCount; + nRemainingRanges -= nRangeCount; + } +} + +void XclExpMergedcells::SaveXml( XclExpXmlStream& rStrm ) +{ + size_t nCount = maMergedRanges.size(); + if( !nCount ) + return; + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_mergeCells, XML_count, OString::number(nCount)); + for( size_t i = 0; i < nCount; ++i ) + { + const ScRange & rRange = maMergedRanges[ i ]; + rWorksheet->singleElement(XML_mergeCell, XML_ref, + XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), rRange)); + } + rWorksheet->endElement( XML_mergeCells ); +} + +// Hyperlinks ================================================================= + +XclExpHyperlink::XclExpHyperlink( const XclExpRoot& rRoot, const SvxURLField& rUrlField, const ScAddress& rScPos ) : + XclExpRecord( EXC_ID_HLINK ), + maScPos( rScPos ), + mxVarData( new SvMemoryStream ), + mnFlags( 0 ) +{ + const OUString& rUrl = rUrlField.GetURL(); + const OUString& rRepr = rUrlField.GetRepresentation(); + INetURLObject aUrlObj( rUrl ); + const INetProtocol eProtocol = aUrlObj.GetProtocol(); + bool bWithRepr = !rRepr.isEmpty(); + XclExpStream aXclStrm( *mxVarData, rRoot ); // using in raw write mode. + + // description + if( bWithRepr ) + { + XclExpString aDescr( rRepr, XclStrFlags::ForceUnicode, 255 ); + aXclStrm << sal_uInt32( aDescr.Len() + 1 ); // string length + 1 trailing zero word + aDescr.WriteBuffer( aXclStrm ); // NO flags + aXclStrm << sal_uInt16( 0 ); + + mnFlags |= EXC_HLINK_DESCR; + m_Repr = rRepr; + } + + // file link or URL + if( eProtocol == INetProtocol::File || eProtocol == INetProtocol::Smb ) + { + sal_uInt16 nLevel; + bool bRel; + OUString aFileName( + BuildFileName(nLevel, bRel, rUrl, rRoot, rRoot.GetOutput() == EXC_OUTPUT_XML_2007)); + + if( eProtocol == INetProtocol::Smb ) + { + // #n382718# (and #n261623#) Convert smb notation to '\\' + aFileName = aUrlObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + aFileName = aFileName.copy(4); // skip the 'smb:' part + aFileName = aFileName.replace('/', '\\'); + } + + if( !bRel ) + mnFlags |= EXC_HLINK_ABS; + mnFlags |= EXC_HLINK_BODY; + + OString aAsciiLink(OUStringToOString(aFileName, + rRoot.GetTextEncoding())); + XclExpString aLink( aFileName, XclStrFlags::ForceUnicode, 255 ); + aXclStrm << XclTools::maGuidFileMoniker + << nLevel + << sal_uInt32( aAsciiLink.getLength() + 1 ); // string length + 1 trailing zero byte + aXclStrm.Write( aAsciiLink.getStr(), aAsciiLink.getLength() ); + aXclStrm << sal_uInt8( 0 ) + << sal_uInt32( 0xDEADFFFF ); + aXclStrm.WriteZeroBytes( 20 ); + aXclStrm << sal_uInt32( aLink.GetBufferSize() + 6 ) + << sal_uInt32( aLink.GetBufferSize() ) // byte count, not string length + << sal_uInt16( 0x0003 ); + aLink.WriteBuffer( aXclStrm ); // NO flags + + if (m_Repr.isEmpty()) + m_Repr = aFileName; + + msTarget = XclXmlUtils::ToOUString( aLink ); + + if( bRel ) + { + for( int i = 0; i < nLevel; ++i ) + msTarget = "../" + msTarget; + } + else if (rRoot.GetOutput() != EXC_OUTPUT_XML_2007) + { + // xls expects the file:/// part appended ( or at least + // ms2007 does, ms2010 is more tolerant ) + msTarget = "file:///" + msTarget; + } + } + else if( eProtocol != INetProtocol::NotValid ) + { + XclExpString aUrl( aUrlObj.GetURLNoMark(), XclStrFlags::ForceUnicode, 255 ); + aXclStrm << XclTools::maGuidUrlMoniker + << sal_uInt32( aUrl.GetBufferSize() + 2 ); // byte count + 1 trailing zero word + aUrl.WriteBuffer( aXclStrm ); // NO flags + aXclStrm << sal_uInt16( 0 ); + + mnFlags |= EXC_HLINK_BODY | EXC_HLINK_ABS; + if (m_Repr.isEmpty()) + m_Repr = rUrl; + + msTarget = XclXmlUtils::ToOUString( aUrl ); + } + else if( !rUrl.isEmpty() && rUrl[0] == '#' ) // hack for #89066# + { + OUString aTextMark( rUrl.copy( 1 ) ); + + sal_Int32 nSepPos = aTextMark.lastIndexOf( '!' ); + sal_Int32 nPointPos = aTextMark.lastIndexOf( '.' ); + // last dot is the separator, if there is no ! after it + if(nSepPos < nPointPos) + { + nSepPos = nPointPos; + aTextMark = aTextMark.replaceAt( nSepPos, 1, u"!" ); + } + + if (nSepPos != -1) + { + std::u16string_view aSheetName(aTextMark.subView(0, nSepPos)); + + if (aSheetName.find(' ') != std::u16string_view::npos && aSheetName[0] != '\'') + { + aTextMark = "'" + aTextMark.replaceAt(nSepPos, 0, u"'"); + } + } + else + { + SCTAB nTab; + if (rRoot.GetDoc().GetTable(aTextMark, nTab)) + aTextMark += "!A1"; // tdf#143220 link to sheet not valid without cell reference + } + + mxTextMark.reset( new XclExpString( aTextMark, XclStrFlags::ForceUnicode, 255 ) ); + } + + // text mark + if( !mxTextMark && aUrlObj.HasMark() ) + mxTextMark.reset( new XclExpString( aUrlObj.GetMark(), XclStrFlags::ForceUnicode, 255 ) ); + + if( mxTextMark ) + { + aXclStrm << sal_uInt32( mxTextMark->Len() + 1 ); // string length + 1 trailing zero word + mxTextMark->WriteBuffer( aXclStrm ); // NO flags + aXclStrm << sal_uInt16( 0 ); + + mnFlags |= EXC_HLINK_MARK; + + OUString location = XclXmlUtils::ToOUString(*mxTextMark); + if (!location.isEmpty() && msTarget.endsWith(OUStringConcatenation("#" + location))) + msTarget = msTarget.copy(0, msTarget.getLength() - location.getLength() - 1); + } + + SetRecSize( 32 + mxVarData->Tell() ); +} + +XclExpHyperlink::~XclExpHyperlink() +{ +} + +OUString XclExpHyperlink::BuildFileName( + sal_uInt16& rnLevel, bool& rbRel, const OUString& rUrl, const XclExpRoot& rRoot, bool bEncoded ) +{ + INetURLObject aURLObject( rUrl ); + OUString aDosName(bEncoded ? aURLObject.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) + : aURLObject.getFSysPath(FSysStyle::Dos)); + rnLevel = 0; + rbRel = rRoot.IsRelUrl(); + + if( rbRel ) + { + // try to convert to relative file name + OUString aTmpName( aDosName ); + aDosName = INetURLObject::GetRelURL( rRoot.GetBasePath(), rUrl, + INetURLObject::EncodeMechanism::WasEncoded, + (bEncoded ? INetURLObject::DecodeMechanism::ToIUri : INetURLObject::DecodeMechanism::WithCharset)); + + if (aDosName.startsWith(INET_FILE_SCHEME)) + { + // not converted to rel -> back to old, return absolute flag + aDosName = aTmpName; + rbRel = false; + } + else if (aDosName.startsWith("./")) + { + aDosName = aDosName.copy(2); + } + else + { + while (aDosName.startsWith("../")) + { + aDosName = aDosName.copy(3); + ++rnLevel; + } + } + } + return aDosName; +} + +void XclExpHyperlink::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nXclCol = static_cast< sal_uInt16 >( maScPos.Col() ); + sal_uInt16 nXclRow = static_cast< sal_uInt16 >( maScPos.Row() ); + rStrm << nXclRow << nXclRow << nXclCol << nXclCol; + WriteEmbeddedData( rStrm ); +} + +void XclExpHyperlink::WriteEmbeddedData( XclExpStream& rStrm ) +{ + rStrm << XclTools::maGuidStdLink + << sal_uInt32( 2 ) + << mnFlags; + + mxVarData->Seek( STREAM_SEEK_TO_BEGIN ); + rStrm.CopyFromStream( *mxVarData ); +} + +void XclExpHyperlink::SaveXml( XclExpXmlStream& rStrm ) +{ + OUString sId = !msTarget.isEmpty() ? rStrm.addRelation( rStrm.GetCurrentStream()->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + msTarget, true ) : OUString(); + std::optional<OString> sTextMark; + if (mxTextMark) + sTextMark = XclXmlUtils::ToOString(*mxTextMark); + rStrm.GetCurrentStream()->singleElement( XML_hyperlink, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maScPos), + FSNS( XML_r, XML_id ), sax_fastparser::UseIf(sId, !sId.isEmpty()), + XML_location, sTextMark, + // OOXTODO: XML_tooltip, from record HLinkTooltip 800h wzTooltip + XML_display, m_Repr ); +} + +// Label ranges =============================================================== + +XclExpLabelranges::XclExpLabelranges( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + SCTAB nScTab = GetCurrScTab(); + // row label ranges + FillRangeList( maRowRanges, rRoot.GetDoc().GetRowNameRangesRef(), nScTab ); + // row labels only over 1 column (restriction of Excel97/2000/XP) + for ( size_t i = 0, nRanges = maRowRanges.size(); i < nRanges; ++i ) + { + ScRange & rScRange = maRowRanges[ i ]; + if( rScRange.aStart.Col() != rScRange.aEnd.Col() ) + rScRange.aEnd.SetCol( rScRange.aStart.Col() ); + } + // col label ranges + FillRangeList( maColRanges, rRoot.GetDoc().GetColNameRangesRef(), nScTab ); +} + +void XclExpLabelranges::FillRangeList( ScRangeList& rScRanges, + const ScRangePairListRef& xLabelRangesRef, SCTAB nScTab ) +{ + for ( size_t i = 0, nPairs = xLabelRangesRef->size(); i < nPairs; ++i ) + { + const ScRangePair & rRangePair = (*xLabelRangesRef)[i]; + const ScRange& rScRange = rRangePair.GetRange( 0 ); + if( rScRange.aStart.Tab() == nScTab ) + rScRanges.push_back( rScRange ); + } +} + +void XclExpLabelranges::Save( XclExpStream& rStrm ) +{ + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + XclRangeList aRowXclRanges, aColXclRanges; + rAddrConv.ConvertRangeList( aRowXclRanges, maRowRanges, false ); + rAddrConv.ConvertRangeList( aColXclRanges, maColRanges, false ); + if( !aRowXclRanges.empty() || !aColXclRanges.empty() ) + { + rStrm.StartRecord( EXC_ID_LABELRANGES, 4 + 8 * (aRowXclRanges.size() + aColXclRanges.size()) ); + rStrm << aRowXclRanges << aColXclRanges; + rStrm.EndRecord(); + } +} + +// Conditional formatting ==================================================== + +/** Represents a CF record that contains one condition of a conditional format. */ +class XclExpCFImpl : protected XclExpRoot +{ +public: + explicit XclExpCFImpl( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ); + + /** Writes the body of the CF record. */ + void WriteBody( XclExpStream& rStrm ); + void SaveXml( XclExpXmlStream& rStrm ); + +private: + const ScCondFormatEntry& mrFormatEntry; /// Calc conditional format entry. + ScAddress maOrigin; /// Top left cell of the combined range + XclFontData maFontData; /// Font formatting attributes. + XclExpCellBorder maBorder; /// Border formatting attributes. + XclExpCellArea maArea; /// Pattern formatting attributes. + XclTokenArrayRef mxTokArr1; /// Formula for first condition. + XclTokenArrayRef mxTokArr2; /// Formula for second condition. + sal_uInt32 mnFontColorId; /// Font color ID. + sal_uInt8 mnType; /// Type of the condition (cell/formula). + sal_uInt8 mnOperator; /// Comparison operator for cell type. + sal_Int32 mnPriority; /// Priority of this entry; needed for oox export + bool mbFontUsed; /// true = Any font attribute used. + bool mbHeightUsed; /// true = Font height used. + bool mbWeightUsed; /// true = Font weight used. + bool mbColorUsed; /// true = Font color used. + bool mbUnderlUsed; /// true = Font underline type used. + bool mbItalicUsed; /// true = Font posture used. + bool mbStrikeUsed; /// true = Font strikeout used. + bool mbBorderUsed; /// true = Border attribute used. + bool mbPattUsed; /// true = Pattern attribute used. + bool mbFormula2; +}; + +XclExpCFImpl::XclExpCFImpl( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ) : + XclExpRoot( rRoot ), + mrFormatEntry( rFormatEntry ), + maOrigin( aOrigin ), + mnFontColorId( 0 ), + mnType( EXC_CF_TYPE_CELL ), + mnOperator( EXC_CF_CMP_NONE ), + mnPriority( nPriority ), + mbFontUsed( false ), + mbHeightUsed( false ), + mbWeightUsed( false ), + mbColorUsed( false ), + mbUnderlUsed( false ), + mbItalicUsed( false ), + mbStrikeUsed( false ), + mbBorderUsed( false ), + mbPattUsed( false ), + mbFormula2(false) +{ + // Set correct tab for maOrigin from GetValidSrcPos() of the format-entry. + ScAddress aValidSrcPos = mrFormatEntry.GetValidSrcPos(); + maOrigin.SetTab(aValidSrcPos.Tab()); + + /* Get formatting attributes here, and not in WriteBody(). This is needed to + correctly insert all colors into the palette. */ + + if( SfxStyleSheetBase* pStyleSheet = GetDoc().GetStyleSheetPool()->Find( mrFormatEntry.GetStyle(), SfxStyleFamily::Para ) ) + { + const SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + + // font + mbHeightUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_HEIGHT, true ); + mbWeightUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_WEIGHT, true ); + mbColorUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_COLOR, true ); + mbUnderlUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_UNDERLINE, true ); + mbItalicUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_POSTURE, true ); + mbStrikeUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_CROSSEDOUT, true ); + mbFontUsed = mbHeightUsed || mbWeightUsed || mbColorUsed || mbUnderlUsed || mbItalicUsed || mbStrikeUsed; + if( mbFontUsed ) + { + vcl::Font aFont; + ScPatternAttr::GetFont( aFont, rItemSet, SC_AUTOCOL_RAW ); + maFontData.FillFromVclFont( aFont ); + mnFontColorId = GetPalette().InsertColor( maFontData.maColor, EXC_COLOR_CELLTEXT ); + } + + // border + mbBorderUsed = ScfTools::CheckItem( rItemSet, ATTR_BORDER, true ); + if( mbBorderUsed ) + maBorder.FillFromItemSet( rItemSet, GetPalette(), GetBiff() ); + + // pattern + mbPattUsed = ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, true ); + if( mbPattUsed ) + maArea.FillFromItemSet( rItemSet, GetPalette(), true ); + } + + // *** mode and comparison operator *** + + switch( rFormatEntry.GetOperation() ) + { + case ScConditionMode::NONE: + mnType = EXC_CF_TYPE_NONE; + break; + case ScConditionMode::Between: + mnOperator = EXC_CF_CMP_BETWEEN; + mbFormula2 = true; + break; + case ScConditionMode::NotBetween: + mnOperator = EXC_CF_CMP_NOT_BETWEEN; + mbFormula2 = true; + break; + case ScConditionMode::Equal: + mnOperator = EXC_CF_CMP_EQUAL; + break; + case ScConditionMode::NotEqual: + mnOperator = EXC_CF_CMP_NOT_EQUAL; + break; + case ScConditionMode::Greater: + mnOperator = EXC_CF_CMP_GREATER; + break; + case ScConditionMode::Less: + mnOperator = EXC_CF_CMP_LESS; + break; + case ScConditionMode::EqGreater: + mnOperator = EXC_CF_CMP_GREATER_EQUAL; + break; + case ScConditionMode::EqLess: + mnOperator = EXC_CF_CMP_LESS_EQUAL; + break; + case ScConditionMode::Direct: + mnType = EXC_CF_TYPE_FMLA; + break; + default: + mnType = EXC_CF_TYPE_NONE; + OSL_FAIL( "XclExpCF::WriteBody - unknown condition type" ); + } +} + +void XclExpCFImpl::WriteBody( XclExpStream& rStrm ) +{ + + // *** formulas *** + + XclExpFormulaCompiler& rFmlaComp = GetFormulaCompiler(); + + std::unique_ptr< ScTokenArray > xScTokArr( mrFormatEntry.CreateFlatCopiedTokenArray( 0 ) ); + mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_CONDFMT, *xScTokArr ); + + if (mbFormula2) + { + xScTokArr = mrFormatEntry.CreateFlatCopiedTokenArray( 1 ); + mxTokArr2 = rFmlaComp.CreateFormula( EXC_FMLATYPE_CONDFMT, *xScTokArr ); + } + + // *** mode and comparison operator *** + + rStrm << mnType << mnOperator; + + // *** formula sizes *** + + sal_uInt16 nFmlaSize1 = mxTokArr1 ? mxTokArr1->GetSize() : 0; + sal_uInt16 nFmlaSize2 = mxTokArr2 ? mxTokArr2->GetSize() : 0; + rStrm << nFmlaSize1 << nFmlaSize2; + + // *** formatting blocks *** + + if( mbFontUsed || mbBorderUsed || mbPattUsed ) + { + sal_uInt32 nFlags = EXC_CF_ALLDEFAULT; + + ::set_flag( nFlags, EXC_CF_BLOCK_FONT, mbFontUsed ); + ::set_flag( nFlags, EXC_CF_BLOCK_BORDER, mbBorderUsed ); + ::set_flag( nFlags, EXC_CF_BLOCK_AREA, mbPattUsed ); + + // attributes used -> set flags to 0. + ::set_flag( nFlags, EXC_CF_BORDER_ALL, !mbBorderUsed ); + ::set_flag( nFlags, EXC_CF_AREA_ALL, !mbPattUsed ); + + rStrm << nFlags << sal_uInt16( 0 ); + + if( mbFontUsed ) + { + // font height, 0xFFFFFFFF indicates unused + sal_uInt32 nHeight = mbHeightUsed ? maFontData.mnHeight : 0xFFFFFFFF; + // font style: italic and strikeout + sal_uInt32 nStyle = 0; + ::set_flag( nStyle, EXC_CF_FONT_STYLE, maFontData.mbItalic ); + ::set_flag( nStyle, EXC_CF_FONT_STRIKEOUT, maFontData.mbStrikeout ); + // font color, 0xFFFFFFFF indicates unused + sal_uInt32 nColor = mbColorUsed ? GetPalette().GetColorIndex( mnFontColorId ) : 0xFFFFFFFF; + // font used flags for italic, weight, and strikeout -> 0 = used, 1 = default + sal_uInt32 nFontFlags1 = EXC_CF_FONT_ALLDEFAULT; + ::set_flag( nFontFlags1, EXC_CF_FONT_STYLE, !(mbItalicUsed || mbWeightUsed) ); + ::set_flag( nFontFlags1, EXC_CF_FONT_STRIKEOUT, !mbStrikeUsed ); + // font used flag for underline -> 0 = used, 1 = default + sal_uInt32 nFontFlags3 = mbUnderlUsed ? 0 : EXC_CF_FONT_UNDERL; + + rStrm.WriteZeroBytesToRecord( 64 ); + rStrm << nHeight + << nStyle + << maFontData.mnWeight + << EXC_FONTESC_NONE + << maFontData.mnUnderline; + rStrm.WriteZeroBytesToRecord( 3 ); + rStrm << nColor + << sal_uInt32( 0 ) + << nFontFlags1 + << EXC_CF_FONT_ESCAPEM // escapement never used -> set the flag + << nFontFlags3; + rStrm.WriteZeroBytesToRecord( 16 ); + rStrm << sal_uInt16( 1 ); // must be 1 + } + + if( mbBorderUsed ) + { + sal_uInt16 nLineStyle = 0; + sal_uInt32 nLineColor = 0; + maBorder.SetFinalColors( GetPalette() ); + maBorder.FillToCF8( nLineStyle, nLineColor ); + rStrm << nLineStyle << nLineColor << sal_uInt16( 0 ); + } + + if( mbPattUsed ) + { + sal_uInt16 nPattern = 0, nColor = 0; + maArea.SetFinalColors( GetPalette() ); + maArea.FillToCF8( nPattern, nColor ); + rStrm << nPattern << nColor; + } + } + else + { + // no data blocks at all + rStrm << sal_uInt32( 0 ) << sal_uInt16( 0 ); + } + + // *** formulas *** + + if( mxTokArr1 ) + mxTokArr1->WriteArray( rStrm ); + if( mxTokArr2 ) + mxTokArr2->WriteArray( rStrm ); +} + +namespace { + +const char* GetOperatorString(ScConditionMode eMode, bool& bFrmla2) +{ + const char *pRet = nullptr; + switch(eMode) + { + case ScConditionMode::Equal: + pRet = "equal"; + break; + case ScConditionMode::Less: + pRet = "lessThan"; + break; + case ScConditionMode::Greater: + pRet = "greaterThan"; + break; + case ScConditionMode::EqLess: + pRet = "lessThanOrEqual"; + break; + case ScConditionMode::EqGreater: + pRet = "greaterThanOrEqual"; + break; + case ScConditionMode::NotEqual: + pRet = "notEqual"; + break; + case ScConditionMode::Between: + bFrmla2 = true; + pRet = "between"; + break; + case ScConditionMode::NotBetween: + bFrmla2 = true; + pRet = "notBetween"; + break; + case ScConditionMode::Duplicate: + pRet = nullptr; + break; + case ScConditionMode::NotDuplicate: + pRet = nullptr; + break; + case ScConditionMode::BeginsWith: + pRet = "beginsWith"; + break; + case ScConditionMode::EndsWith: + pRet = "endsWith"; + break; + case ScConditionMode::ContainsText: + pRet = "containsText"; + break; + case ScConditionMode::NotContainsText: + pRet = "notContains"; + break; + case ScConditionMode::Direct: + break; + case ScConditionMode::NONE: + default: + break; + } + return pRet; +} + +const char* GetTypeString(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::Direct: + return "expression"; + case ScConditionMode::Top10: + case ScConditionMode::TopPercent: + case ScConditionMode::Bottom10: + case ScConditionMode::BottomPercent: + return "top10"; + case ScConditionMode::AboveAverage: + case ScConditionMode::BelowAverage: + case ScConditionMode::AboveEqualAverage: + case ScConditionMode::BelowEqualAverage: + return "aboveAverage"; + case ScConditionMode::NotDuplicate: + return "uniqueValues"; + case ScConditionMode::Duplicate: + return "duplicateValues"; + case ScConditionMode::Error: + return "containsErrors"; + case ScConditionMode::NoError: + return "notContainsErrors"; + case ScConditionMode::BeginsWith: + return "beginsWith"; + case ScConditionMode::EndsWith: + return "endsWith"; + case ScConditionMode::ContainsText: + return "containsText"; + case ScConditionMode::NotContainsText: + return "notContainsText"; + default: + return "cellIs"; + } +} + +bool IsTopBottomRule(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::Top10: + case ScConditionMode::Bottom10: + case ScConditionMode::TopPercent: + case ScConditionMode::BottomPercent: + return true; + default: + break; + } + + return false; +} + +bool IsTextRule(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + return true; + default: + break; + } + + return false; +} + +bool RequiresFormula(ScConditionMode eMode) +{ + if (IsTopBottomRule(eMode)) + return false; + else if (IsTextRule(eMode)) + return false; + + switch (eMode) + { + case ScConditionMode::NoError: + case ScConditionMode::Error: + case ScConditionMode::Duplicate: + case ScConditionMode::NotDuplicate: + return false; + default: + break; + } + + return true; +} + +bool RequiresFixedFormula(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::NoError: + case ScConditionMode::Error: + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + return true; + default: + break; + } + + return false; +} + +OString GetFixedFormula(ScConditionMode eMode, const ScAddress& rAddress, std::string_view rText) +{ + OStringBuffer aBuffer; + XclXmlUtils::ToOString(aBuffer, rAddress); + OString aPos = aBuffer.makeStringAndClear(); + switch (eMode) + { + case ScConditionMode::Error: + return OString("ISERROR(" + aPos + ")") ; + case ScConditionMode::NoError: + return OString("NOT(ISERROR(" + aPos + "))") ; + case ScConditionMode::BeginsWith: + return OString("LEFT(" + aPos + ",LEN(\"" + rText + "\"))=\"" + rText + "\""); + case ScConditionMode::EndsWith: + return OString("RIGHT(" + aPos +",LEN(\"" + rText + "\"))=\"" + rText + "\""); + case ScConditionMode::ContainsText: + return OString(OString::Concat("NOT(ISERROR(SEARCH(\"") + rText + "\"," + aPos + ")))"); + case ScConditionMode::NotContainsText: + return OString(OString::Concat("ISERROR(SEARCH(\"") + rText + "\"," + aPos + "))"); + default: + break; + } + + return ""; +} + +} + +void XclExpCFImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + bool bFmla2 = false; + ScConditionMode eOperation = mrFormatEntry.GetOperation(); + bool bAboveAverage = eOperation == ScConditionMode::AboveAverage || + eOperation == ScConditionMode::AboveEqualAverage; + bool bEqualAverage = eOperation == ScConditionMode::AboveEqualAverage || + eOperation == ScConditionMode::BelowEqualAverage; + bool bBottom = eOperation == ScConditionMode::Bottom10 + || eOperation == ScConditionMode::BottomPercent; + bool bPercent = eOperation == ScConditionMode::TopPercent || + eOperation == ScConditionMode::BottomPercent; + OUString aRank("0"); + if(IsTopBottomRule(eOperation)) + { + // position and formula grammar are not important + // we only store a number there + aRank = mrFormatEntry.GetExpression(ScAddress(0,0,0), 0); + } + OString aText; + if(IsTextRule(eOperation)) + { + // we need to write the text without quotes + // we have to actually get the string from + // the token array for that + std::unique_ptr<ScTokenArray> pTokenArray(mrFormatEntry.CreateFlatCopiedTokenArray(0)); + if(pTokenArray->GetLen()) + aText = pTokenArray->FirstToken()->GetString().getString().toUtf8(); + } + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_cfRule, + XML_type, GetTypeString( mrFormatEntry.GetOperation() ), + XML_priority, OString::number(mnPriority + 1), + XML_operator, GetOperatorString( mrFormatEntry.GetOperation(), bFmla2 ), + XML_aboveAverage, ToPsz10(bAboveAverage), + XML_equalAverage, ToPsz10(bEqualAverage), + XML_bottom, ToPsz10(bBottom), + XML_percent, ToPsz10(bPercent), + XML_rank, aRank, + XML_text, aText, + XML_dxfId, OString::number(GetDxfs().GetDxfId(mrFormatEntry.GetStyle())) ); + + if (RequiresFixedFormula(eOperation)) + { + rWorksheet->startElement(XML_formula); + OString aFormula = GetFixedFormula(eOperation, maOrigin, aText); + rWorksheet->writeEscaped(aFormula.getStr()); + rWorksheet->endElement( XML_formula ); + } + else if(RequiresFormula(eOperation)) + { + rWorksheet->startElement(XML_formula); + std::unique_ptr<ScTokenArray> pTokenArray(mrFormatEntry.CreateFlatCopiedTokenArray(0)); + rWorksheet->writeEscaped(XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormatEntry.GetValidSrcPos(), + pTokenArray.get())); + rWorksheet->endElement( XML_formula ); + if (bFmla2) + { + rWorksheet->startElement(XML_formula); + std::unique_ptr<ScTokenArray> pTokenArray2(mrFormatEntry.CreateFlatCopiedTokenArray(1)); + rWorksheet->writeEscaped(XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormatEntry.GetValidSrcPos(), + pTokenArray2.get())); + rWorksheet->endElement( XML_formula ); + } + } + // OOXTODO: XML_extLst + rWorksheet->endElement( XML_cfRule ); +} + +XclExpCF::XclExpCF( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ) : + XclExpRecord( EXC_ID_CF ), + XclExpRoot( rRoot ), + mxImpl( new XclExpCFImpl( rRoot, rFormatEntry, nPriority, aOrigin ) ) +{ +} + +XclExpCF::~XclExpCF() +{ +} + +void XclExpCF::WriteBody( XclExpStream& rStrm ) +{ + mxImpl->WriteBody( rStrm ); +} + +void XclExpCF::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +XclExpDateFormat::XclExpDateFormat( const XclExpRoot& rRoot, const ScCondDateFormatEntry& rFormatEntry, sal_Int32 nPriority ): + XclExpRecord( EXC_ID_CF ), + XclExpRoot( rRoot ), + mrFormatEntry(rFormatEntry), + mnPriority(nPriority) +{ +} + +XclExpDateFormat::~XclExpDateFormat() +{ +} + +namespace { + +const char* getTimePeriodString( condformat::ScCondFormatDateType eType ) +{ + switch(eType) + { + case condformat::TODAY: + return "today"; + case condformat::YESTERDAY: + return "yesterday"; + case condformat::TOMORROW: + return "yesterday"; + case condformat::THISWEEK: + return "thisWeek"; + case condformat::LASTWEEK: + return "lastWeek"; + case condformat::NEXTWEEK: + return "nextWeek"; + case condformat::THISMONTH: + return "thisMonth"; + case condformat::LASTMONTH: + return "lastMonth"; + case condformat::NEXTMONTH: + return "nextMonth"; + case condformat::LAST7DAYS: + return "last7Days"; + default: + break; + } + return nullptr; +} + +} + +void XclExpDateFormat::SaveXml( XclExpXmlStream& rStrm ) +{ + // only write the supported entries into OOXML + const char* sTimePeriod = getTimePeriodString(mrFormatEntry.GetDateType()); + if(!sTimePeriod) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_cfRule, + XML_type, "timePeriod", + XML_priority, OString::number(mnPriority + 1), + XML_timePeriod, sTimePeriod, + XML_dxfId, OString::number(GetDxfs().GetDxfId(mrFormatEntry.GetStyleName())) ); + rWorksheet->endElement( XML_cfRule); +} + +XclExpCfvo::XclExpCfvo(const XclExpRoot& rRoot, const ScColorScaleEntry& rEntry, const ScAddress& rAddr, bool bFirst): + XclExpRoot( rRoot ), + mrEntry(rEntry), + maSrcPos(rAddr), + mbFirst(bFirst) +{ +} + +namespace { + +OString getColorScaleType( const ScColorScaleEntry& rEntry, bool bFirst ) +{ + switch(rEntry.GetType()) + { + case COLORSCALE_MIN: + return "min"; + case COLORSCALE_MAX: + return "max"; + case COLORSCALE_PERCENT: + return "percent"; + case COLORSCALE_FORMULA: + return "formula"; + case COLORSCALE_AUTO: + if(bFirst) + return "min"; + else + return "max"; + case COLORSCALE_PERCENTILE: + return "percentile"; + default: + break; + } + + return "num"; +} + +} + +void XclExpCfvo::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + OString aValue; + if(mrEntry.GetType() == COLORSCALE_FORMULA) + { + OUString aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), maSrcPos, + mrEntry.GetFormula()); + aValue = OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8 ); + } + else + { + aValue = OString::number( mrEntry.GetValue() ); + } + + rWorksheet->startElement( XML_cfvo, + XML_type, getColorScaleType(mrEntry, mbFirst), + XML_val, aValue ); + + rWorksheet->endElement( XML_cfvo ); +} + +XclExpColScaleCol::XclExpColScaleCol( const XclExpRoot& rRoot, const Color& rColor ): + XclExpRoot( rRoot ), + mrColor( rColor ) +{ +} + +XclExpColScaleCol::~XclExpColScaleCol() +{ +} + +void XclExpColScaleCol::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(XML_color, XML_rgb, XclXmlUtils::ToOString(mrColor)); + + rWorksheet->endElement( XML_color ); +} + +namespace { + +OString createHexStringFromDigit(sal_uInt8 nDigit) +{ + OString aString = OString::number( nDigit, 16 ); + if(aString.getLength() == 1) + aString += OString::number(0); + return aString; +} + +OString createGuidStringFromInt(sal_uInt8 nGuid[16]) +{ + OStringBuffer aBuffer; + aBuffer.append('{'); + for(size_t i = 0; i < 16; ++i) + { + aBuffer.append(createHexStringFromDigit(nGuid[i])); + if(i == 3|| i == 5 || i == 7 || i == 9 ) + aBuffer.append('-'); + } + aBuffer.append('}'); + OString aString = aBuffer.makeStringAndClear(); + return aString.toAsciiUpperCase(); +} + +OString generateGUIDString() +{ + sal_uInt8 nGuid[16]; + rtl_createUuid(nGuid, nullptr, true); + return createGuidStringFromInt(nGuid); +} + +} + +XclExpCondfmt::XclExpCondfmt( const XclExpRoot& rRoot, const ScConditionalFormat& rCondFormat, const XclExtLstRef& xExtLst, sal_Int32& rIndex ) : + XclExpRecord( EXC_ID_CONDFMT ), + XclExpRoot( rRoot ) +{ + const ScRangeList& aScRanges = rCondFormat.GetRange(); + GetAddressConverter().ConvertRangeList( maXclRanges, aScRanges, true ); + if( maXclRanges.empty() ) + return; + + std::vector<XclExpExtCondFormatData> aExtEntries; + ScAddress aOrigin = aScRanges.Combine().aStart; + for( size_t nIndex = 0, nCount = rCondFormat.size(); nIndex < nCount; ++nIndex ) + if( const ScFormatEntry* pFormatEntry = rCondFormat.GetEntry( nIndex ) ) + { + if(pFormatEntry->GetType() == ScFormatEntry::Type::Condition) + maCFList.AppendNewRecord( new XclExpCF( GetRoot(), static_cast<const ScCondFormatEntry&>(*pFormatEntry), ++rIndex, aOrigin ) ); + else if(pFormatEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + const ScCondFormatEntry& rFormat = static_cast<const ScCondFormatEntry&>(*pFormatEntry); + XclExpExtCondFormatData aExtEntry; + aExtEntry.nPriority = ++rIndex; + aExtEntry.aGUID = generateGUIDString(); + aExtEntry.pEntry = &rFormat; + aExtEntries.push_back(aExtEntry); + } + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Colorscale) + maCFList.AppendNewRecord( new XclExpColorScale( GetRoot(), static_cast<const ScColorScaleFormat&>(*pFormatEntry), ++rIndex ) ); + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Databar) + { + const ScDataBarFormat& rFormat = static_cast<const ScDataBarFormat&>(*pFormatEntry); + XclExpExtCondFormatData aExtEntry; + aExtEntry.nPriority = -1; + aExtEntry.aGUID = generateGUIDString(); + aExtEntry.pEntry = &rFormat; + aExtEntries.push_back(aExtEntry); + + maCFList.AppendNewRecord( new XclExpDataBar( GetRoot(), rFormat, ++rIndex, aExtEntry.aGUID)); + } + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Iconset) + { + // don't export iconSet entries that are not in OOXML + const ScIconSetFormat& rIconSet = static_cast<const ScIconSetFormat&>(*pFormatEntry); + bool bNeedsExt = false; + switch (rIconSet.GetIconSetData()->eIconSetType) + { + case IconSet_3Smilies: + case IconSet_3ColorSmilies: + case IconSet_3Stars: + case IconSet_3Triangles: + case IconSet_5Boxes: + { + bNeedsExt = true; + } + break; + default: + break; + } + + bNeedsExt |= rIconSet.GetIconSetData()->mbCustom; + + if (bNeedsExt) + { + XclExpExtCondFormatData aExtEntry; + aExtEntry.nPriority = ++rIndex; + aExtEntry.aGUID = generateGUIDString(); + aExtEntry.pEntry = &rIconSet; + aExtEntries.push_back(aExtEntry); + } + else + maCFList.AppendNewRecord( new XclExpIconSet( GetRoot(), rIconSet, ++rIndex ) ); + } + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Date) + maCFList.AppendNewRecord( new XclExpDateFormat( GetRoot(), static_cast<const ScCondDateFormatEntry&>(*pFormatEntry), ++rIndex ) ); + } + aScRanges.Format( msSeqRef, ScRefFlags::VALID, GetDoc(), formula::FormulaGrammar::CONV_XL_OOX, ' ', true ); + + if(!aExtEntries.empty() && xExtLst) + { + XclExpExt* pParent = xExtLst->GetItem( XclExpExtDataBarType ); + if( !pParent ) + { + xExtLst->AddRecord( new XclExpExtCondFormat( *xExtLst ) ); + pParent = xExtLst->GetItem( XclExpExtDataBarType ); + } + static_cast<XclExpExtCondFormat*>(xExtLst->GetItem( XclExpExtDataBarType ))->AddRecord( + new XclExpExtConditionalFormatting( *pParent, aExtEntries, aScRanges)); + } +} + +XclExpCondfmt::~XclExpCondfmt() +{ +} + +bool XclExpCondfmt::IsValidForBinary() const +{ + // ccf (2 bytes): An unsigned integer that specifies the count of CF records that follow this + // record. MUST be greater than or equal to 0x0001, and less than or equal to 0x0003. + + SAL_WARN_IF( maCFList.GetSize() > 3, "sc.filter", "More than 3 conditional filters for cell(s), won't export"); + + return !maCFList.IsEmpty() && maCFList.GetSize() <= 3 && !maXclRanges.empty(); +} + +bool XclExpCondfmt::IsValidForXml() const +{ + return !maCFList.IsEmpty() && !maXclRanges.empty(); +} + +void XclExpCondfmt::Save( XclExpStream& rStrm ) +{ + if( IsValidForBinary() ) + { + XclExpRecord::Save( rStrm ); + maCFList.Save( rStrm ); + } +} + +void XclExpCondfmt::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE( !maCFList.IsEmpty(), "XclExpCondfmt::WriteBody - no CF records to write" ); + OSL_ENSURE( !maXclRanges.empty(), "XclExpCondfmt::WriteBody - no cell ranges found" ); + + rStrm << static_cast< sal_uInt16 >( maCFList.GetSize() ) + << sal_uInt16( 1 ) + << maXclRanges.GetEnclosingRange() + << maXclRanges; +} + +void XclExpCondfmt::SaveXml( XclExpXmlStream& rStrm ) +{ + if( !IsValidForXml() ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_conditionalFormatting, + XML_sqref, msSeqRef + // OOXTODO: XML_pivot + ); + + maCFList.SaveXml( rStrm ); + + rWorksheet->endElement( XML_conditionalFormatting ); +} + +XclExpColorScale::XclExpColorScale( const XclExpRoot& rRoot, const ScColorScaleFormat& rFormat, sal_Int32 nPriority ): + XclExpRoot( rRoot ), + mnPriority( nPriority ) +{ + const ScRange & rRange = rFormat.GetRange().front(); + ScAddress aAddr = rRange.aStart; + for(const auto& rxColorScaleEntry : rFormat) + { + // exact position is not important, we allow only absolute refs + + XclExpCfvoList::RecordRefType xCfvo( new XclExpCfvo( GetRoot(), *rxColorScaleEntry, aAddr ) ); + maCfvoList.AppendRecord( xCfvo ); + XclExpColScaleColList::RecordRefType xClo( new XclExpColScaleCol( GetRoot(), rxColorScaleEntry->GetColor() ) ); + maColList.AppendRecord( xClo ); + } +} + +void XclExpColorScale::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_cfRule, + XML_type, "colorScale", + XML_priority, OString::number(mnPriority + 1) ); + + rWorksheet->startElement(XML_colorScale); + + maCfvoList.SaveXml(rStrm); + maColList.SaveXml(rStrm); + + rWorksheet->endElement( XML_colorScale ); + + rWorksheet->endElement( XML_cfRule ); +} + +XclExpDataBar::XclExpDataBar( const XclExpRoot& rRoot, const ScDataBarFormat& rFormat, sal_Int32 nPriority, const OString& rGUID): + XclExpRoot( rRoot ), + mrFormat( rFormat ), + mnPriority( nPriority ), + maGUID(rGUID) +{ + const ScRange & rRange = rFormat.GetRange().front(); + ScAddress aAddr = rRange.aStart; + // exact position is not important, we allow only absolute refs + mpCfvoLowerLimit.reset( + new XclExpCfvo(GetRoot(), *mrFormat.GetDataBarData()->mpLowerLimit, aAddr, true)); + mpCfvoUpperLimit.reset( + new XclExpCfvo(GetRoot(), *mrFormat.GetDataBarData()->mpUpperLimit, aAddr, false)); + + mpCol.reset( new XclExpColScaleCol( GetRoot(), mrFormat.GetDataBarData()->maPositiveColor ) ); +} + +void XclExpDataBar::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_cfRule, + XML_type, "dataBar", + XML_priority, OString::number(mnPriority + 1) ); + + rWorksheet->startElement( XML_dataBar, + XML_showValue, ToPsz10(!mrFormat.GetDataBarData()->mbOnlyBar), + XML_minLength, OString::number(sal_uInt32(mrFormat.GetDataBarData()->mnMinLength)), + XML_maxLength, OString::number(sal_uInt32(mrFormat.GetDataBarData()->mnMaxLength)) ); + + mpCfvoLowerLimit->SaveXml(rStrm); + mpCfvoUpperLimit->SaveXml(rStrm); + mpCol->SaveXml(rStrm); + + rWorksheet->endElement( XML_dataBar ); + + // extLst entries for Excel 2010 and 2013 + rWorksheet->startElement(XML_extLst); + rWorksheet->startElement(XML_ext, + FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)), + XML_uri, "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"); + + rWorksheet->startElementNS( XML_x14, XML_id ); + rWorksheet->write(maGUID); + rWorksheet->endElementNS( XML_x14, XML_id ); + + rWorksheet->endElement( XML_ext ); + rWorksheet->endElement( XML_extLst ); + + rWorksheet->endElement( XML_cfRule ); +} + +XclExpIconSet::XclExpIconSet( const XclExpRoot& rRoot, const ScIconSetFormat& rFormat, sal_Int32 nPriority ): + XclExpRoot( rRoot ), + mrFormat( rFormat ), + mnPriority( nPriority ) +{ + const ScRange & rRange = rFormat.GetRange().front(); + ScAddress aAddr = rRange.aStart; + for (auto const& itr : rFormat) + { + // exact position is not important, we allow only absolute refs + + XclExpCfvoList::RecordRefType xCfvo( new XclExpCfvo( GetRoot(), *itr, aAddr ) ); + maCfvoList.AppendRecord( xCfvo ); + } +} + +void XclExpIconSet::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_cfRule, + XML_type, "iconSet", + XML_priority, OString::number(mnPriority + 1) ); + + const char* pIconSetName = ScIconSetFormat::getIconSetName(mrFormat.GetIconSetData()->eIconSetType); + rWorksheet->startElement( XML_iconSet, + XML_iconSet, pIconSetName, + XML_showValue, sax_fastparser::UseIf("0", !mrFormat.GetIconSetData()->mbShowValue), + XML_reverse, sax_fastparser::UseIf("1", mrFormat.GetIconSetData()->mbReverse)); + + maCfvoList.SaveXml( rStrm ); + + rWorksheet->endElement( XML_iconSet ); + rWorksheet->endElement( XML_cfRule ); +} + +XclExpCondFormatBuffer::XclExpCondFormatBuffer( const XclExpRoot& rRoot, const XclExtLstRef& xExtLst ) : + XclExpRoot( rRoot ) +{ + if( const ScConditionalFormatList* pCondFmtList = GetDoc().GetCondFormList(GetCurrScTab()) ) + { + sal_Int32 nIndex = 0; + for( const auto& rxCondFmt : *pCondFmtList) + { + XclExpCondfmtList::RecordRefType xCondfmtRec( new XclExpCondfmt( GetRoot(), *rxCondFmt, xExtLst, nIndex )); + if( xCondfmtRec->IsValidForXml() ) + maCondfmtList.AppendRecord( xCondfmtRec ); + } + } +} + +void XclExpCondFormatBuffer::Save( XclExpStream& rStrm ) +{ + maCondfmtList.Save( rStrm ); +} + +void XclExpCondFormatBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + maCondfmtList.SaveXml( rStrm ); +} + +// Validation ================================================================= + +namespace { + +/** Writes a formula for the DV record. */ +void lclWriteDvFormula( XclExpStream& rStrm, const XclTokenArray* pXclTokArr ) +{ + sal_uInt16 nFmlaSize = pXclTokArr ? pXclTokArr->GetSize() : 0; + rStrm << nFmlaSize << sal_uInt16( 0 ); + if( pXclTokArr ) + pXclTokArr->WriteArray( rStrm ); +} + +/** Writes a formula for the DV record, based on a single string. */ +void lclWriteDvFormula( XclExpStream& rStrm, const XclExpString& rString ) +{ + // fake a formula with a single tStr token + rStrm << static_cast< sal_uInt16 >( rString.GetSize() + 1 ) + << sal_uInt16( 0 ) + << EXC_TOKID_STR + << rString; +} + +const char* lcl_GetValidationType( sal_uInt32 nFlags ) +{ + switch( nFlags & EXC_DV_MODE_MASK ) + { + case EXC_DV_MODE_ANY: return "none"; + case EXC_DV_MODE_WHOLE: return "whole"; + case EXC_DV_MODE_DECIMAL: return "decimal"; + case EXC_DV_MODE_LIST: return "list"; + case EXC_DV_MODE_DATE: return "date"; + case EXC_DV_MODE_TIME: return "time"; + case EXC_DV_MODE_TEXTLEN: return "textLength"; + case EXC_DV_MODE_CUSTOM: return "custom"; + } + return nullptr; +} + +const char* lcl_GetOperatorType( sal_uInt32 nFlags ) +{ + switch( nFlags & EXC_DV_COND_MASK ) + { + case EXC_DV_COND_BETWEEN: return "between"; + case EXC_DV_COND_NOTBETWEEN: return "notBetween"; + case EXC_DV_COND_EQUAL: return "equal"; + case EXC_DV_COND_NOTEQUAL: return "notEqual"; + case EXC_DV_COND_GREATER: return "greaterThan"; + case EXC_DV_COND_LESS: return "lessThan"; + case EXC_DV_COND_EQGREATER: return "greaterThanOrEqual"; + case EXC_DV_COND_EQLESS: return "lessThanOrEqual"; + } + return nullptr; +} + +const char* lcl_GetErrorType( sal_uInt32 nFlags ) +{ + switch (nFlags & EXC_DV_ERROR_MASK) + { + case EXC_DV_ERROR_STOP: return "stop"; + case EXC_DV_ERROR_WARNING: return "warning"; + case EXC_DV_ERROR_INFO: return "information"; + } + return nullptr; +} + +void lcl_SetValidationText(const OUString& rText, XclExpString& rValidationText) +{ + if ( !rText.isEmpty() ) + { + // maximum length allowed in Excel is 255 characters + if ( rText.getLength() > 255 ) + { + OUStringBuffer aBuf( rText ); + rValidationText.Assign( + comphelper::string::truncateToLength(aBuf, 255).makeStringAndClear() ); + } + else + rValidationText.Assign( rText ); + } + else + rValidationText.Assign( '\0' ); +} + +} // namespace + +XclExpDV::XclExpDV( const XclExpRoot& rRoot, sal_uLong nScHandle ) : + XclExpRecord( EXC_ID_DV ), + XclExpRoot( rRoot ), + mnFlags( 0 ), + mnScHandle( nScHandle ) +{ + if( const ScValidationData* pValData = GetDoc().GetValidationEntry( mnScHandle ) ) + { + // prompt box - empty string represented by single NUL character + OUString aTitle, aText; + bool bShowPrompt = pValData->GetInput( aTitle, aText ); + lcl_SetValidationText(aTitle, maPromptTitle); + lcl_SetValidationText(aText, maPromptText); + + // error box - empty string represented by single NUL character + ScValidErrorStyle eScErrorStyle; + bool bShowError = pValData->GetErrMsg( aTitle, aText, eScErrorStyle ); + lcl_SetValidationText(aTitle, maErrorTitle); + lcl_SetValidationText(aText, maErrorText); + + // flags + switch( pValData->GetDataMode() ) + { + case SC_VALID_ANY: mnFlags |= EXC_DV_MODE_ANY; break; + case SC_VALID_WHOLE: mnFlags |= EXC_DV_MODE_WHOLE; break; + case SC_VALID_DECIMAL: mnFlags |= EXC_DV_MODE_DECIMAL; break; + case SC_VALID_LIST: mnFlags |= EXC_DV_MODE_LIST; break; + case SC_VALID_DATE: mnFlags |= EXC_DV_MODE_DATE; break; + case SC_VALID_TIME: mnFlags |= EXC_DV_MODE_TIME; break; + case SC_VALID_TEXTLEN: mnFlags |= EXC_DV_MODE_TEXTLEN; break; + case SC_VALID_CUSTOM: mnFlags |= EXC_DV_MODE_CUSTOM; break; + default: OSL_FAIL( "XclExpDV::XclExpDV - unknown mode" ); + } + + switch( pValData->GetOperation() ) + { + case ScConditionMode::NONE: + case ScConditionMode::Equal: mnFlags |= EXC_DV_COND_EQUAL; break; + case ScConditionMode::Less: mnFlags |= EXC_DV_COND_LESS; break; + case ScConditionMode::Greater: mnFlags |= EXC_DV_COND_GREATER; break; + case ScConditionMode::EqLess: mnFlags |= EXC_DV_COND_EQLESS; break; + case ScConditionMode::EqGreater: mnFlags |= EXC_DV_COND_EQGREATER; break; + case ScConditionMode::NotEqual: mnFlags |= EXC_DV_COND_NOTEQUAL; break; + case ScConditionMode::Between: mnFlags |= EXC_DV_COND_BETWEEN; break; + case ScConditionMode::NotBetween: mnFlags |= EXC_DV_COND_NOTBETWEEN; break; + default: OSL_FAIL( "XclExpDV::XclExpDV - unknown condition" ); + } + switch( eScErrorStyle ) + { + case SC_VALERR_STOP: mnFlags |= EXC_DV_ERROR_STOP; break; + case SC_VALERR_WARNING: mnFlags |= EXC_DV_ERROR_WARNING; break; + case SC_VALERR_INFO: mnFlags |= EXC_DV_ERROR_INFO; break; + case SC_VALERR_MACRO: + // set INFO for validity with macro call, delete title + mnFlags |= EXC_DV_ERROR_INFO; + maErrorTitle.Assign( '\0' ); // contains macro name + break; + default: OSL_FAIL( "XclExpDV::XclExpDV - unknown error style" ); + } + ::set_flag( mnFlags, EXC_DV_IGNOREBLANK, pValData->IsIgnoreBlank() ); + ::set_flag( mnFlags, EXC_DV_SUPPRESSDROPDOWN, pValData->GetListType() == css::sheet::TableValidationVisibility::INVISIBLE ); + ::set_flag( mnFlags, EXC_DV_SHOWPROMPT, bShowPrompt ); + ::set_flag( mnFlags, EXC_DV_SHOWERROR, bShowError ); + + // formulas + XclExpFormulaCompiler& rFmlaComp = GetFormulaCompiler(); + + // first formula + std::unique_ptr< ScTokenArray > xScTokArr = pValData->CreateFlatCopiedTokenArray( 0 ); + if (xScTokArr) + { + if( pValData->GetDataMode() == SC_VALID_LIST ) + { + OUString aString; + if( XclTokenArrayHelper::GetStringList( aString, *xScTokArr, '\n' ) ) + { + bool bList = false; + OUStringBuffer sListBuf; + OUStringBuffer sFormulaBuf; + sFormulaBuf.append( '"' ); + /* Formula is a list of string tokens -> build the Excel string. + Data validity is BIFF8 only (important for the XclExpString object). + Excel uses the NUL character as string list separator. */ + mxString1.reset( new XclExpString( XclStrFlags::EightBitLength ) ); + if (!aString.isEmpty()) + { + sal_Int32 nStringIx = 0; + for(;;) + { + const std::u16string_view aToken( o3tl::getToken(aString, 0, '\n', nStringIx ) ); + if (aToken.find(',') != std::u16string_view::npos) + { + sListBuf.append('"'); + sListBuf.append(aToken); + sListBuf.append('"'); + bList = true; + } + else + sListBuf.append(aToken); + mxString1->Append( aToken ); + sFormulaBuf.append( aToken ); + if (nStringIx<0) + break; + sal_Unicode cUnicodeChar = 0; + mxString1->Append( std::u16string_view(&cUnicodeChar, 1) ); + sFormulaBuf.append( ',' ); + sListBuf.append( ',' ); + } + } + ::set_flag( mnFlags, EXC_DV_STRINGLIST ); + + // maximum length allowed in Excel is 255 characters, and don't end with an empty token + // It should be 8192 but Excel doesn't accept it for unknown reason + // See also https://bugs.documentfoundation.org/show_bug.cgi?id=137167#c2 for more details + sal_uInt32 nLen = sFormulaBuf.getLength(); + if( nLen > 256 ) // 255 + beginning quote mark + { + nLen = 256; + if( sFormulaBuf[nLen - 1] == ',' ) + --nLen; + sFormulaBuf.truncate(nLen); + } + + sFormulaBuf.append( '"' ); + msFormula1 = sFormulaBuf.makeStringAndClear(); + if (bList) + msList = sListBuf.makeStringAndClear(); + else + sListBuf.remove(0, sListBuf.getLength()); + } + else + { + /* All other formulas in validation are stored like conditional + formatting formulas (with tRefN/tAreaN tokens as value or + array class). But NOT the cell references and defined names + in list validation - they are stored as reference class + tokens... Example: + 1) Cell must be equal to A1 -> formula is =A1 -> writes tRefNV token + 2) List is taken from A1 -> formula is =A1 -> writes tRefNR token + Formula compiler supports this by offering two different functions + CreateDataValFormula() and CreateListValFormula(). */ + if(GetOutput() == EXC_OUTPUT_BINARY) + mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_LISTVAL, *xScTokArr ); + else + msFormula1 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(), + xScTokArr.get()); + } + } + else + { + // no list validation -> convert the formula + if(GetOutput() == EXC_OUTPUT_BINARY) + mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_DATAVAL, *xScTokArr ); + else + msFormula1 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(), + xScTokArr.get()); + } + } + + // second formula + xScTokArr = pValData->CreateFlatCopiedTokenArray( 1 ); + if (xScTokArr) + { + if(GetOutput() == EXC_OUTPUT_BINARY) + mxTokArr2 = rFmlaComp.CreateFormula( EXC_FMLATYPE_DATAVAL, *xScTokArr ); + else + msFormula2 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(), + xScTokArr.get()); + } + } + else + { + OSL_FAIL( "XclExpDV::XclExpDV - missing core data" ); + mnScHandle = ULONG_MAX; + } +} + +XclExpDV::~XclExpDV() +{ +} + +void XclExpDV::InsertCellRange( const ScRange& rRange ) +{ + maScRanges.Join( rRange ); +} + +bool XclExpDV::Finalize() +{ + GetAddressConverter().ConvertRangeList( maXclRanges, maScRanges, true ); + return (mnScHandle != ULONG_MAX) && !maXclRanges.empty(); +} + +void XclExpDV::WriteBody( XclExpStream& rStrm ) +{ + // flags and strings + rStrm << mnFlags << maPromptTitle << maErrorTitle << maPromptText << maErrorText; + // condition formulas + if( mxString1 ) + lclWriteDvFormula( rStrm, *mxString1 ); + else + lclWriteDvFormula( rStrm, mxTokArr1.get() ); + lclWriteDvFormula( rStrm, mxTokArr2.get() ); + // cell ranges + rStrm << maXclRanges; +} + +void XclExpDV::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_dataValidation, + XML_allowBlank, ToPsz( ::get_flag( mnFlags, EXC_DV_IGNOREBLANK ) ), + XML_error, XESTRING_TO_PSZ( maErrorText ), + XML_errorStyle, lcl_GetErrorType(mnFlags), + XML_errorTitle, XESTRING_TO_PSZ( maErrorTitle ), + // OOXTODO: XML_imeMode, + XML_operator, lcl_GetOperatorType( mnFlags ), + XML_prompt, XESTRING_TO_PSZ( maPromptText ), + XML_promptTitle, XESTRING_TO_PSZ( maPromptTitle ), + // showDropDown should have been showNoDropDown - check oox/xlsx import for details + XML_showDropDown, ToPsz( ::get_flag( mnFlags, EXC_DV_SUPPRESSDROPDOWN ) ), + XML_showErrorMessage, ToPsz( ::get_flag( mnFlags, EXC_DV_SHOWERROR ) ), + XML_showInputMessage, ToPsz( ::get_flag( mnFlags, EXC_DV_SHOWPROMPT ) ), + XML_sqref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maScRanges), + XML_type, lcl_GetValidationType(mnFlags) ); + if (!msList.isEmpty()) + { + rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), + FSNS(XML_xmlns, XML_x12ac),rStrm.getNamespaceURL(OOX_NS(x12ac)), + FSNS(XML_xmlns, XML_mc),rStrm.getNamespaceURL(OOX_NS(mce))); + rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x12ac"); + rWorksheet->startElement(FSNS(XML_x12ac, XML_list)); + rWorksheet->writeEscaped(msList); + rWorksheet->endElement(FSNS(XML_x12ac, XML_list)); + rWorksheet->endElement(FSNS(XML_mc, XML_Choice)); + rWorksheet->startElement(FSNS(XML_mc, XML_Fallback)); + rWorksheet->startElement(XML_formula1); + rWorksheet->writeEscaped(msFormula1); + rWorksheet->endElement(XML_formula1); + rWorksheet->endElement(FSNS(XML_mc, XML_Fallback)); + rWorksheet->endElement(FSNS(XML_mc, XML_AlternateContent)); + } + if (msList.isEmpty() && !msFormula1.isEmpty()) + { + rWorksheet->startElement(XML_formula1); + rWorksheet->writeEscaped( msFormula1 ); + rWorksheet->endElement( XML_formula1 ); + } + if( !msFormula2.isEmpty() ) + { + rWorksheet->startElement(XML_formula2); + rWorksheet->writeEscaped( msFormula2 ); + rWorksheet->endElement( XML_formula2 ); + } + rWorksheet->endElement( XML_dataValidation ); +} + +XclExpDval::XclExpDval( const XclExpRoot& rRoot ) : + XclExpRecord( EXC_ID_DVAL, 18 ), + XclExpRoot( rRoot ) +{ +} + +XclExpDval::~XclExpDval() +{ +} + +void XclExpDval::InsertCellRange( const ScRange& rRange, sal_uLong nScHandle ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + XclExpDV& rDVRec = SearchOrCreateDv( nScHandle ); + rDVRec.InsertCellRange( rRange ); + } +} + +void XclExpDval::Save( XclExpStream& rStrm ) +{ + // check all records + size_t nPos = maDVList.GetSize(); + while( nPos ) + { + --nPos; // backwards to keep nPos valid + XclExpDVRef xDVRec = maDVList.GetRecord( nPos ); + if( !xDVRec->Finalize() ) + maDVList.RemoveRecord( nPos ); + } + + // write the DVAL and the DV's + if( !maDVList.IsEmpty() ) + { + XclExpRecord::Save( rStrm ); + maDVList.Save( rStrm ); + } +} + +void XclExpDval::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maDVList.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_dataValidations, + XML_count, OString::number(maDVList.GetSize()) + // OOXTODO: XML_disablePrompts, + // OOXTODO: XML_xWindow, + // OOXTODO: XML_yWindow + ); + maDVList.SaveXml( rStrm ); + rWorksheet->endElement( XML_dataValidations ); +} + +XclExpDV& XclExpDval::SearchOrCreateDv( sal_uLong nScHandle ) +{ + // test last found record + if( mxLastFoundDV && (mxLastFoundDV->GetScHandle() == nScHandle) ) + return *mxLastFoundDV; + + // binary search + size_t nCurrPos = 0; + if( !maDVList.IsEmpty() ) + { + size_t nFirstPos = 0; + size_t nLastPos = maDVList.GetSize() - 1; + bool bLoop = true; + sal_uLong nCurrScHandle = ::std::numeric_limits< sal_uLong >::max(); + while( (nFirstPos <= nLastPos) && bLoop ) + { + nCurrPos = (nFirstPos + nLastPos) / 2; + mxLastFoundDV = maDVList.GetRecord( nCurrPos ); + nCurrScHandle = mxLastFoundDV->GetScHandle(); + if( nCurrScHandle == nScHandle ) + bLoop = false; + else if( nCurrScHandle < nScHandle ) + nFirstPos = nCurrPos + 1; + else if( nCurrPos ) + nLastPos = nCurrPos - 1; + else // special case for nLastPos = -1 + bLoop = false; + } + if( nCurrScHandle == nScHandle ) + return *mxLastFoundDV; + else if( nCurrScHandle < nScHandle ) + ++nCurrPos; + } + + // create new DV record + mxLastFoundDV = new XclExpDV( *this, nScHandle ); + maDVList.InsertRecord( mxLastFoundDV, nCurrPos ); + return *mxLastFoundDV; +} + +void XclExpDval::WriteBody( XclExpStream& rStrm ) +{ + rStrm.WriteZeroBytes( 10 ); + rStrm << EXC_DVAL_NOOBJ << static_cast< sal_uInt32 >( maDVList.GetSize() ); +} + +// Web Queries ================================================================ + +XclExpWebQuery::XclExpWebQuery( + const OUString& rRangeName, + const OUString& rUrl, + std::u16string_view rSource, + sal_Int32 nRefrSecs ) : + maDestRange( rRangeName ), + maUrl( rUrl ), + // refresh delay time: seconds -> minutes + mnRefresh( ulimit_cast< sal_Int16 >( (nRefrSecs + 59) / 60 ) ), + mbEntireDoc( false ) +{ + // comma separated list of HTML table names or indexes + OUString aNewTables; + OUString aAppendTable; + bool bExitLoop = false; + if (!rSource.empty()) + { + sal_Int32 nStringIx = 0; + do + { + OUString aToken( o3tl::getToken(rSource, 0, ';', nStringIx ) ); + mbEntireDoc = ScfTools::IsHTMLDocName( aToken ); + bExitLoop = mbEntireDoc || ScfTools::IsHTMLTablesName( aToken ); + if( !bExitLoop && ScfTools::GetHTMLNameFromName( aToken, aAppendTable ) ) + aNewTables = ScGlobal::addToken( aNewTables, aAppendTable, ',' ); + } + while (nStringIx>0 && !bExitLoop); + } + + if( !bExitLoop ) // neither HTML_all nor HTML_tables found + { + if( !aNewTables.isEmpty() ) + mxQryTables.reset( new XclExpString( aNewTables ) ); + else + mbEntireDoc = true; + } +} + +XclExpWebQuery::~XclExpWebQuery() +{ +} + +void XclExpWebQuery::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( !mbEntireDoc || !mxQryTables, "XclExpWebQuery::Save - illegal mode" ); + sal_uInt16 nFlags; + + // QSI record + rStrm.StartRecord( EXC_ID_QSI, 10 + maDestRange.GetSize() ); + rStrm << EXC_QSI_DEFAULTFLAGS + << sal_uInt16( 0x0010 ) + << sal_uInt16( 0x0012 ) + << sal_uInt32( 0x00000000 ) + << maDestRange; + rStrm.EndRecord(); + + // PARAMQRY record + nFlags = 0; + ::insert_value( nFlags, EXC_PQRYTYPE_WEBQUERY, 0, 3 ); + ::set_flag( nFlags, EXC_PQRY_WEBQUERY ); + ::set_flag( nFlags, EXC_PQRY_TABLES, !mbEntireDoc ); + rStrm.StartRecord( EXC_ID_PQRY, 12 ); + rStrm << nFlags + << sal_uInt16( 0x0000 ) + << sal_uInt16( 0x0001 ); + rStrm.WriteZeroBytes( 6 ); + rStrm.EndRecord(); + + // WQSTRING record + rStrm.StartRecord( EXC_ID_WQSTRING, maUrl.GetSize() ); + rStrm << maUrl; + rStrm.EndRecord(); + + // unknown record 0x0802 + rStrm.StartRecord( EXC_ID_0802, 16 + maDestRange.GetSize() ); + rStrm << EXC_ID_0802; // repeated record id ?!? + rStrm.WriteZeroBytes( 6 ); + rStrm << sal_uInt16( 0x0003 ) + << sal_uInt32( 0x00000000 ) + << sal_uInt16( 0x0010 ) + << maDestRange; + rStrm.EndRecord(); + + // WEBQRYSETTINGS record + nFlags = mxQryTables ? EXC_WQSETT_SPECTABLES : EXC_WQSETT_ALL; + rStrm.StartRecord( EXC_ID_WQSETT, 28 ); + rStrm << EXC_ID_WQSETT // repeated record id ?!? + << sal_uInt16( 0x0000 ) + << sal_uInt16( 0x0004 ) + << sal_uInt16( 0x0000 ) + << EXC_WQSETT_DEFAULTFLAGS + << nFlags; + rStrm.WriteZeroBytes( 10 ); + rStrm << mnRefresh // refresh delay in minutes + << EXC_WQSETT_FORMATFULL + << sal_uInt16( 0x0000 ); + rStrm.EndRecord(); + + // WEBQRYTABLES record + if( mxQryTables ) + { + rStrm.StartRecord( EXC_ID_WQTABLES, 4 + mxQryTables->GetSize() ); + rStrm << EXC_ID_WQTABLES // repeated record id ?!? + << sal_uInt16( 0x0000 ) + << *mxQryTables; // comma separated list of source tables + rStrm.EndRecord(); + } +} + +XclExpWebQueryBuffer::XclExpWebQueryBuffer( const XclExpRoot& rRoot ) +{ + SCTAB nScTab = rRoot.GetCurrScTab(); + SfxObjectShell* pShell = rRoot.GetDocShell(); + if( !pShell ) return; + ScfPropertySet aModelProp( pShell->GetModel() ); + if( !aModelProp.Is() ) return; + + Reference< XAreaLinks > xAreaLinks; + aModelProp.GetProperty( xAreaLinks, SC_UNO_AREALINKS ); + if( !xAreaLinks.is() ) return; + + for( sal_Int32 nIndex = 0, nCount = xAreaLinks->getCount(); nIndex < nCount; ++nIndex ) + { + Reference< XAreaLink > xAreaLink( xAreaLinks->getByIndex( nIndex ), UNO_QUERY ); + if( xAreaLink.is() ) + { + CellRangeAddress aDestRange( xAreaLink->getDestArea() ); + if( static_cast< SCTAB >( aDestRange.Sheet ) == nScTab ) + { + ScfPropertySet aLinkProp( xAreaLink ); + OUString aFilter; + if( aLinkProp.GetProperty( aFilter, SC_UNONAME_FILTER ) && + (aFilter == EXC_WEBQRY_FILTER) ) + { + // get properties + OUString /*aFilterOpt,*/ aUrl; + sal_Int32 nRefresh = 0; + +// aLinkProp.GetProperty( aFilterOpt, SC_UNONAME_FILTOPT ); + aLinkProp.GetProperty( aUrl, SC_UNONAME_LINKURL ); + aLinkProp.GetProperty( nRefresh, SC_UNONAME_REFDELAY ); + + OUString aAbsDoc( ScGlobal::GetAbsDocName( aUrl, pShell ) ); + INetURLObject aUrlObj( aAbsDoc ); + OUString aWebQueryUrl( aUrlObj.getFSysPath( FSysStyle::Dos ) ); + if( aWebQueryUrl.isEmpty() ) + aWebQueryUrl = aAbsDoc; + + // find range or create a new range + OUString aRangeName; + ScRange aScDestRange; + ScUnoConversion::FillScRange( aScDestRange, aDestRange ); + if( const ScRangeData* pRangeData = rRoot.GetNamedRanges().findByRange( aScDestRange ) ) + { + aRangeName = pRangeData->GetName(); + } + else + { + XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler(); + XclExpNameManager& rNameMgr = rRoot.GetNameManager(); + + // create a new unique defined name containing the range + XclTokenArrayRef xTokArr = rFmlaComp.CreateFormula( EXC_FMLATYPE_WQUERY, aScDestRange ); + sal_uInt16 nNameIdx = rNameMgr.InsertUniqueName( aUrlObj.getBase(), xTokArr, nScTab ); + aRangeName = rNameMgr.GetOrigName( nNameIdx ); + } + + // create and store the web query record + if( !aRangeName.isEmpty() ) + AppendNewRecord( new XclExpWebQuery( + aRangeName, aWebQueryUrl, xAreaLink->getSourceArea(), nRefresh ) ); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xedbdata.cxx b/sc/source/filter/excel/xedbdata.cxx new file mode 100644 index 000000000..350f6f70e --- /dev/null +++ b/sc/source/filter/excel/xedbdata.cxx @@ -0,0 +1,261 @@ +/* -*- 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/. + */ + +#include <xedbdata.hxx> +#include <excrecds.hxx> +#include <dbdata.hxx> +#include <document.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> + +using namespace oox; + +namespace { + +/** (So far) dummy implementation of table export for BIFF5/BIFF7. */ +class XclExpTablesImpl5 : public XclExpTables +{ +public: + explicit XclExpTablesImpl5( const XclExpRoot& rRoot ); + + virtual void Save( XclExpStream& rStrm ) override; + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +/** Implementation of table export for OOXML, so far dummy for BIFF8. */ +class XclExpTablesImpl8 : public XclExpTables +{ +public: + explicit XclExpTablesImpl8( const XclExpRoot& rRoot ); + + virtual void Save( XclExpStream& rStrm ) override; + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +} + +XclExpTablesImpl5::XclExpTablesImpl5( const XclExpRoot& rRoot ) : + XclExpTables( rRoot ) +{ +} + +void XclExpTablesImpl5::Save( XclExpStream& /*rStrm*/ ) +{ + // not implemented +} + +void XclExpTablesImpl5::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ + // not applicable +} + + +XclExpTablesImpl8::XclExpTablesImpl8( const XclExpRoot& rRoot ) : + XclExpTables( rRoot ) +{ +} + +void XclExpTablesImpl8::Save( XclExpStream& /*rStrm*/ ) +{ + // not implemented +} + +void XclExpTablesImpl8::SaveXml( XclExpXmlStream& rStrm ) +{ + + sax_fastparser::FSHelperPtr& pWorksheetStrm = rStrm.GetCurrentStream(); + pWorksheetStrm->startElement(XML_tableParts); + for (auto const& it : maTables) + { + OUString aRelId; + sax_fastparser::FSHelperPtr pTableStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/tables/", "table", it.mnTableId), + XclXmlUtils::GetStreamName("../tables/", "table", it.mnTableId), + pWorksheetStrm->getOutputStream(), + CREATE_XL_CONTENT_TYPE("table"), + CREATE_OFFICEDOC_RELATION_TYPE("table"), + &aRelId); + + pWorksheetStrm->singleElement(XML_tablePart, FSNS(XML_r, XML_id), aRelId.toUtf8()); + + rStrm.PushStream( pTableStrm); + SaveTableXml( rStrm, it); + rStrm.PopStream(); + } + pWorksheetStrm->endElement( XML_tableParts); +} + + +XclExpTablesManager::XclExpTablesManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpTablesManager::~XclExpTablesManager() +{ +} + +void XclExpTablesManager::Initialize() +{ + // All non-const to be able to call RefreshTableColumnNames(). + ScDocument& rDoc = GetDoc(); + ScDBCollection* pDBColl = rDoc.GetDBCollection(); + if (!pDBColl) + return; + + ScDBCollection::NamedDBs& rDBs = pDBColl->getNamedDBs(); + if (rDBs.empty()) + return; + + sal_Int32 nTableId = 0; + for (const auto& rxDB : rDBs) + { + ScDBData* pDBData = rxDB.get(); + pDBData->RefreshTableColumnNames( &rDoc); // currently not in sync, so refresh + ScRange aRange( ScAddress::UNINITIALIZED); + pDBData->GetArea( aRange); + SCTAB nTab = aRange.aStart.Tab(); + TablesMapType::iterator it = maTablesMap.find( nTab); + if (it == maTablesMap.end()) + { + rtl::Reference< XclExpTables > pNew; + switch( GetBiff() ) + { + case EXC_BIFF5: + pNew = new XclExpTablesImpl5( GetRoot()); + break; + case EXC_BIFF8: + pNew = new XclExpTablesImpl8( GetRoot()); + break; + default: + assert(!"Unknown BIFF type!"); + continue; // for + } + ::std::pair< TablesMapType::iterator, bool > ins( maTablesMap.insert( ::std::make_pair( nTab, pNew))); + if (!ins.second) + { + assert(!"XclExpTablesManager::Initialize - XclExpTables insert failed"); + continue; // for + } + it = ins.first; + } + it->second->AppendTable( pDBData, ++nTableId); + } +} + +rtl::Reference< XclExpTables > XclExpTablesManager::GetTablesBySheet( SCTAB nTab ) +{ + TablesMapType::iterator it = maTablesMap.find(nTab); + return it == maTablesMap.end() ? nullptr : it->second; +} + +XclExpTables::Entry::Entry( const ScDBData* pData, sal_Int32 nTableId ) : + mpData(pData), mnTableId(nTableId) +{ +} + +XclExpTables::XclExpTables( const XclExpRoot& rRoot ) : + XclExpRoot(rRoot) +{ +} + +XclExpTables::~XclExpTables() +{ +} + +void XclExpTables::AppendTable( const ScDBData* pData, sal_Int32 nTableId ) +{ + maTables.emplace_back( pData, nTableId); +} + +void XclExpTables::SaveTableXml( XclExpXmlStream& rStrm, const Entry& rEntry ) +{ + const ScDBData& rData = *rEntry.mpData; + ScRange aRange( ScAddress::UNINITIALIZED); + rData.GetArea( aRange); + sax_fastparser::FSHelperPtr& pTableStrm = rStrm.GetCurrentStream(); + pTableStrm->startElement( XML_table, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + XML_id, OString::number( rEntry.mnTableId), + XML_name, rData.GetName().toUtf8(), + XML_displayName, rData.GetName().toUtf8(), + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aRange), + XML_headerRowCount, ToPsz10(rData.HasHeader()), + XML_totalsRowCount, ToPsz10(rData.HasTotals()), + XML_totalsRowShown, ToPsz10(rData.HasTotals()) // we don't support that but if there are totals they are shown + // OOXTODO: XML_comment, ..., + // OOXTODO: XML_connectionId, ..., + // OOXTODO: XML_dataCellStyle, ..., + // OOXTODO: XML_dataDxfId, ..., + // OOXTODO: XML_headerRowBorderDxfId, ..., + // OOXTODO: XML_headerRowCellStyle, ..., + // OOXTODO: XML_headerRowDxfId, ..., + // OOXTODO: XML_insertRow, ..., + // OOXTODO: XML_insertRowShift, ..., + // OOXTODO: XML_published, ..., + // OOXTODO: XML_tableBorderDxfId, ..., + // OOXTODO: XML_tableType, ..., + // OOXTODO: XML_totalsRowBorderDxfId, ..., + // OOXTODO: XML_totalsRowCellStyle, ..., + // OOXTODO: XML_totalsRowDxfId, ... + ); + + if (rData.HasAutoFilter()) + { + /* TODO: does this need to exclude totals row? */ + + /* TODO: in OOXML 12.3.21 Table Definition Part has information + * that an applied autoFilter has child elements + * <af:filterColumn><af:filters><af:filter>. + * When not applied but buttons hidden, Excel writes, for example, + * <filterColumn colId="0" hiddenButton="1"/> */ + + ExcAutoFilterRecs aAutoFilter( rStrm.GetRoot(), aRange.aStart.Tab(), &rData); + aAutoFilter.SaveXml( rStrm); + } + + const std::vector< OUString >& rColNames = rData.GetTableColumnNames(); + if (!rColNames.empty()) + { + pTableStrm->startElement(XML_tableColumns, + XML_count, OString::number(aRange.aEnd.Col() - aRange.aStart.Col() + 1)); + + for (size_t i=0, n=rColNames.size(); i < n; ++i) + { + // OOXTODO: write <calculatedColumnFormula> once we support it, in + // which case we'd need start/endElement XML_tableColumn for such + // column. + + // OOXTODO: write <totalsRowFormula> once we support it. + + pTableStrm->singleElement( XML_tableColumn, + XML_id, OString::number(i+1), + XML_name, rColNames[i].toUtf8() + // OOXTODO: XML_dataCellStyle, ..., + // OOXTODO: XML_dataDxfId, ..., + // OOXTODO: XML_headerRowCellStyle, ..., + // OOXTODO: XML_headerRowDxfId, ..., + // OOXTODO: XML_queryTableFieldId, ..., + // OOXTODO: XML_totalsRowCellStyle, ..., + // OOXTODO: XML_totalsRowDxfId, ..., + // OOXTODO: XML_totalsRowFunction, ..., + // OOXTODO: XML_totalsRowLabel, ..., + // OOXTODO: XML_uniqueName, ... + ); + } + + pTableStrm->endElement( XML_tableColumns); + } + + // OOXTODO: write <tableStyleInfo> once we have table styles. + + pTableStrm->endElement( XML_table); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeescher.cxx b/sc/source/filter/excel/xeescher.cxx new file mode 100644 index 000000000..d166b172a --- /dev/null +++ b/sc/source/filter/excel/xeescher.cxx @@ -0,0 +1,2046 @@ +/* -*- 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 <xeescher.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/awt/VisualEffect.hpp> +#include <com/sun/star/awt/ScrollBarOrientation.hpp> +#include <com/sun/star/awt/XControlModel.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/chart/XChartDocument.hpp> + +#include <set> +#include <vcl/BitmapReadAccess.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdocapt.hxx> +#include <editeng/outlobj.hxx> +#include <unotools/tempfile.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <svtools/embedhlp.hxx> + +#include <unonames.hxx> +#include <convuno.hxx> +#include <postit.hxx> + +#include <fapihelper.hxx> +#include <xcl97esc.hxx> +#include <xechart.hxx> +#include <xeformula.hxx> +#include <xehelper.hxx> +#include <xelink.hxx> +#include <xename.hxx> +#include <xestyle.hxx> +#include <xllink.hxx> +#include <xltools.hxx> +#include <userdat.hxx> +#include <drwlayer.hxx> +#include <svl/itemset.hxx> +#include <svx/sdtaitm.hxx> +#include <document.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> + +#include <comphelper/sequence.hxx> +#include <oox/token/tokens.hxx> +#include <oox/token/relationship.hxx> +#include <oox/export/drawingml.hxx> +#include <oox/export/chartexport.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/export/vmlexport.hxx> +#include <memory> + +using namespace com::sun::star; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::lang::XServiceInfo; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XShapes; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::form::binding::XBindableValue; +using ::com::sun::star::form::binding::XListEntrySink; +using ::com::sun::star::script::ScriptEventDescriptor; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::oox::drawingml::DrawingML; +using ::oox::drawingml::ChartExport; +using namespace oox; + +namespace +{ + +const char *ToHorizAlign( SdrTextHorzAdjust eAdjust ) +{ + switch( eAdjust ) + { + case SDRTEXTHORZADJUST_CENTER: + return "center"; + case SDRTEXTHORZADJUST_RIGHT: + return "right"; + case SDRTEXTHORZADJUST_BLOCK: + return "justify"; + case SDRTEXTHORZADJUST_LEFT: + default: + return "left"; + } +} + +const char *ToVertAlign( SdrTextVertAdjust eAdjust ) +{ + switch( eAdjust ) + { + case SDRTEXTVERTADJUST_CENTER: + return "center"; + case SDRTEXTVERTADJUST_BOTTOM: + return "bottom"; + case SDRTEXTVERTADJUST_BLOCK: + return "justify"; + case SDRTEXTVERTADJUST_TOP: + default: + return "top"; + } +} + +void lcl_WriteAnchorVertex( sax_fastparser::FSHelperPtr const & rComments, const tools::Rectangle &aRect ) +{ + rComments->startElement(FSNS(XML_xdr, XML_col)); + rComments->writeEscaped( OUString::number( aRect.Left() ) ); + rComments->endElement( FSNS( XML_xdr, XML_col ) ); + rComments->startElement(FSNS(XML_xdr, XML_colOff)); + rComments->writeEscaped( OUString::number( aRect.Top() ) ); + rComments->endElement( FSNS( XML_xdr, XML_colOff ) ); + rComments->startElement(FSNS(XML_xdr, XML_row)); + rComments->writeEscaped( OUString::number( aRect.Right() ) ); + rComments->endElement( FSNS( XML_xdr, XML_row ) ); + rComments->startElement(FSNS(XML_xdr, XML_rowOff)); + rComments->writeEscaped( OUString::number( aRect.Bottom() ) ); + rComments->endElement( FSNS( XML_xdr, XML_rowOff ) ); +} + +tools::Long lcl_hmm2output(tools::Long value, bool bInEMU) +{ + return o3tl::convert(value, o3tl::Length::mm100, bInEMU ? o3tl::Length::emu : o3tl::Length::px); +} + +void lcl_GetFromTo( const XclExpRoot& rRoot, const tools::Rectangle &aRect, sal_Int32 nTab, tools::Rectangle &aFrom, tools::Rectangle &aTo, bool bInEMU = false ) +{ + sal_Int32 nCol = 0, nRow = 0; + sal_Int32 nColOff = 0, nRowOff= 0; + + const bool bRTL = rRoot.GetDoc().IsNegativePage( nTab ); + if (!bRTL) + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Left() <= aRect.Left() ) + { + nCol++; + nColOff = aRect.Left() - r.Left(); + } + if( r.Top() <= aRect.Top() ) + { + nRow++; + nRowOff = aRect.Top() - r.Top(); + } + if( r.Left() > aRect.Left() && r.Top() > aRect.Top() ) + { + aFrom = tools::Rectangle( nCol-1, lcl_hmm2output( nColOff, bInEMU ), + nRow-1, lcl_hmm2output( nRowOff, bInEMU ) ); + break; + } + } + } + else + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Left() >= aRect.Left() ) + { + nCol++; + nColOff = r.Left() - aRect.Left(); + } + if( r.Top() <= aRect.Top() ) + { + nRow++; + nRowOff = aRect.Top() - r.Top(); + } + if( r.Left() < aRect.Left() && r.Top() > aRect.Top() ) + { + aFrom = tools::Rectangle( nCol-1, lcl_hmm2output( nColOff, bInEMU ), + nRow-1, lcl_hmm2output( nRowOff, bInEMU ) ); + break; + } + } + } + if (!bRTL) + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Right() < aRect.Right() ) + nCol++; + if( r.Bottom() < aRect.Bottom() ) + nRow++; + if( r.Right() >= aRect.Right() && r.Bottom() >= aRect.Bottom() ) + { + aTo = tools::Rectangle( nCol, lcl_hmm2output( aRect.Right() - r.Left(), bInEMU ), + nRow, lcl_hmm2output( aRect.Bottom() - r.Top(), bInEMU )); + break; + } + } + } + else + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Right() >= aRect.Right() ) + nCol++; + if( r.Bottom() < aRect.Bottom() ) + nRow++; + if( r.Right() < aRect.Right() && r.Bottom() >= aRect.Bottom() ) + { + aTo = tools::Rectangle( nCol, lcl_hmm2output( r.Left() - aRect.Right(), bInEMU ), + nRow, lcl_hmm2output( aRect.Bottom() - r.Top(), bInEMU )); + break; + } + } + } +} + +} // namespace + +// Escher client anchor ======================================================= + +XclExpDffAnchorBase::XclExpDffAnchorBase( const XclExpRoot& rRoot, sal_uInt16 nFlags ) : + XclExpRoot( rRoot ), + mnFlags( nFlags ) +{ +} + +void XclExpDffAnchorBase::SetFlags( const SdrObject& rSdrObj ) +{ + ImplSetFlags( rSdrObj ); +} + +void XclExpDffAnchorBase::SetSdrObject( const SdrObject& rSdrObj ) +{ + ImplSetFlags( rSdrObj ); + ImplCalcAnchorRect( rSdrObj.GetCurrentBoundRect(), MapUnit::Map100thMM ); +} + +void XclExpDffAnchorBase::WriteDffData( EscherEx& rEscherEx ) const +{ + rEscherEx.AddAtom( 18, ESCHER_ClientAnchor ); + rEscherEx.GetStream().WriteUInt16( mnFlags ); + WriteXclObjAnchor( rEscherEx.GetStream(), maAnchor ); +} + +void XclExpDffAnchorBase::WriteData( EscherEx& rEscherEx, const tools::Rectangle& rRect ) +{ + // the passed rectangle is in twips + ImplCalcAnchorRect( rRect, MapUnit::MapTwip ); + WriteDffData( rEscherEx ); +} + +void XclExpDffAnchorBase::ImplSetFlags( const SdrObject& ) +{ + OSL_FAIL( "XclExpDffAnchorBase::ImplSetFlags - not implemented" ); +} + +void XclExpDffAnchorBase::ImplCalcAnchorRect( const tools::Rectangle&, MapUnit ) +{ + OSL_FAIL( "XclExpDffAnchorBase::ImplCalcAnchorRect - not implemented" ); +} + +XclExpDffSheetAnchor::XclExpDffSheetAnchor( const XclExpRoot& rRoot ) : + XclExpDffAnchorBase( rRoot ), + mnScTab( rRoot.GetCurrScTab() ) +{ +} + +void XclExpDffSheetAnchor::ImplSetFlags( const SdrObject& rSdrObj ) +{ + // set flags for cell/page anchoring + if ( ScDrawLayer::GetAnchorType( rSdrObj ) == SCA_CELL ) + mnFlags = 0; + else + mnFlags = EXC_ESC_ANCHOR_LOCKED; +} + +void XclExpDffSheetAnchor::ImplCalcAnchorRect( const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + maAnchor.SetRect( GetRoot(), mnScTab, rRect, eMapUnit ); +} + +XclExpDffEmbeddedAnchor::XclExpDffEmbeddedAnchor( const XclExpRoot& rRoot, + const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY ) : + XclExpDffAnchorBase( rRoot ), + maPageSize( rPageSize ), + mnScaleX( nScaleX ), + mnScaleY( nScaleY ) +{ +} + +void XclExpDffEmbeddedAnchor::ImplSetFlags( const SdrObject& /*rSdrObj*/ ) +{ + // TODO (unsupported feature): fixed size +} + +void XclExpDffEmbeddedAnchor::ImplCalcAnchorRect( const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + maAnchor.SetRect( maPageSize, mnScaleX, mnScaleY, rRect, eMapUnit ); +} + +XclExpDffNoteAnchor::XclExpDffNoteAnchor( const XclExpRoot& rRoot, const tools::Rectangle& rRect ) : + XclExpDffAnchorBase( rRoot, EXC_ESC_ANCHOR_SIZELOCKED ) +{ + maAnchor.SetRect( rRoot, rRoot.GetCurrScTab(), rRect, MapUnit::Map100thMM ); +} + +XclExpDffDropDownAnchor::XclExpDffDropDownAnchor( const XclExpRoot& rRoot, const ScAddress& rScPos ) : + XclExpDffAnchorBase( rRoot, EXC_ESC_ANCHOR_POSLOCKED ) +{ + GetAddressConverter().ConvertAddress( maAnchor.maFirst, rScPos, true ); + maAnchor.maLast.mnCol = maAnchor.maFirst.mnCol + 1; + maAnchor.maLast.mnRow = maAnchor.maFirst.mnRow + 1; + maAnchor.mnLX = maAnchor.mnTY = maAnchor.mnRX = maAnchor.mnBY = 0; +} + +// MSODRAWING* records ======================================================== + +XclExpMsoDrawingBase::XclExpMsoDrawingBase( XclEscherEx& rEscherEx, sal_uInt16 nRecId ) : + XclExpRecord( nRecId ), + mrEscherEx( rEscherEx ), + mnFragmentKey( rEscherEx.InitNextDffFragment() ) +{ +} + +void XclExpMsoDrawingBase::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE( mrEscherEx.GetStreamPos() == mrEscherEx.GetDffFragmentPos( mnFragmentKey ), + "XclExpMsoDrawingBase::WriteBody - DFF stream position mismatch" ); + rStrm.CopyFromStream( mrEscherEx.GetStream(), mrEscherEx.GetDffFragmentSize( mnFragmentKey ) ); +} + +XclExpMsoDrawingGroup::XclExpMsoDrawingGroup( XclEscherEx& rEscherEx ) : + XclExpMsoDrawingBase( rEscherEx, EXC_ID_MSODRAWINGGROUP ) +{ + SvStream& rDffStrm = mrEscherEx.GetStream(); + + // write the DGGCONTAINER with some default settings + mrEscherEx.OpenContainer( ESCHER_DggContainer ); + + // TODO: stuff the OPT atom with our own document defaults? + static const sal_uInt8 spnDffOpt[] = { + 0xBF, 0x00, 0x08, 0x00, 0x08, 0x00, 0x81, 0x01, + 0x09, 0x00, 0x00, 0x08, 0xC0, 0x01, 0x40, 0x00, + 0x00, 0x08 + }; + mrEscherEx.AddAtom( sizeof( spnDffOpt ), ESCHER_OPT, 3, 3 ); + rDffStrm.WriteBytes(spnDffOpt, sizeof(spnDffOpt)); + + // SPLITMENUCOLORS contains colors in toolbar + static const sal_uInt8 spnDffSplitMenuColors[] = { + 0x0D, 0x00, 0x00, 0x08, 0x0C, 0x00, 0x00, 0x08, + 0x17, 0x00, 0x00, 0x08, 0xF7, 0x00, 0x00, 0x10 + }; + mrEscherEx.AddAtom( sizeof( spnDffSplitMenuColors ), ESCHER_SplitMenuColors, 0, 4 ); + rDffStrm.WriteBytes(spnDffSplitMenuColors, sizeof(spnDffSplitMenuColors)); + + // close the DGGCONTAINER + mrEscherEx.CloseContainer(); + mrEscherEx.UpdateDffFragmentEnd(); +} + +XclExpMsoDrawing::XclExpMsoDrawing( XclEscherEx& rEscherEx ) : + XclExpMsoDrawingBase( rEscherEx, EXC_ID_MSODRAWING ) +{ +} + +XclExpImgData::XclExpImgData( const Graphic& rGraphic, sal_uInt16 nRecId ) : + maGraphic( rGraphic ), + mnRecId( nRecId ) +{ +} + +void XclExpImgData::Save( XclExpStream& rStrm ) +{ + Bitmap aBmp = maGraphic.GetBitmapEx().GetBitmap(); + if (aBmp.getPixelFormat() != vcl::PixelFormat::N24_BPP) + aBmp.Convert( BmpConversion::N24Bit ); + + Bitmap::ScopedReadAccess pAccess(aBmp); + if( !pAccess ) + return; + + sal_Int32 nWidth = ::std::min< sal_Int32 >( pAccess->Width(), 0xFFFF ); + sal_Int32 nHeight = ::std::min< sal_Int32 >( pAccess->Height(), 0xFFFF ); + if( (nWidth <= 0) || (nHeight <= 0) ) + return; + + sal_uInt8 nPadding = static_cast< sal_uInt8 >( nWidth & 0x03 ); + sal_uInt32 nTmpSize = static_cast< sal_uInt32 >( (nWidth * 3 + nPadding) * nHeight + 12 ); + + rStrm.StartRecord( mnRecId, nTmpSize + 4 ); + + rStrm << EXC_IMGDATA_BMP // BMP format + << EXC_IMGDATA_WIN // Windows + << nTmpSize // size after _this_ field + << sal_uInt32( 12 ) // BITMAPCOREHEADER size + << static_cast< sal_uInt16 >( nWidth ) // width + << static_cast< sal_uInt16 >( nHeight ) // height + << sal_uInt16( 1 ) // planes + << sal_uInt16( 24 ); // bits per pixel + + for( sal_Int32 nY = nHeight - 1; nY >= 0; --nY ) + { + Scanline pScanline = pAccess->GetScanline( nY ); + for( sal_Int32 nX = 0; nX < nWidth; ++nX ) + { + const BitmapColor& rBmpColor = pAccess->GetPixelFromData( pScanline, nX ); + rStrm << rBmpColor.GetBlue() << rBmpColor.GetGreen() << rBmpColor.GetRed(); + } + rStrm.WriteZeroBytes( nPadding ); + } + + rStrm.EndRecord(); +} + +void XclExpImgData::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pWorksheet = rStrm.GetCurrentStream(); + + DrawingML aDML(pWorksheet, &rStrm, drawingml::DOCUMENT_XLSX); + OUString rId = aDML.WriteImage( maGraphic ); + pWorksheet->singleElement(XML_picture, FSNS(XML_r, XML_id), rId); +} + +XclExpControlHelper::XclExpControlHelper( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnEntryCount( 0 ) +{ +} + +XclExpControlHelper::~XclExpControlHelper() +{ +} + +void XclExpControlHelper::ConvertSheetLinks( Reference< XShape > const & xShape ) +{ + mxCellLink.reset(); + mxCellLinkAddress.SetInvalid(); + mxSrcRange.reset(); + mnEntryCount = 0; + + // get control model + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( xShape ); + if( !xCtrlModel.is() ) + return; + + // *** cell link *** ------------------------------------------------------ + + Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY ); + if( xBindable.is() ) + { + Reference< XServiceInfo > xServInfo( xBindable->getValueBinding(), UNO_QUERY ); + if( xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_VALBIND ) ) + { + ScfPropertySet aBindProp( xServInfo ); + CellAddress aApiAddress; + if( aBindProp.GetProperty( aApiAddress, SC_UNONAME_BOUNDCELL ) ) + { + ScUnoConversion::FillScAddress( mxCellLinkAddress, aApiAddress ); + if( GetTabInfo().IsExportTab( mxCellLinkAddress.Tab() ) ) + mxCellLink = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CONTROL, mxCellLinkAddress ); + } + } + } + + // *** source range *** --------------------------------------------------- + + Reference< XListEntrySink > xEntrySink( xCtrlModel, UNO_QUERY ); + if( !xEntrySink.is() ) + return; + + Reference< XServiceInfo > xServInfo( xEntrySink->getListEntrySource(), UNO_QUERY ); + if( !(xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_LISTSOURCE )) ) + return; + + ScfPropertySet aSinkProp( xServInfo ); + CellRangeAddress aApiRange; + if( aSinkProp.GetProperty( aApiRange, SC_UNONAME_CELLRANGE ) ) + { + ScRange aSrcRange; + ScUnoConversion::FillScRange( aSrcRange, aApiRange ); + if( (aSrcRange.aStart.Tab() == aSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( aSrcRange.aStart.Tab() ) ) + mxSrcRange = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CONTROL, aSrcRange ); + mnEntryCount = static_cast< sal_uInt16 >( aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1 ); + } +} + +void XclExpControlHelper::WriteFormula( XclExpStream& rStrm, const XclTokenArray& rTokArr ) +{ + sal_uInt16 nFmlaSize = rTokArr.GetSize(); + rStrm << nFmlaSize << sal_uInt32( 0 ); + rTokArr.WriteArray( rStrm ); + if( nFmlaSize & 1 ) // pad to 16-bit + rStrm << sal_uInt8( 0 ); +} + +void XclExpControlHelper::WriteFormulaSubRec( XclExpStream& rStrm, sal_uInt16 nSubRecId, const XclTokenArray& rTokArr ) +{ + rStrm.StartRecord( nSubRecId, (rTokArr.GetSize() + 5) & ~1 ); + WriteFormula( rStrm, rTokArr ); + rStrm.EndRecord(); +} + +//delete for exporting OCX +//#if EXC_EXP_OCX_CTRL + +XclExpOcxControlObj::XclExpOcxControlObj( XclExpObjectManager& rObjMgr, Reference< XShape > const & xShape, + const tools::Rectangle* pChildAnchor, const OUString& rClassName, sal_uInt32 nStrmStart, sal_uInt32 nStrmSize ) : + XclObj( rObjMgr, EXC_OBJTYPE_PICTURE, true ), + XclExpControlHelper( rObjMgr.GetRoot() ), + maClassName( rClassName ), + mnStrmStart( nStrmStart ), + mnStrmSize( nStrmSize ) +{ + ScfPropertySet aCtrlProp( XclControlHelper::GetControlModel( xShape ) ); + + // OBJ record flags + SetLocked( true ); + SetPrintable( aCtrlProp.GetBoolProperty( "Printable" ) ); + SetAutoFill( false ); + SetAutoLine( false ); + + // fill DFF property set + mrEscherEx.OpenContainer( ESCHER_SpContainer ); + mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, + ShapeFlag::HaveShapeProperty | ShapeFlag::HaveAnchor | ShapeFlag::OLEShape ); + tools::Rectangle aDummyRect; + EscherPropertyContainer aPropOpt( mrEscherEx.GetGraphicProvider(), mrEscherEx.QueryPictureStream(), aDummyRect ); + aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x00080008 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_lineColor, 0x08000040 ); + aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080000 ); // bool field + + // #i51348# name of the control, may overwrite shape name + OUString aCtrlName; + if( aCtrlProp.GetProperty( aCtrlName, "Name" ) && !aCtrlName.isEmpty() ) + aPropOpt.AddOpt( ESCHER_Prop_wzName, aCtrlName ); + + // meta file + //TODO - needs check + Reference< XPropertySet > xShapePS( xShape, UNO_QUERY ); + if( xShapePS.is() && aPropOpt.CreateGraphicProperties( xShapePS, "MetaFile", false ) ) + { + sal_uInt32 nBlipId; + if( aPropOpt.GetOpt( ESCHER_Prop_pib, nBlipId ) ) + aPropOpt.AddOpt( ESCHER_Prop_pictureId, nBlipId ); + } + + // write DFF property set to stream + aPropOpt.Commit( mrEscherEx.GetStream() ); + + // anchor + ImplWriteAnchor( SdrObject::getSdrObjectFromXShape( xShape ), pChildAnchor ); + + mrEscherEx.AddAtom( 0, ESCHER_ClientData ); // OBJ record + mrEscherEx.CloseContainer(); // ESCHER_SpContainer + mrEscherEx.UpdateDffFragmentEnd(); + + // spreadsheet links + ConvertSheetLinks( xShape ); +} + +void XclExpOcxControlObj::WriteSubRecs( XclExpStream& rStrm ) +{ + // OBJCF - clipboard format + rStrm.StartRecord( EXC_ID_OBJCF, 2 ); + rStrm << sal_uInt16( 2 ); + rStrm.EndRecord(); + + // OBJFLAGS + rStrm.StartRecord( EXC_ID_OBJFLAGS, 2 ); + rStrm << sal_uInt16( 0x0031 ); + rStrm.EndRecord(); + + // OBJPICTFMLA + XclExpString aClass( maClassName ); + sal_uInt16 nClassNameSize = static_cast< sal_uInt16 >( aClass.GetSize() ); + sal_uInt16 nClassNamePad = nClassNameSize & 1; + sal_uInt16 nFirstPartSize = 12 + nClassNameSize + nClassNamePad; + + const XclTokenArray* pCellLink = GetCellLinkTokArr(); + sal_uInt16 nCellLinkSize = pCellLink ? ((pCellLink->GetSize() + 7) & 0xFFFE) : 0; + + const XclTokenArray* pSrcRange = GetSourceRangeTokArr(); + sal_uInt16 nSrcRangeSize = pSrcRange ? ((pSrcRange->GetSize() + 7) & 0xFFFE) : 0; + + sal_uInt16 nPictFmlaSize = nFirstPartSize + nCellLinkSize + nSrcRangeSize + 18; + rStrm.StartRecord( EXC_ID_OBJPICTFMLA, nPictFmlaSize ); + + rStrm << nFirstPartSize // size of first part + << sal_uInt16( 5 ) // formula size + << sal_uInt32( 0 ) // unknown ID + << sal_uInt8( 0x02 ) << sal_uInt32( 0 ) // tTbl token with unknown ID + << sal_uInt8( 3 ) // pad to word + << aClass; // "Forms.***.1" + rStrm.WriteZeroBytes( nClassNamePad ); // pad to word + rStrm << mnStrmStart // start in 'Ctls' stream + << mnStrmSize // size in 'Ctls' stream + << sal_uInt32( 0 ); // class ID size + // cell link + rStrm << nCellLinkSize; + if( pCellLink ) + WriteFormula( rStrm, *pCellLink ); + // list source range + rStrm << nSrcRangeSize; + if( pSrcRange ) + WriteFormula( rStrm, *pSrcRange ); + + rStrm.EndRecord(); +} + +//#else + +XclExpTbxControlObj::XclExpTbxControlObj( XclExpObjectManager& rRoot, Reference< XShape > const & xShape , const tools::Rectangle* pChildAnchor ) : + XclObj( rRoot, EXC_OBJTYPE_UNKNOWN, true ), + XclMacroHelper( rRoot ), + mxShape( xShape ), + meEventType( EXC_TBX_EVENT_ACTION ), + mnHeight( 0 ), + mnState( 0 ), + mnLineCount( 0 ), + mnSelEntry( 0 ), + mnScrollValue( 0 ), + mnScrollMin( 0 ), + mnScrollMax( 100 ), + mnScrollStep( 1 ), + mnScrollPage( 10 ), + mbFlatButton( false ), + mbFlatBorder( false ), + mbMultiSel( false ), + mbScrollHor( false ), + mbPrint( false ), + mbVisible( false ), + mnShapeId( 0 ), + mrRoot(rRoot) +{ + namespace FormCompType = css::form::FormComponentType; + namespace AwtVisualEffect = css::awt::VisualEffect; + namespace AwtScrollOrient = css::awt::ScrollBarOrientation; + + ScfPropertySet aCtrlProp( XclControlHelper::GetControlModel( xShape ) ); + if( !xShape.is() || !aCtrlProp.Is() ) + return; + + mnHeight = xShape->getSize().Height; + if( mnHeight <= 0 ) + return; + + // control type + sal_Int16 nClassId = 0; + if( aCtrlProp.GetProperty( nClassId, "ClassId" ) ) + { + switch( nClassId ) + { + case FormCompType::COMMANDBUTTON: mnObjType = EXC_OBJTYPE_BUTTON; meEventType = EXC_TBX_EVENT_ACTION; break; + case FormCompType::RADIOBUTTON: mnObjType = EXC_OBJTYPE_OPTIONBUTTON; meEventType = EXC_TBX_EVENT_ACTION; break; + case FormCompType::CHECKBOX: mnObjType = EXC_OBJTYPE_CHECKBOX; meEventType = EXC_TBX_EVENT_ACTION; break; + case FormCompType::LISTBOX: mnObjType = EXC_OBJTYPE_LISTBOX; meEventType = EXC_TBX_EVENT_CHANGE; break; + case FormCompType::COMBOBOX: mnObjType = EXC_OBJTYPE_DROPDOWN; meEventType = EXC_TBX_EVENT_CHANGE; break; + case FormCompType::GROUPBOX: mnObjType = EXC_OBJTYPE_GROUPBOX; meEventType = EXC_TBX_EVENT_MOUSE; break; + case FormCompType::FIXEDTEXT: mnObjType = EXC_OBJTYPE_LABEL; meEventType = EXC_TBX_EVENT_MOUSE; break; + case FormCompType::SCROLLBAR: mnObjType = EXC_OBJTYPE_SCROLLBAR; meEventType = EXC_TBX_EVENT_VALUE; break; + case FormCompType::SPINBUTTON: mnObjType = EXC_OBJTYPE_SPIN; meEventType = EXC_TBX_EVENT_VALUE; break; + } + } + if( mnObjType == EXC_OBJTYPE_UNKNOWN ) + return; + + // OBJ record flags + SetLocked( true ); + mbPrint = aCtrlProp.GetBoolProperty( "Printable" ); + SetPrintable( mbPrint ); + SetAutoFill( false ); + SetAutoLine( false ); + + // fill DFF property set + mrEscherEx.OpenContainer( ESCHER_SpContainer ); + mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, ShapeFlag::HaveAnchor | ShapeFlag::HaveShapeProperty ); + EscherPropertyContainer aPropOpt; + mbVisible = aCtrlProp.GetBoolProperty( "EnableVisible" ); + aPropOpt.AddOpt( ESCHER_Prop_fPrint, mbVisible ? 0x00080000 : 0x00080002 ); // visible flag + + aPropOpt.AddOpt( ESCHER_Prop_LockAgainstGrouping, 0x01000100 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_lTxid, 0 ); // Text ID + aPropOpt.AddOpt( ESCHER_Prop_WrapText, 0x00000001 ); + aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x001A0008 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_fNoFillHitTest, 0x00100000 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080000 ); // bool field + + // #i51348# name of the control, may overwrite shape name + if( aCtrlProp.GetProperty( msCtrlName, "Name" ) && !msCtrlName.isEmpty() ) + aPropOpt.AddOpt( ESCHER_Prop_wzName, msCtrlName ); + + //Export description as alt text + if( SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ) ) + { + OUString aAltTxt; + OUString aDescrText = pSdrObj->GetDescription(); + if(!aDescrText.isEmpty()) + aAltTxt = aDescrText.copy( 0, std::min<sal_Int32>(MSPROP_DESCRIPTION_MAX_LEN, aDescrText.getLength()) ); + aPropOpt.AddOpt( ESCHER_Prop_wzDescription, aAltTxt ); + } + + // write DFF property set to stream + aPropOpt.Commit( mrEscherEx.GetStream() ); + + // anchor + ImplWriteAnchor( SdrObject::getSdrObjectFromXShape( xShape ), pChildAnchor ); + + mrEscherEx.AddAtom( 0, ESCHER_ClientData ); // OBJ record + mrEscherEx.UpdateDffFragmentEnd(); + + // control label + if( aCtrlProp.GetProperty( msLabel, "Label" ) ) + { + /* Be sure to construct the MSODRAWING record containing the + ClientTextbox atom after the base OBJ's MSODRAWING record data is + completed. */ + pClientTextbox.reset( new XclExpMsoDrawing( mrEscherEx ) ); + mrEscherEx.AddAtom( 0, ESCHER_ClientTextbox ); // TXO record + mrEscherEx.UpdateDffFragmentEnd(); + + sal_uInt16 nXclFont = EXC_FONT_APP; + if( !msLabel.isEmpty() ) + { + XclFontData aFontData; + GetFontPropSetHelper().ReadFontProperties( aFontData, aCtrlProp, EXC_FONTPROPSET_CONTROL ); + if( (!aFontData.maName.isEmpty() ) && (aFontData.mnHeight > 0) ) + nXclFont = GetFontBuffer().Insert( aFontData, EXC_COLOR_CTRLTEXT ); + } + + pTxo.reset( new XclTxo( msLabel, nXclFont ) ); + pTxo->SetHorAlign( (mnObjType == EXC_OBJTYPE_BUTTON) ? EXC_OBJ_HOR_CENTER : EXC_OBJ_HOR_LEFT ); + pTxo->SetVerAlign( EXC_OBJ_VER_CENTER ); + } + + mrEscherEx.CloseContainer(); // ESCHER_SpContainer + + // other properties + aCtrlProp.GetProperty( mnLineCount, "LineCount" ); + + // border style + sal_Int16 nApiButton = AwtVisualEffect::LOOK3D; + sal_Int16 nApiBorder = AwtVisualEffect::LOOK3D; + switch( nClassId ) + { + case FormCompType::LISTBOX: + case FormCompType::COMBOBOX: + aCtrlProp.GetProperty( nApiBorder, "Border" ); + break; + case FormCompType::CHECKBOX: + case FormCompType::RADIOBUTTON: + aCtrlProp.GetProperty( nApiButton, "VisualEffect" ); + nApiBorder = AwtVisualEffect::NONE; + break; + // Push button cannot be set to flat in Excel + case FormCompType::COMMANDBUTTON: + nApiBorder = AwtVisualEffect::LOOK3D; + break; + // Label does not support a border in Excel + case FormCompType::FIXEDTEXT: + nApiBorder = AwtVisualEffect::NONE; + break; + /* Scroll bar and spin button have a "Border" property, but it is + really used for a border, and not for own 3D/flat look (#i34712#). */ + case FormCompType::SCROLLBAR: + case FormCompType::SPINBUTTON: + nApiButton = AwtVisualEffect::LOOK3D; + nApiBorder = AwtVisualEffect::NONE; + break; + // Group box does not support flat style (#i34712#) + case FormCompType::GROUPBOX: + nApiBorder = AwtVisualEffect::LOOK3D; + break; + } + mbFlatButton = nApiButton != AwtVisualEffect::LOOK3D; + mbFlatBorder = nApiBorder != AwtVisualEffect::LOOK3D; + + // control state + sal_Int16 nApiState = 0; + if( aCtrlProp.GetProperty( nApiState, "State" ) ) + { + switch( nApiState ) + { + case 0: mnState = EXC_OBJ_CHECKBOX_UNCHECKED; break; + case 1: mnState = EXC_OBJ_CHECKBOX_CHECKED; break; + case 2: mnState = EXC_OBJ_CHECKBOX_TRISTATE; break; + } + } + + // special control contents + switch( nClassId ) + { + case FormCompType::LISTBOX: + { + mbMultiSel = aCtrlProp.GetBoolProperty( "MultiSelection" ); + Sequence< sal_Int16 > aSelection; + if( aCtrlProp.GetProperty( aSelection, "SelectedItems" ) ) + { + if( aSelection.hasElements() ) + { + mnSelEntry = aSelection[ 0 ] + 1; + comphelper::sequenceToContainer(maMultiSel, aSelection); + } + } + + // convert listbox with dropdown button to Excel dropdown + if( aCtrlProp.GetBoolProperty( "Dropdown" ) ) + mnObjType = EXC_OBJTYPE_DROPDOWN; + } + break; + + case FormCompType::COMBOBOX: + { + Sequence< OUString > aStringList; + OUString aDefText; + if( aCtrlProp.GetProperty( aStringList, "StringItemList" ) && + aCtrlProp.GetProperty( aDefText, "Text" ) && + aStringList.hasElements() && !aDefText.isEmpty() ) + { + auto nIndex = comphelper::findValue(aStringList, aDefText); + if( nIndex != -1 ) + mnSelEntry = static_cast< sal_Int16 >( nIndex + 1 ); // 1-based + if( mnSelEntry > 0 ) + maMultiSel.resize( 1, mnSelEntry - 1 ); + } + + // convert combobox without dropdown button to Excel listbox + if( !aCtrlProp.GetBoolProperty( "Dropdown" ) ) + mnObjType = EXC_OBJTYPE_LISTBOX; + } + break; + + case FormCompType::SCROLLBAR: + { + sal_Int32 nApiValue = 0; + if( aCtrlProp.GetProperty( nApiValue, "ScrollValueMin" ) ) + mnScrollMin = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "ScrollValueMax" ) ) + mnScrollMax = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "ScrollValue" ) ) + mnScrollValue = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, mnScrollMax ); + if( aCtrlProp.GetProperty( nApiValue, "LineIncrement" ) ) + mnScrollStep = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "BlockIncrement" ) ) + mnScrollPage = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "Orientation" ) ) + mbScrollHor = nApiValue == AwtScrollOrient::HORIZONTAL; + } + break; + + case FormCompType::SPINBUTTON: + { + sal_Int32 nApiValue = 0; + if( aCtrlProp.GetProperty( nApiValue, "SpinValueMin" ) ) + mnScrollMin = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "SpinValueMax" ) ) + mnScrollMax = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "SpinValue" ) ) + mnScrollValue = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, mnScrollMax ); + if( aCtrlProp.GetProperty( nApiValue, "SpinIncrement" ) ) + mnScrollStep = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "Orientation" ) ) + mbScrollHor = nApiValue == AwtScrollOrient::HORIZONTAL; + } + break; + } + + { + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( xShape ); + if( xCtrlModel.is() ) + { + Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY ); + if( xBindable.is() ) + { + Reference< XServiceInfo > xServInfo( xBindable->getValueBinding(), UNO_QUERY ); + if( xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_VALBIND ) ) + { + ScfPropertySet aBindProp( xServInfo ); + CellAddress aApiAddress; + if( aBindProp.GetProperty( aApiAddress, SC_UNONAME_BOUNDCELL ) ) + { + ScUnoConversion::FillScAddress( mxCellLinkAddress, aApiAddress ); + if( SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ) ) + lcl_GetFromTo( rRoot, pSdrObj->GetLogicRect(), mxCellLinkAddress.Tab(), maAreaFrom, maAreaTo, true ); + } + } + } + } + } + + // spreadsheet links + ConvertSheetLinks( xShape ); +} + +bool XclExpTbxControlObj::SetMacroLink( const ScriptEventDescriptor& rEvent ) +{ + return XclMacroHelper::SetMacroLink( rEvent, meEventType ); +} + +void XclExpTbxControlObj::WriteSubRecs( XclExpStream& rStrm ) +{ + switch( mnObjType ) + { + // *** Push buttons, labels *** + + case EXC_OBJTYPE_BUTTON: + case EXC_OBJTYPE_LABEL: + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + break; + + // *** Check boxes, option buttons *** + + case EXC_OBJTYPE_CHECKBOX: + case EXC_OBJTYPE_OPTIONBUTTON: + { + // ftCbls - box properties + sal_uInt16 nStyle = 0; + ::set_flag( nStyle, EXC_OBJ_CHECKBOX_FLAT, mbFlatButton ); + + rStrm.StartRecord( EXC_ID_OBJCBLS, 12 ); + rStrm << mnState; + rStrm.WriteZeroBytes( 8 ); + rStrm << nStyle; + rStrm.EndRecord(); + + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + // ftCblsFmla subrecord - cell link + WriteCellLinkSubRec( rStrm, EXC_ID_OBJCBLSFMLA ); + + // ftCblsData subrecord - box properties, again + rStrm.StartRecord( EXC_ID_OBJCBLS, 8 ); + rStrm << mnState; + rStrm.WriteZeroBytes( 4 ); + rStrm << nStyle; + rStrm.EndRecord(); + } + break; + + // *** List boxes, combo boxes *** + + case EXC_OBJTYPE_LISTBOX: + case EXC_OBJTYPE_DROPDOWN: + { + sal_uInt16 nEntryCount = GetSourceEntryCount(); + + // ftSbs subrecord - Scroll bars + sal_Int32 nLineHeight = XclTools::GetHmmFromTwips( 200 ); // always 10pt + if( mnObjType == EXC_OBJTYPE_LISTBOX ) + mnLineCount = static_cast< sal_uInt16 >( mnHeight / nLineHeight ); + mnScrollValue = 0; + mnScrollMin = 0; + sal_uInt16 nInvisLines = (nEntryCount >= mnLineCount) ? (nEntryCount - mnLineCount) : 0; + mnScrollMax = limit_cast< sal_uInt16 >( nInvisLines, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + mnScrollStep = 1; + mnScrollPage = limit_cast< sal_uInt16 >( mnLineCount, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + mbScrollHor = false; + WriteSbs( rStrm ); + + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + // ftSbsFmla subrecord - cell link + WriteCellLinkSubRec( rStrm, EXC_ID_OBJSBSFMLA ); + + // ftLbsData - source data range and box properties + sal_uInt16 nStyle = 0; + ::insert_value( nStyle, mbMultiSel ? EXC_OBJ_LISTBOX_MULTI : EXC_OBJ_LISTBOX_SINGLE, 4, 2 ); + ::set_flag( nStyle, EXC_OBJ_LISTBOX_FLAT, mbFlatBorder ); + + rStrm.StartRecord( EXC_ID_OBJLBSDATA, 0 ); + + if( const XclTokenArray* pSrcRange = GetSourceRangeTokArr() ) + { + rStrm << static_cast< sal_uInt16 >( (pSrcRange->GetSize() + 7) & 0xFFFE ); + WriteFormula( rStrm, *pSrcRange ); + } + else + rStrm << sal_uInt16( 0 ); + + rStrm << nEntryCount << mnSelEntry << nStyle << sal_uInt16( 0 ); + if( mnObjType == EXC_OBJTYPE_LISTBOX ) + { + if( nEntryCount ) + { + ScfUInt8Vec aSelEx( nEntryCount, 0 ); + for( const auto& rItem : maMultiSel ) + if( rItem < nEntryCount ) + aSelEx[ rItem ] = 1; + rStrm.Write( aSelEx.data(), aSelEx.size() ); + } + } + else if( mnObjType == EXC_OBJTYPE_DROPDOWN ) + { + rStrm << sal_uInt16( 0 ) << mnLineCount << sal_uInt16( 0 ) << sal_uInt16( 0 ); + } + + rStrm.EndRecord(); + } + break; + + // *** Spin buttons, scrollbars *** + + case EXC_OBJTYPE_SPIN: + case EXC_OBJTYPE_SCROLLBAR: + { + // ftSbs subrecord - scroll bars + WriteSbs( rStrm ); + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + // ftSbsFmla subrecord - cell link + WriteCellLinkSubRec( rStrm, EXC_ID_OBJSBSFMLA ); + } + break; + + // *** Group boxes *** + + case EXC_OBJTYPE_GROUPBOX: + { + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + + // ftGboData subrecord - group box properties + sal_uInt16 nStyle = 0; + ::set_flag( nStyle, EXC_OBJ_GROUPBOX_FLAT, mbFlatBorder ); + + rStrm.StartRecord( EXC_ID_OBJGBODATA, 6 ); + rStrm << sal_uInt32( 0 ) + << nStyle; + rStrm.EndRecord(); + } + break; + } +} + +void XclExpTbxControlObj::WriteCellLinkSubRec( XclExpStream& rStrm, sal_uInt16 nSubRecId ) +{ + if( const XclTokenArray* pCellLink = GetCellLinkTokArr() ) + WriteFormulaSubRec( rStrm, nSubRecId, *pCellLink ); +} + +void XclExpTbxControlObj::WriteSbs( XclExpStream& rStrm ) +{ + sal_uInt16 nOrient = 0; + ::set_flag( nOrient, EXC_OBJ_SCROLLBAR_HOR, mbScrollHor ); + sal_uInt16 nStyle = EXC_OBJ_SCROLLBAR_DEFFLAGS; + ::set_flag( nStyle, EXC_OBJ_SCROLLBAR_FLAT, mbFlatButton ); + + rStrm.StartRecord( EXC_ID_OBJSBS, 20 ); + rStrm << sal_uInt32( 0 ) // reserved + << mnScrollValue // thumb position + << mnScrollMin // thumb min pos + << mnScrollMax // thumb max pos + << mnScrollStep // line increment + << mnScrollPage // page increment + << nOrient // 0 = vertical, 1 = horizontal + << sal_uInt16( 15 ) // thumb width + << nStyle; // flags/style + rStrm.EndRecord(); +} + +void XclExpTbxControlObj::setShapeId(sal_Int32 aShapeId) +{ + mnShapeId = aShapeId; +} + +namespace +{ +/// Handles the VML export of form controls (e.g. checkboxes). +class VmlFormControlExporter : public oox::vml::VMLExport +{ + sal_uInt16 m_nObjType; + tools::Rectangle m_aAreaFrom; + tools::Rectangle m_aAreaTo; + OUString m_aLabel; + OUString m_aMacroName; + +public: + VmlFormControlExporter(const sax_fastparser::FSHelperPtr& p, sal_uInt16 nObjType, + const tools::Rectangle& rAreaFrom, const tools::Rectangle& rAreaTo, + const OUString& rLabel, const OUString& rMacroName); + +protected: + using VMLExport::StartShape; + sal_Int32 StartShape() override; + using VMLExport::EndShape; + void EndShape(sal_Int32 nShapeElement) override; +}; + +VmlFormControlExporter::VmlFormControlExporter(const sax_fastparser::FSHelperPtr& p, + sal_uInt16 nObjType, + const tools::Rectangle& rAreaFrom, + const tools::Rectangle& rAreaTo, + const OUString& rLabel, const OUString& rMacroName) + : VMLExport(p) + , m_nObjType(nObjType) + , m_aAreaFrom(rAreaFrom) + , m_aAreaTo(rAreaTo) + , m_aLabel(rLabel) + , m_aMacroName(rMacroName) +{ +} + +sal_Int32 VmlFormControlExporter::StartShape() +{ + // Host control. + AddShapeAttribute(XML_type, "#_x0000_t201"); + return VMLExport::StartShape(); +} + +void VmlFormControlExporter::EndShape(sal_Int32 nShapeElement) +{ + sax_fastparser::FSHelperPtr pVmlDrawing = GetFS(); + + pVmlDrawing->startElement(FSNS(XML_v, XML_textbox)); + pVmlDrawing->startElement(XML_div); + pVmlDrawing->write(m_aLabel); + pVmlDrawing->endElement(XML_div); + pVmlDrawing->endElement(FSNS(XML_v, XML_textbox)); + + OString aObjectType; + switch (m_nObjType) + { + case EXC_OBJTYPE_CHECKBOX: + aObjectType = "Checkbox"; + break; + case EXC_OBJTYPE_BUTTON: + aObjectType = "Button"; + break; + } + pVmlDrawing->startElement(FSNS(XML_x, XML_ClientData), XML_ObjectType, aObjectType); + OString aAnchor + = OString::number(m_aAreaFrom.Left()) + ", " + OString::number(m_aAreaFrom.Top()) + ", " + + OString::number(m_aAreaFrom.Right()) + ", " + OString::number(m_aAreaFrom.Bottom()) + ", " + + OString::number(m_aAreaTo.Left()) + ", " + OString::number(m_aAreaTo.Top()) + ", " + + OString::number(m_aAreaTo.Right()) + ", " + OString::number(m_aAreaTo.Bottom()); + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_Anchor), aAnchor); + + if (!m_aMacroName.isEmpty()) + { + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_FmlaMacro), m_aMacroName); + } + + // XclExpOcxControlObj::WriteSubRecs() has the same fixed values. + if (m_nObjType == EXC_OBJTYPE_BUTTON) + { + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_TextHAlign), "Center"); + } + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_TextVAlign), "Center"); + + pVmlDrawing->endElement(FSNS(XML_x, XML_ClientData)); + VMLExport::EndShape(nShapeElement); +} + +} + +/// Save into xl/drawings/vmlDrawing1.vml. +void XclExpTbxControlObj::SaveVml(XclExpXmlStream& rStrm) +{ + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + tools::Rectangle aAreaFrom; + tools::Rectangle aAreaTo; + // Unlike XclExpTbxControlObj::SaveXml(), this is not calculated in EMUs. + lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo); + VmlFormControlExporter aFormControlExporter(rStrm.GetCurrentStream(), GetObjType(), aAreaFrom, + aAreaTo, msLabel, GetMacroName()); + aFormControlExporter.AddSdrObject(*pObj, /*bIsFollowingTextFlow=*/false, /*eHOri=*/-1, + /*eVOri=*/-1, /*eHRel=*/-1, /*eVRel=*/-1, + /*pWrapAttrList=*/nullptr, /*bOOxmlExport=*/true); +} + +// save into xl\drawings\drawing1.xml +void XclExpTbxControlObj::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& pDrawing = rStrm.GetCurrentStream(); + + pDrawing->startElement(FSNS(XML_mc, XML_AlternateContent), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce))); + pDrawing->startElement(FSNS(XML_mc, XML_Choice), + FSNS(XML_xmlns, XML_a14), rStrm.getNamespaceURL(OOX_NS(a14)), + XML_Requires, "a14"); + + pDrawing->startElement(FSNS(XML_xdr, XML_twoCellAnchor), XML_editAs, "oneCell"); + { + pDrawing->startElement(FSNS(XML_xdr, XML_from)); + lcl_WriteAnchorVertex(pDrawing, maAreaFrom); + pDrawing->endElement(FSNS(XML_xdr, XML_from)); + pDrawing->startElement(FSNS(XML_xdr, XML_to)); + lcl_WriteAnchorVertex(pDrawing, maAreaTo); + pDrawing->endElement(FSNS(XML_xdr, XML_to)); + + pDrawing->startElement(FSNS(XML_xdr, XML_sp)); + { + // xdr:nvSpPr + pDrawing->startElement(FSNS(XML_xdr, XML_nvSpPr)); + { + pDrawing->singleElement(FSNS(XML_xdr, XML_cNvPr), + XML_id, OString::number(mnShapeId).getStr(), + XML_name, msCtrlName, // control name + XML_descr, msLabel, // description as alt text + XML_hidden, mbVisible ? "0" : "1"); + pDrawing->singleElement(FSNS(XML_xdr, XML_cNvSpPr)); + } + pDrawing->endElement(FSNS(XML_xdr, XML_nvSpPr)); + + // xdr:spPr + pDrawing->startElement(FSNS(XML_xdr, XML_spPr)); + { + // a:xfrm + pDrawing->startElement(FSNS(XML_a, XML_xfrm)); + { + pDrawing->singleElement(FSNS(XML_a, XML_off), + XML_x, "0", + XML_y, "0"); + pDrawing->singleElement(FSNS(XML_a, XML_ext), + XML_cx, "0", + XML_cy, "0"); + } + pDrawing->endElement(FSNS(XML_a, XML_xfrm)); + + // a:prstGeom + pDrawing->startElement(FSNS(XML_a, XML_prstGeom), XML_prst, "rect"); + { + pDrawing->singleElement(FSNS(XML_a, XML_avLst)); + } + pDrawing->endElement(FSNS(XML_a, XML_prstGeom)); + } + pDrawing->endElement(FSNS(XML_xdr, XML_spPr)); + + // xdr:txBody + { + pDrawing->startElement(FSNS(XML_xdr, XML_txBody)); + +#define DEFLRINS 254 +#define DEFTBINS 127 + sal_Int32 nLeft, nRight, nTop, nBottom; + nLeft = nRight = DEFLRINS; + nTop = nBottom = DEFTBINS; + + // top inset looks a bit different compared to ppt export + // check if something related doesn't work as expected + Reference< XPropertySet > rXPropSet(mxShape, UNO_QUERY); + + try + { + css::uno::Any mAny; + + mAny = rXPropSet->getPropertyValue("TextLeftDistance"); + if (mAny.hasValue()) + mAny >>= nLeft; + + mAny = rXPropSet->getPropertyValue("TextRightDistance"); + if (mAny.hasValue()) + mAny >>= nRight; + + mAny = rXPropSet->getPropertyValue("TextUpperDistance"); + if (mAny.hasValue()) + mAny >>= nTop; + + mAny = rXPropSet->getPropertyValue("TextLowerDistance"); + if (mAny.hasValue()) + mAny >>= nBottom; + } + catch (...) + { + } + + // Specifies the inset of the bounding rectangle. + // Insets are used just as internal margins for text boxes within shapes. + // If this attribute is omitted, then a value of 45720 or 0.05 inches is implied. + pDrawing->startElementNS(XML_a, XML_bodyPr, + XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != DEFLRINS), + XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != DEFLRINS), + XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != DEFTBINS), + XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != DEFTBINS), + XML_anchor, "ctr"); + + { + bool bTextAutoGrowHeight = false; + + try + { + css::uno::Any mAny; + + mAny = rXPropSet->getPropertyValue("TextAutoGrowHeight"); + if (mAny.hasValue()) + mAny >>= bTextAutoGrowHeight; + } + catch (...) + { + } + + pDrawing->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); + } + + pDrawing->endElementNS(XML_a, XML_bodyPr); + + { + pDrawing->startElementNS(XML_a, XML_p); + pDrawing->startElementNS(XML_a, XML_r); + pDrawing->startElementNS(XML_a, XML_t); + pDrawing->write(msLabel); + pDrawing->endElementNS(XML_a, XML_t); + pDrawing->endElementNS(XML_a, XML_r); + pDrawing->endElementNS(XML_a, XML_p); + } + + pDrawing->endElement(FSNS(XML_xdr, XML_txBody)); + } + } + pDrawing->endElement(FSNS(XML_xdr, XML_sp)); + pDrawing->singleElement(FSNS(XML_xdr, XML_clientData)); + } + pDrawing->endElement(FSNS(XML_xdr, XML_twoCellAnchor)); + pDrawing->endElement( FSNS( XML_mc, XML_Choice ) ); + pDrawing->endElement( FSNS( XML_mc, XML_AlternateContent ) ); +} + +// output into ctrlProp1.xml +OUString XclExpTbxControlObj::SaveControlPropertiesXml(XclExpXmlStream& rStrm) const +{ + OUString sIdFormControlPr; + + switch (mnObjType) + { + case EXC_OBJTYPE_CHECKBOX: + { + const sal_Int32 nDrawing = DrawingML::getNewDrawingUniqueId(); + sax_fastparser::FSHelperPtr pFormControl = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName( "xl/", "ctrlProps/ctrlProps", nDrawing ), + XclXmlUtils::GetStreamName( "../", "ctrlProps/ctrlProps", nDrawing ), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.ms-excel.controlproperties+xml", + oox::getRelationship(Relationship::CTRLPROP), + &sIdFormControlPr ); + + rStrm.PushStream( pFormControl ); + // checkbox + // <formControlPr + // xmlns="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" + // objectType="CheckBox" checked="Checked" lockText="1" noThreeD="1"/> + // + pFormControl->write("<formControlPr xmlns=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main\" objectType=\"CheckBox\""); + if (mnState == EXC_OBJ_CHECKBOX_CHECKED) + pFormControl->write(" checked=\"Checked\""); + + pFormControl->write(" autoLine=\"false\""); + + if (mbPrint) + pFormControl->write(" print=\"true\""); + else + pFormControl->write(" print=\"false\""); + + if (mxCellLinkAddress.IsValid()) + { + OUString aCellLink = mxCellLinkAddress.Format(ScRefFlags::ADDR_ABS, + &GetDoc(), + ScAddress::Details(::formula::FormulaGrammar::CONV_XL_A1)); + + // "Sheet1!$C$5" + pFormControl->write(" fmlaLink=\""); + if (aCellLink.indexOf('!') < 0) + { + pFormControl->write(GetTabInfo().GetScTabName(mxCellLinkAddress.Tab())); + pFormControl->write("!"); + } + pFormControl->write(aCellLink); + pFormControl->write("\""); + } + + pFormControl->write(" lockText=\"1\" noThreeD=\"1\"/>"); + rStrm.PopStream(); + + break; + } + case EXC_OBJTYPE_BUTTON: + { + sal_Int32 nDrawing = DrawingML::getNewDrawingUniqueId(); + sax_fastparser::FSHelperPtr pFormControl = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/", "ctrlProps/ctrlProps", nDrawing), + XclXmlUtils::GetStreamName("../", "ctrlProps/ctrlProps", nDrawing), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.ms-excel.controlproperties+xml", + oox::getRelationship(Relationship::CTRLPROP), &sIdFormControlPr); + + pFormControl->singleElement(XML_formControlPr, XML_xmlns, + rStrm.getNamespaceURL(OOX_NS(xls14Lst)), XML_objectType, + "Button", XML_lockText, "1"); + break; + } + } + + return sIdFormControlPr; +} + +// output into sheet1.xml +void XclExpTbxControlObj::SaveSheetXml(XclExpXmlStream& rStrm, const OUString& aIdFormControlPr) const +{ + switch (mnObjType) + { + case EXC_OBJTYPE_CHECKBOX: + { + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce))); + rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x14"); + + rWorksheet->startElement( + XML_control, + XML_shapeId, OString::number(mnShapeId).getStr(), + FSNS(XML_r, XML_id), aIdFormControlPr, + XML_name, msLabel); // text to display with checkbox button + + rWorksheet->write("<controlPr defaultSize=\"0\" locked=\"1\" autoFill=\"0\" autoLine=\"0\" autoPict=\"0\""); + + if (mbPrint) + rWorksheet->write(" print=\"true\""); + else + rWorksheet->write(" print=\"false\""); + + if (!msCtrlName.isEmpty()) + { + rWorksheet->write(" altText=\""); + rWorksheet->write(msCtrlName); // alt text + rWorksheet->write("\""); + } + + rWorksheet->write(">"); + + rWorksheet->startElement(XML_anchor, XML_moveWithCells, "true", XML_sizeWithCells, "false"); + rWorksheet->startElement(XML_from); + lcl_WriteAnchorVertex(rWorksheet, maAreaFrom); + rWorksheet->endElement(XML_from); + rWorksheet->startElement(XML_to); + lcl_WriteAnchorVertex(rWorksheet, maAreaTo); + rWorksheet->endElement(XML_to); + rWorksheet->endElement( XML_anchor ); + + rWorksheet->write("</controlPr>"); + + rWorksheet->endElement(XML_control); + rWorksheet->endElement( FSNS( XML_mc, XML_Choice ) ); + rWorksheet->endElement( FSNS( XML_mc, XML_AlternateContent ) ); + + break; + } + case EXC_OBJTYPE_BUTTON: + { + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), FSNS(XML_xmlns, XML_mc), + rStrm.getNamespaceURL(OOX_NS(mce))); + rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x14"); + + rWorksheet->startElement(XML_control, XML_shapeId, OString::number(mnShapeId).getStr(), + FSNS(XML_r, XML_id), aIdFormControlPr, XML_name, msCtrlName); + + OString aMacroName = GetMacroName().toUtf8(); + // Omit the macro attribute if it would be empty. + const char* pMacroName = aMacroName.isEmpty() ? nullptr : aMacroName.getStr(); + rWorksheet->startElement(XML_controlPr, XML_defaultSize, "0", XML_print, + mbPrint ? "true" : "false", XML_autoFill, "0", XML_autoPict, + "0", XML_macro, pMacroName); + + rWorksheet->startElement(XML_anchor, XML_moveWithCells, "true", XML_sizeWithCells, + "false"); + + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + tools::Rectangle aAreaFrom; + tools::Rectangle aAreaTo; + lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo, + /*bInEMU=*/true); + + rWorksheet->startElement(XML_from); + lcl_WriteAnchorVertex(rWorksheet, aAreaFrom); + rWorksheet->endElement(XML_from); + rWorksheet->startElement(XML_to); + lcl_WriteAnchorVertex(rWorksheet, aAreaTo); + rWorksheet->endElement(XML_to); + rWorksheet->endElement(XML_anchor); + + rWorksheet->endElement(XML_controlPr); + + rWorksheet->endElement(XML_control); + rWorksheet->endElement(FSNS(XML_mc, XML_Choice)); + rWorksheet->endElement(FSNS(XML_mc, XML_AlternateContent)); + break; + } + } +} + +//#endif + +XclExpChartObj::XclExpChartObj( XclExpObjectManager& rObjMgr, Reference< XShape > const & xShape, const tools::Rectangle* pChildAnchor, ScDocument* pDoc ) : + XclObj( rObjMgr, EXC_OBJTYPE_CHART ), + XclExpRoot( rObjMgr.GetRoot() ), mxShape( xShape ), + mpDoc(pDoc) +{ + // create the MSODRAWING record contents for the chart object + mrEscherEx.OpenContainer( ESCHER_SpContainer ); + mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, ShapeFlag::HaveAnchor | ShapeFlag::HaveShapeProperty ); + EscherPropertyContainer aPropOpt; + aPropOpt.AddOpt( ESCHER_Prop_LockAgainstGrouping, 0x01040104 ); + aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x00080008 ); + aPropOpt.AddOpt( ESCHER_Prop_fillColor, 0x0800004E ); + aPropOpt.AddOpt( ESCHER_Prop_fillBackColor, 0x0800004D ); + aPropOpt.AddOpt( ESCHER_Prop_fNoFillHitTest, 0x00110010 ); + aPropOpt.AddOpt( ESCHER_Prop_lineColor, 0x0800004D ); + aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080008 ); + aPropOpt.AddOpt( ESCHER_Prop_fshadowObscured, 0x00020000 ); + aPropOpt.AddOpt( ESCHER_Prop_fPrint, 0x00080000 ); + aPropOpt.Commit( mrEscherEx.GetStream() ); + + // anchor + SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ); + ImplWriteAnchor( pSdrObj, pChildAnchor ); + + // client data (the following OBJ record) + mrEscherEx.AddAtom( 0, ESCHER_ClientData ); + mrEscherEx.CloseContainer(); // ESCHER_SpContainer + mrEscherEx.UpdateDffFragmentEnd(); + + // load the chart OLE object + if( SdrOle2Obj* pSdrOleObj = dynamic_cast< SdrOle2Obj* >( pSdrObj ) ) + svt::EmbeddedObjectRef::TryRunningState( pSdrOleObj->GetObjRef() ); + + // create the chart substream object + ScfPropertySet aShapeProp( xShape ); + css::awt::Rectangle aBoundRect; + aShapeProp.GetProperty( aBoundRect, "BoundRect" ); + tools::Rectangle aChartRect( Point( aBoundRect.X, aBoundRect.Y ), Size( aBoundRect.Width, aBoundRect.Height ) ); + mxChart = std::make_shared<XclExpChart>(GetRoot(), GetChartDoc(), aChartRect); +} + +XclExpChartObj::~XclExpChartObj() +{ +} + +void XclExpChartObj::Save( XclExpStream& rStrm ) +{ + // content of OBJ record + XclObj::Save( rStrm ); + // chart substream + mxChart->Save( rStrm ); +} + +void XclExpChartObj::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pDrawing = rStrm.GetCurrentStream(); + + // FIXME: two cell? it seems the two cell anchor is incorrect. + pDrawing->startElement( FSNS( XML_xdr, XML_twoCellAnchor ), // OOXTODO: oneCellAnchor, absoluteAnchor + XML_editAs, "oneCell" ); + Reference< XPropertySet > xPropSet( mxShape, UNO_QUERY ); + if (xPropSet.is()) + { + XclObjAny::WriteFromTo( rStrm, mxShape, GetTab() ); + ChartExport aChartExport(XML_xdr, pDrawing, GetChartDoc(), &rStrm, drawingml::DOCUMENT_XLSX); + auto pURLTransformer = std::make_shared<ScURLTransformer>(*mpDoc); + aChartExport.SetURLTranslator(pURLTransformer); + static sal_Int32 nChartCount = 0; + nChartCount++; + sal_Int32 nID = rStrm.GetUniqueId(); + aChartExport.WriteChartObj( mxShape, nID, nChartCount ); + // TODO: get the correcto chart number + } + + pDrawing->singleElement( FSNS( XML_xdr, XML_clientData) + // OOXTODO: XML_fLocksWithSheet + // OOXTODO: XML_fPrintsWithSheet + ); + pDrawing->endElement( FSNS( XML_xdr, XML_twoCellAnchor ) ); +} + +css::uno::Reference<css::chart::XChartDocument> XclExpChartObj::GetChartDoc() const +{ + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + if (!pObj || pObj->GetObjIdentifier() != SdrObjKind::OLE2) + return {}; + // May load here - makes sure that we are working with actually loaded OLE object + return css::uno::Reference<css::chart::XChartDocument>( + static_cast<SdrOle2Obj*>(pObj)->getXModel(), css::uno::UNO_QUERY); +} + +XclExpNote::XclExpNote(const XclExpRoot& rRoot, const ScAddress& rScPos, + const ScPostIt* pScNote, std::u16string_view rAddText) + : XclExpRecord(EXC_ID_NOTE) + , mrRoot(rRoot) + , maScPos(rScPos) + , mnObjId(EXC_OBJ_INVALID_ID) + , mbVisible(pScNote && pScNote->IsCaptionShown()) + , meTHA(SDRTEXTHORZADJUST_LEFT) + , meTVA(SDRTEXTVERTADJUST_TOP) + , mbAutoScale(false) + , mbLocked(false) + , mbAutoFill(false) + , mbColHidden(false) + , mbRowHidden(false) +{ + // get the main note text + OUString aNoteText; + if( pScNote ) + { + aNoteText = pScNote->GetText(); + const EditTextObject *pEditObj = pScNote->GetEditTextObject(); + if( pEditObj ) + mpNoteContents = XclExpStringHelper::CreateString( rRoot, *pEditObj ); + } + // append additional text + aNoteText = ScGlobal::addToken( aNoteText, rAddText, '\n', 2 ); + + // initialize record dependent on BIFF type + switch( rRoot.GetBiff() ) + { + case EXC_BIFF5: + maNoteText = OUStringToOString(aNoteText, rRoot.GetTextEncoding()); + break; + + case EXC_BIFF8: + { + // TODO: additional text + if( pScNote ) + { + if( SdrCaptionObj* pCaption = pScNote->GetOrCreateCaption( maScPos ) ) + { + lcl_GetFromTo( rRoot, pCaption->GetLogicRect(), maScPos.Tab(), maCommentFrom, maCommentTo ); + if( const OutlinerParaObject* pOPO = pCaption->GetOutlinerParaObject() ) + mnObjId = rRoot.GetObjectManager().AddObj( std::make_unique<XclObjComment>( rRoot.GetObjectManager(), pCaption->GetLogicRect(), pOPO->GetTextObject(), pCaption, mbVisible, maScPos, maCommentFrom, maCommentTo ) ); + + SfxItemSet aItemSet = pCaption->GetMergedItemSet(); + meTVA = pCaption->GetTextVerticalAdjust(); + meTHA = pCaption->GetTextHorizontalAdjust(); + mbAutoScale = pCaption->GetFitToSize() != drawing::TextFitToSizeType_NONE; + mbLocked = pCaption->IsMoveProtect() || pCaption->IsResizeProtect(); + + // AutoFill style would change if Postit.cxx object creation values are changed + OUString aCol(aItemSet.Get(XATTR_FILLCOLOR).GetValue()); + mbAutoFill = aCol.isEmpty() && (aItemSet.Get(XATTR_FILLSTYLE).GetValue() == drawing::FillStyle_SOLID); + mbRowHidden = (rRoot.GetDoc().RowHidden(maScPos.Row(),maScPos.Tab())); + mbColHidden = (rRoot.GetDoc().ColHidden(maScPos.Col(),maScPos.Tab())); + } + // stAuthor (variable): An XLUnicodeString that specifies the name of the comment + // author. String length MUST be greater than or equal to 1 and less than or equal + // to 54. + if( pScNote->GetAuthor().isEmpty() ) + maAuthor = XclExpString( " " ); + else + maAuthor = XclExpString( pScNote->GetAuthor(), XclStrFlags::NONE, 54 ); + } + + SetRecSize( 9 + maAuthor.GetSize() ); + } + break; + + default: DBG_ERROR_BIFF(); + } +} + +void XclExpNote::Save( XclExpStream& rStrm ) +{ + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF5: + { + // write the NOTE record directly, there may be the need to create more than one + const char* pcBuffer = maNoteText.getStr(); + sal_uInt16 nCharsLeft = static_cast< sal_uInt16 >( maNoteText.getLength() ); + + while( nCharsLeft ) + { + sal_uInt16 nWriteChars = ::std::min( nCharsLeft, EXC_NOTE5_MAXLEN ); + + rStrm.StartRecord( EXC_ID_NOTE, 6 + nWriteChars ); + if( pcBuffer == maNoteText.getStr() ) + { + // first record: row, col, length of complete text + rStrm << static_cast< sal_uInt16 >( maScPos.Row() ) + << static_cast< sal_uInt16 >( maScPos.Col() ) + << nCharsLeft; // still contains full length + } + else + { + // next records: -1, 0, length of current text segment + rStrm << sal_uInt16( 0xFFFF ) + << sal_uInt16( 0 ) + << nWriteChars; + } + rStrm.Write( pcBuffer, nWriteChars ); + rStrm.EndRecord(); + + pcBuffer += nWriteChars; + nCharsLeft = nCharsLeft - nWriteChars; + } + } + break; + + case EXC_BIFF8: + if( mnObjId != EXC_OBJ_INVALID_ID ) + XclExpRecord::Save( rStrm ); + break; + + default: DBG_ERROR_BIFF(); + } +} + +void XclExpNote::WriteBody( XclExpStream& rStrm ) +{ + // BIFF5/BIFF7 is written separately + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ); + + sal_uInt16 nFlags = 0; + ::set_flag( nFlags, EXC_NOTE_VISIBLE, mbVisible ); + + rStrm << static_cast< sal_uInt16 >( maScPos.Row() ) + << static_cast< sal_uInt16 >( maScPos.Col() ) + << nFlags + << mnObjId + << maAuthor + << sal_uInt8( 0 ); +} + +void XclExpNote::WriteXml( sal_Int32 nAuthorId, XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr rComments = rStrm.GetCurrentStream(); + + rComments->startElement( XML_comment, + XML_ref, XclXmlUtils::ToOString(mrRoot.GetDoc(), maScPos), + XML_authorId, OString::number(nAuthorId) + // OOXTODO: XML_guid + ); + rComments->startElement(XML_text); + // OOXTODO: phoneticPr, rPh, r + if( mpNoteContents ) + mpNoteContents->WriteXml( rStrm ); + rComments->endElement( XML_text ); + +/* + Export of commentPr is disabled, since the current (Oct 2010) + version of MSO 2010 doesn't yet support commentPr + */ +#if 1//def XLSX_OOXML_FUTURE + if( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 ) + { + rComments->startElement(FSNS(XML_mc, XML_AlternateContent)); + rComments->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "v2"); + rComments->startElement( XML_commentPr, + XML_autoFill, ToPsz( mbAutoFill ), + XML_autoScale, ToPsz( mbAutoScale ), + XML_colHidden, ToPsz( mbColHidden ), + XML_locked, ToPsz( mbLocked ), + XML_rowHidden, ToPsz( mbRowHidden ), + XML_textHAlign, ToHorizAlign( meTHA ), + XML_textVAlign, ToVertAlign( meTVA ) ); + rComments->startElement(XML_anchor, XML_moveWithCells, "false", XML_sizeWithCells, "false"); + rComments->startElement(FSNS(XML_xdr, XML_from)); + lcl_WriteAnchorVertex( rComments, maCommentFrom ); + rComments->endElement( FSNS( XML_xdr, XML_from ) ); + rComments->startElement(FSNS(XML_xdr, XML_to)); + lcl_WriteAnchorVertex( rComments, maCommentTo ); + rComments->endElement( FSNS( XML_xdr, XML_to ) ); + rComments->endElement( XML_anchor ); + rComments->endElement( XML_commentPr ); + + rComments->endElement( FSNS( XML_mc, XML_Choice ) ); + rComments->startElement(FSNS(XML_mc, XML_Fallback)); + // Any fallback code ? + rComments->endElement( FSNS( XML_mc, XML_Fallback ) ); + rComments->endElement( FSNS( XML_mc, XML_AlternateContent ) ); + } +#endif + rComments->endElement( XML_comment ); +} + +XclMacroHelper::XclMacroHelper( const XclExpRoot& rRoot ) : + XclExpControlHelper( rRoot ) +{ +} + +XclMacroHelper::~XclMacroHelper() +{ +} + +void XclMacroHelper::WriteMacroSubRec( XclExpStream& rStrm ) +{ + if( mxMacroLink ) + WriteFormulaSubRec( rStrm, EXC_ID_OBJMACRO, *mxMacroLink ); +} + +const OUString& XclMacroHelper::GetMacroName() const { return maMacroName; } + +bool +XclMacroHelper::SetMacroLink( const ScriptEventDescriptor& rEvent, const XclTbxEventType& nEventType ) +{ + maMacroName = XclControlHelper::ExtractFromMacroDescriptor(rEvent, nEventType); + if (!maMacroName.isEmpty()) + { + return SetMacroLink(maMacroName); + } + return false; +} + +bool +XclMacroHelper::SetMacroLink( const OUString& rMacroName ) +{ + // OOXML documents do not store any defined name for VBA macros (while BIFF documents do). + bool bOOXML = GetOutput() == EXC_OUTPUT_XML_2007; + if (!rMacroName.isEmpty() && !bOOXML) + { + sal_uInt16 nExtSheet = GetLocalLinkManager().FindExtSheet( EXC_EXTSH_OWNDOC ); + sal_uInt16 nNameIdx + = GetNameManager().InsertMacroCall(rMacroName, /*bVBasic=*/true, /*bFunc=*/false); + mxMacroLink = GetFormulaCompiler().CreateNameXFormula( nExtSheet, nNameIdx ); + return true; + } + return false; +} + +XclExpShapeObj::XclExpShapeObj( XclExpObjectManager& rRoot, css::uno::Reference< css::drawing::XShape > const & xShape, ScDocument* pDoc ) : + XclObjAny( rRoot, xShape, pDoc ), + XclMacroHelper( rRoot ) +{ + if (SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape(xShape)) + { + ScMacroInfo* pInfo = ScDrawLayer::GetMacroInfo( pSdrObj ); + if ( pInfo && !pInfo->GetMacro().isEmpty() ) +// FIXME ooo330-m2: XclControlHelper::GetXclMacroName was removed in upstream sources; they started to call XclTools::GetXclMacroName instead; is this enough? it has only one parameter +// SetMacroLink( XclControlHelper::GetXclMacroName( pInfo->GetMacro(), rRoot.GetDocShell() ) ); + SetMacroLink( XclTools::GetXclMacroName( pInfo->GetMacro() ) ); + } +} + +XclExpShapeObj::~XclExpShapeObj() +{ +} + +void XclExpShapeObj::WriteSubRecs( XclExpStream& rStrm ) +{ + XclObjAny::WriteSubRecs( rStrm ); + WriteMacroSubRec( rStrm ); +} + +XclExpComments::XclExpComments( SCTAB nTab, XclExpRecordList< XclExpNote >& rNotes ) + : mnTab( nTab ), mrNotes( rNotes ) +{ +} + +void XclExpComments::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mrNotes.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr rComments = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName( "xl/", "comments", mnTab + 1 ), + XclXmlUtils::GetStreamName( "../", "comments", mnTab + 1 ), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml", + oox::getRelationship(Relationship::COMMENTS)); + rStrm.PushStream( rComments ); + + if( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 ) + rComments->startElement( XML_comments, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)), + FSNS(XML_xmlns, XML_xdr), rStrm.getNamespaceURL(OOX_NS(dmlSpreadDr)), + FSNS(XML_xmlns, XML_v2), rStrm.getNamespaceURL(OOX_NS(mceTest)), + FSNS( XML_mc, XML_Ignorable ), "v2" ); + else + rComments->startElement( XML_comments, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)), + FSNS(XML_xmlns, XML_xdr), rStrm.getNamespaceURL(OOX_NS(dmlSpreadDr)) ); + + rComments->startElement(XML_authors); + + typedef std::set<OUString> Authors; + Authors aAuthors; + + size_t nNotes = mrNotes.GetSize(); + for( size_t i = 0; i < nNotes; ++i ) + { + aAuthors.insert( XclXmlUtils::ToOUString( mrNotes.GetRecord( i )->GetAuthor() ) ); + } + + for( const auto& rAuthor : aAuthors ) + { + rComments->startElement(XML_author); + rComments->writeEscaped( rAuthor ); + rComments->endElement( XML_author ); + } + + rComments->endElement( XML_authors ); + rComments->startElement(XML_commentList); + + Authors::const_iterator aAuthorsBegin = aAuthors.begin(); + for( size_t i = 0; i < nNotes; ++i ) + { + XclExpRecordList< XclExpNote >::RecordRefType xNote = mrNotes.GetRecord( i ); + Authors::const_iterator aAuthor = aAuthors.find( + XclXmlUtils::ToOUString( xNote->GetAuthor() ) ); + sal_Int32 nAuthorId = distance( aAuthorsBegin, aAuthor ); + xNote->WriteXml( nAuthorId, rStrm ); + } + + rComments->endElement( XML_commentList ); + rComments->endElement( XML_comments ); + + rStrm.PopStream(); +} + +// object manager ============================================================= + +XclExpObjectManager::XclExpObjectManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + InitStream( true ); + mxEscherEx = std::make_shared<XclEscherEx>( GetRoot(), *this, *mxDffStrm ); +} + +XclExpObjectManager::XclExpObjectManager( const XclExpObjectManager& rParent ) : + XclExpRoot( rParent.GetRoot() ) +{ + InitStream( false ); + mxEscherEx = std::make_shared<XclEscherEx>( GetRoot(), *this, *mxDffStrm, rParent.mxEscherEx.get() ); +} + +XclExpObjectManager::~XclExpObjectManager() +{ +} + +XclExpDffAnchorBase* XclExpObjectManager::CreateDffAnchor() const +{ + return new XclExpDffSheetAnchor( GetRoot() ); +} + +rtl::Reference< XclExpRecordBase > XclExpObjectManager::CreateDrawingGroup() +{ + return new XclExpMsoDrawingGroup( *mxEscherEx ); +} + +void XclExpObjectManager::StartSheet() +{ + mxObjList = new XclExpObjList( GetRoot(), *mxEscherEx ); +} + +rtl::Reference< XclExpRecordBase > XclExpObjectManager::ProcessDrawing( const SdrPage* pSdrPage ) +{ + if( pSdrPage ) + mxEscherEx->AddSdrPage( *pSdrPage, GetOutput() != EXC_OUTPUT_BINARY ); + // the first dummy object may still be open + OSL_ENSURE( mxEscherEx->GetGroupLevel() <= 1, "XclExpObjectManager::ProcessDrawing - still groups open?" ); + while( mxEscherEx->GetGroupLevel() ) + mxEscherEx->LeaveGroup(); + mxObjList->EndSheet(); + return mxObjList; +} + +rtl::Reference< XclExpRecordBase > XclExpObjectManager::ProcessDrawing( const Reference< XShapes >& rxShapes ) +{ + if( rxShapes.is() ) + mxEscherEx->AddUnoShapes( rxShapes, GetOutput() != EXC_OUTPUT_BINARY ); + // the first dummy object may still be open + OSL_ENSURE( mxEscherEx->GetGroupLevel() <= 1, "XclExpObjectManager::ProcessDrawing - still groups open?" ); + while( mxEscherEx->GetGroupLevel() ) + mxEscherEx->LeaveGroup(); + mxObjList->EndSheet(); + return mxObjList; +} + +void XclExpObjectManager::EndDocument() +{ + mxEscherEx->EndDocument(); +} + +XclExpMsoDrawing* XclExpObjectManager::GetMsodrawingPerSheet() +{ + return mxObjList->GetMsodrawingPerSheet(); +} + +bool XclExpObjectManager::HasObj() const +{ + return !mxObjList->empty(); +} + +sal_uInt16 XclExpObjectManager::AddObj( std::unique_ptr<XclObj> pObjRec ) +{ + return mxObjList->Add( std::move(pObjRec) ); +} + +std::unique_ptr<XclObj> XclExpObjectManager::RemoveLastObj() +{ + return mxObjList->pop_back(); +} + +void XclExpObjectManager::InitStream( bool bTempFile ) +{ + if( bTempFile ) + { + mxTempFile = std::make_shared<::utl::TempFile>(); + if( mxTempFile->IsValid() ) + { + mxTempFile->EnableKillingFile(); + mxDffStrm = ::utl::UcbStreamHelper::CreateStream( mxTempFile->GetURL(), StreamMode::STD_READWRITE ); + } + } + + if( !mxDffStrm ) + mxDffStrm = std::make_unique<SvMemoryStream>(); + + mxDffStrm->SetEndian( SvStreamEndian::LITTLE ); +} + +XclExpEmbeddedObjectManager::XclExpEmbeddedObjectManager( + const XclExpObjectManager& rParent, const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY ) : + XclExpObjectManager( rParent ), + maPageSize( rPageSize ), + mnScaleX( nScaleX ), + mnScaleY( nScaleY ) +{ +} + +XclExpDffAnchorBase* XclExpEmbeddedObjectManager::CreateDffAnchor() const +{ + return new XclExpDffEmbeddedAnchor( GetRoot(), maPageSize, mnScaleX, mnScaleY ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeextlst.cxx b/sc/source/filter/excel/xeextlst.cxx new file mode 100644 index 000000000..242f21dbb --- /dev/null +++ b/sc/source/filter/excel/xeextlst.cxx @@ -0,0 +1,652 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <xeextlst.hxx> +#include <xeroot.hxx> +#include <xestyle.hxx> +#include <stlpool.hxx> +#include <scitems.hxx> +#include <svl/itemset.hxx> +#include <svl/intitem.hxx> + +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> + +using namespace ::oox; + +XclExpExt::XclExpExt( const XclExpRoot& rRoot ): + XclExpRoot(rRoot) +{ +} + +XclExtLst::XclExtLst( const XclExpRoot& rRoot ): + XclExpRoot(rRoot) +{ +} + +XclExpExtNegativeColor::XclExpExtNegativeColor( const Color& rColor ): + maColor(rColor) +{ +} + +void XclExpExtNegativeColor::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElementNS( XML_x14, XML_negativeFillColor, + XML_rgb, XclXmlUtils::ToOString(maColor) ); +} + +XclExpExtAxisColor::XclExpExtAxisColor( const Color& rColor ): + maAxisColor(rColor) +{ +} + +void XclExpExtAxisColor::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElementNS( XML_x14, XML_axisColor, + XML_rgb, XclXmlUtils::ToOString(maAxisColor) ); +} + +XclExpExtIcon::XclExpExtIcon(const XclExpRoot& rRoot, const std::pair<ScIconSetType, sal_Int32>& rCustomEntry): + XclExpRoot(rRoot), + nIndex(rCustomEntry.second) +{ + pIconSetName = ScIconSetFormat::getIconSetName(rCustomEntry.first); +} + +void XclExpExtIcon::SaveXml(XclExpXmlStream& rStrm) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + if (nIndex == -1) + { + nIndex = 0; + pIconSetName = "NoIcons"; + } + + rWorksheet->singleElementNS(XML_x14, XML_cfIcon, + XML_iconSet, pIconSetName, + XML_iconId, OString::number(nIndex)); +} + +XclExpExtCfvo::XclExpExtCfvo( const XclExpRoot& rRoot, const ScColorScaleEntry& rEntry, const ScAddress& rSrcPos, bool bFirst ): + XclExpRoot(rRoot), + meType(rEntry.GetType()), + mbFirst(bFirst) +{ + if( rEntry.GetType() == COLORSCALE_FORMULA ) + { + const ScTokenArray* pArr = rEntry.GetFormula(); + OUString aFormula; + if(pArr) + { + aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), rSrcPos, pArr); + } + maValue = OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8 ); + } + else + maValue = OString::number(rEntry.GetValue()); +} + +namespace { + +const char* getColorScaleType( ScColorScaleEntryType eType, bool bFirst ) +{ + switch(eType) + { + case COLORSCALE_MIN: + return "min"; + case COLORSCALE_MAX: + return "max"; + case COLORSCALE_PERCENT: + return "percent"; + case COLORSCALE_FORMULA: + return "formula"; + case COLORSCALE_AUTO: + if(bFirst) + return "autoMin"; + else + return "autoMax"; + case COLORSCALE_PERCENTILE: + return "percentile"; + default: + break; + } + return "num"; +} + +} + +void XclExpExtCfvo::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS(XML_x14, XML_cfvo, XML_type, getColorScaleType(meType, mbFirst)); + + if (meType == COLORSCALE_FORMULA || + meType == COLORSCALE_PERCENT || + meType == COLORSCALE_PERCENTILE || + meType == COLORSCALE_VALUE) + { + rWorksheet->startElementNS(XML_xm, XML_f); + rWorksheet->writeEscaped(maValue.getStr()); + rWorksheet->endElementNS(XML_xm, XML_f); + } + + rWorksheet->endElementNS(XML_x14, XML_cfvo); +} + +XclExpExtCF::XclExpExtCF( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormat ): + XclExpRoot(rRoot), + mrFormat(rFormat) +{ +} + +namespace { + +bool RequiresFixedFormula(ScConditionMode eMode) +{ + switch (eMode) + { + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + return true; + default: + break; + } + + return false; +} + +OString GetFixedFormula(ScConditionMode eMode, const ScAddress& rAddress, std::string_view rText) +{ + OStringBuffer aBuffer; + XclXmlUtils::ToOString(aBuffer, rAddress); + OString aPos = aBuffer.makeStringAndClear(); + switch (eMode) + { + case ScConditionMode::BeginsWith: + return OString("LEFT(" + aPos + ",LEN(" + rText + "))=" + rText); + case ScConditionMode::EndsWith: + return OString("RIGHT(" + aPos + ",LEN(" + rText + "))=" + rText); + case ScConditionMode::ContainsText: + return OString(OString::Concat("NOT(ISERROR(SEARCH(") + rText + "," + aPos + ")))"); + case ScConditionMode::NotContainsText: + return OString(OString::Concat("ISERROR(SEARCH(") + rText + "," + aPos + "))"); + default: + break; + } + + return ""; +} + +} + +void XclExpExtCF::SaveXml( XclExpXmlStream& rStrm ) +{ + OUString aStyleName = mrFormat.GetStyle(); + SfxStyleSheetBasePool* pPool = GetDoc().GetStyleSheetPool(); + SfxStyleSheetBase* pStyle = pPool->Find(aStyleName, SfxStyleFamily::Para); + SfxItemSet& rSet = pStyle->GetItemSet(); + + std::unique_ptr<ScTokenArray> pTokenArray(mrFormat.CreateFlatCopiedTokenArray(0)); + aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormat.GetValidSrcPos(), pTokenArray.get()); + + std::unique_ptr<XclExpColor> pColor(new XclExpColor); + if(!pColor->FillFromItemSet( rSet )) + pColor.reset(); + + std::unique_ptr<XclExpCellBorder> pBorder(new XclExpCellBorder); + if (!pBorder->FillFromItemSet( rSet, GetPalette(), GetBiff()) ) + pBorder.reset(); + + std::unique_ptr<XclExpCellAlign> pAlign(new XclExpCellAlign); + if (!pAlign->FillFromItemSet(*this, rSet, false, GetBiff())) + pAlign.reset(); + + std::unique_ptr<XclExpCellProt> pCellProt(new XclExpCellProt); + if (!pCellProt->FillFromItemSet( rSet )) + pCellProt.reset(); + + std::unique_ptr<XclExpDxfFont> pFont(new XclExpDxfFont(GetRoot(), rSet)); + + std::unique_ptr<XclExpNumFmt> pNumFormat; + if( const SfxUInt32Item* pPoolItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT ) ) + { + sal_uInt32 nScNumFmt = pPoolItem->GetValue(); + XclExpNumFmtBuffer& rNumFmtBuffer = GetRoot().GetNumFmtBuffer(); + sal_uInt32 nXclNumFmt = rNumFmtBuffer.Insert(nScNumFmt); + pNumFormat.reset(new XclExpNumFmt(nScNumFmt, nXclNumFmt, rNumFmtBuffer.GetFormatCode(nScNumFmt))); + } + + XclExpDxf rDxf( GetRoot(), + std::move(pAlign), + std::move(pBorder), + std::move(pFont), + std::move(pNumFormat), + std::move(pCellProt), + std::move(pColor) ); + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + ScConditionMode eOperation = mrFormat.GetOperation(); + if (RequiresFixedFormula(eOperation)) + { + ScAddress aFixedFormulaPos = mrFormat.GetValidSrcPos(); + OString aFixedFormulaText = aFormula.toUtf8(); + OString aFixedFormula = GetFixedFormula(eOperation, aFixedFormulaPos, aFixedFormulaText); + rWorksheet->startElementNS( XML_xm, XML_f ); + rWorksheet->writeEscaped(aFixedFormula.getStr()); + rWorksheet->endElementNS( XML_xm, XML_f ); + + rWorksheet->startElementNS( XML_xm, XML_f ); + rWorksheet->writeEscaped( aFormula ); + rWorksheet->endElementNS( XML_xm, XML_f ); + rDxf.SaveXmlExt(rStrm); + } + else + { + rWorksheet->startElementNS(XML_xm, XML_f); + rWorksheet->writeEscaped(aFormula); + rWorksheet->endElementNS(XML_xm, XML_f); + rDxf.SaveXmlExt(rStrm); + } +} + +XclExpExtDataBar::XclExpExtDataBar( const XclExpRoot& rRoot, const ScDataBarFormat& rFormat, const ScAddress& rPos ): + XclExpRoot(rRoot) +{ + const ScDataBarFormatData& rFormatData = *rFormat.GetDataBarData(); + mpLowerLimit.reset(new XclExpExtCfvo(*this, *rFormatData.mpLowerLimit, rPos, true)); + mpUpperLimit.reset(new XclExpExtCfvo(*this, *rFormatData.mpUpperLimit, rPos, false)); + if (rFormatData.mxNegativeColor) + mpNegativeColor.reset(new XclExpExtNegativeColor(*rFormatData.mxNegativeColor)); + else + mpNegativeColor.reset( new XclExpExtNegativeColor( rFormatData.maPositiveColor ) ); + mpAxisColor.reset( new XclExpExtAxisColor( rFormatData.maAxisColor ) ); + + meAxisPosition = rFormatData.meAxisPosition; + mbGradient = rFormatData.mbGradient; + mnMinLength = rFormatData.mnMinLength; + mnMaxLength = rFormatData.mnMaxLength; +} + +namespace { + +const char* getAxisPosition(databar::ScAxisPosition eAxisPosition) +{ + switch(eAxisPosition) + { + case databar::NONE: + return "none"; + case databar::AUTOMATIC: + return "automatic"; + case databar::MIDDLE: + return "middle"; + } + return ""; +} + +const char* GetOperatorString(ScConditionMode eMode) +{ + const char* pRet = nullptr; + switch(eMode) + { + case ScConditionMode::Equal: + pRet = "equal"; + break; + case ScConditionMode::Less: + pRet = "lessThan"; + break; + case ScConditionMode::Greater: + pRet = "greaterThan"; + break; + case ScConditionMode::EqLess: + pRet = "lessThanOrEqual"; + break; + case ScConditionMode::EqGreater: + pRet = "greaterThanOrEqual"; + break; + case ScConditionMode::NotEqual: + pRet = "notEqual"; + break; + case ScConditionMode::Between: + pRet = "between"; + break; + case ScConditionMode::NotBetween: + pRet = "notBetween"; + break; + case ScConditionMode::Duplicate: + pRet = nullptr; + break; + case ScConditionMode::NotDuplicate: + pRet = nullptr; + break; + case ScConditionMode::BeginsWith: + pRet = "beginsWith"; + break; + case ScConditionMode::EndsWith: + pRet = "endsWith"; + break; + case ScConditionMode::ContainsText: + pRet = "containsText"; + break; + case ScConditionMode::NotContainsText: + pRet = "notContains"; + break; + case ScConditionMode::Direct: + break; + case ScConditionMode::NONE: + default: + break; + } + return pRet; +} + +const char* GetTypeString(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::Direct: + return "expression"; + case ScConditionMode::BeginsWith: + return "beginsWith"; + case ScConditionMode::EndsWith: + return "endsWith"; + case ScConditionMode::ContainsText: + return "containsText"; + case ScConditionMode::NotContainsText: + return "notContainsText"; + default: + return "cellIs"; + } +} + +} + +void XclExpExtDataBar::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS( XML_x14, XML_dataBar, + XML_minLength, OString::number(mnMinLength), + XML_maxLength, OString::number(mnMaxLength), + XML_axisPosition, getAxisPosition(meAxisPosition), + XML_gradient, ToPsz(mbGradient) ); + + mpLowerLimit->SaveXml( rStrm ); + mpUpperLimit->SaveXml( rStrm ); + mpNegativeColor->SaveXml( rStrm ); + mpAxisColor->SaveXml( rStrm ); + + rWorksheet->endElementNS( XML_x14, XML_dataBar ); +} + +XclExpExtIconSet::XclExpExtIconSet(const XclExpRoot& rRoot, const ScIconSetFormat& rFormat, const ScAddress& rPos): + XclExpRoot(rRoot) +{ + const ScIconSetFormatData& rData = *rFormat.GetIconSetData(); + for (auto const& itr : rData.m_Entries) + { + maCfvos.AppendNewRecord(new XclExpExtCfvo(*this, *itr, rPos, false)); + } + mbCustom = rData.mbCustom; + mbReverse = rData.mbReverse; + mbShowValue = rData.mbShowValue; + mpIconSetName = ScIconSetFormat::getIconSetName(rData.eIconSetType); + + if (mbCustom) + { + for (const auto& rItem : rData.maCustomVector) + { + maCustom.AppendNewRecord(new XclExpExtIcon(*this, rItem)); + } + } +} + +void XclExpExtIconSet::SaveXml(XclExpXmlStream& rStrm) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElementNS(XML_x14, XML_iconSet, + XML_iconSet, mpIconSetName, + XML_custom, sax_fastparser::UseIf(ToPsz10(mbCustom), mbCustom), + XML_reverse, ToPsz10(mbReverse), + XML_showValue, ToPsz10(mbShowValue)); + + maCfvos.SaveXml(rStrm); + + if (mbCustom) + { + maCustom.SaveXml(rStrm); + } + + rWorksheet->endElementNS(XML_x14, XML_iconSet); +} + +XclExpExtCfRule::XclExpExtCfRule( const XclExpRoot& rRoot, const ScFormatEntry& rFormat, const ScAddress& rPos, const OString& rId, sal_Int32 nPriority ): + XclExpRoot(rRoot), + maId(rId), + pType(nullptr), + mnPriority(nPriority), + mOperator(nullptr) +{ + switch (rFormat.GetType()) + { + case ScFormatEntry::Type::Databar: + { + const ScDataBarFormat& rDataBar = static_cast<const ScDataBarFormat&>(rFormat); + mxEntry = new XclExpExtDataBar( *this, rDataBar, rPos ); + pType = "dataBar"; + } + break; + case ScFormatEntry::Type::Iconset: + { + const ScIconSetFormat& rIconSet = static_cast<const ScIconSetFormat&>(rFormat); + mxEntry = new XclExpExtIconSet(*this, rIconSet, rPos); + pType = "iconSet"; + } + break; + case ScFormatEntry::Type::ExtCondition: + { + const ScCondFormatEntry& rCondFormat = static_cast<const ScCondFormatEntry&>(rFormat); + mxEntry = new XclExpExtCF(*this, rCondFormat); + pType = GetTypeString(rCondFormat.GetOperation()); + mOperator = GetOperatorString( rCondFormat.GetOperation() ); + } + break; + default: + break; + } +} + +void XclExpExtCfRule::SaveXml( XclExpXmlStream& rStrm ) +{ + if (!mxEntry) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS( XML_x14, XML_cfRule, + XML_type, pType, + XML_priority, sax_fastparser::UseIf(OString::number(mnPriority + 1), mnPriority != -1), + XML_operator, mOperator, + XML_id, maId ); + + mxEntry->SaveXml( rStrm ); + + rWorksheet->endElementNS( XML_x14, XML_cfRule ); + +} + +XclExpExtConditionalFormatting::XclExpExtConditionalFormatting( const XclExpRoot& rRoot, + std::vector<XclExpExtCondFormatData>& rData, const ScRangeList& rRange): + XclExpRoot(rRoot), + maRange(rRange) +{ + ScAddress aAddr = maRange.front().aStart; + for (const auto& rItem : rData) + { + const ScFormatEntry* pEntry = rItem.pEntry; + switch (pEntry->GetType()) + { + case ScFormatEntry::Type::Iconset: + { + const ScIconSetFormat& rIconSet = static_cast<const ScIconSetFormat&>(*pEntry); + bool bNeedsExt = false; + switch (rIconSet.GetIconSetData()->eIconSetType) + { + case IconSet_3Triangles: + case IconSet_3Smilies: + case IconSet_3ColorSmilies: + case IconSet_5Boxes: + case IconSet_3Stars: + bNeedsExt = true; + break; + default: + break; + } + + if (rIconSet.GetIconSetData()->mbCustom) + bNeedsExt = true; + + if (bNeedsExt) + { + maCfRules.AppendNewRecord(new XclExpExtCfRule(*this, *pEntry, aAddr, rItem.aGUID, rItem.nPriority)); + } + } + break; + case ScFormatEntry::Type::Databar: + maCfRules.AppendNewRecord(new XclExpExtCfRule( *this, *pEntry, aAddr, rItem.aGUID, rItem.nPriority)); + break; + case ScFormatEntry::Type::ExtCondition: + maCfRules.AppendNewRecord(new XclExpExtCfRule( *this, *pEntry, aAddr, rItem.aGUID, rItem.nPriority)); + break; + default: + break; + } + } +} + +void XclExpExtConditionalFormatting::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS( XML_x14, XML_conditionalFormatting, + FSNS( XML_xmlns, XML_xm ), rStrm.getNamespaceURL(OOX_NS(xm)) ); + + maCfRules.SaveXml( rStrm ); + rWorksheet->startElementNS(XML_xm, XML_sqref); + rWorksheet->write(XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maRange)); + + rWorksheet->endElementNS( XML_xm, XML_sqref ); + + rWorksheet->endElementNS( XML_x14, XML_conditionalFormatting ); +} + +XclExpExtCalcPr::XclExpExtCalcPr( const XclExpRoot& rRoot, formula::FormulaGrammar::AddressConvention eConv ): + XclExpExt( rRoot ) +{ + maURI = OString("{7626C862-2A13-11E5-B345-FEFF819CDC9F}"); + + switch (eConv) + { + case formula::FormulaGrammar::CONV_OOO: + maSyntax = OString("CalcA1"); + break; + case formula::FormulaGrammar::CONV_XL_A1: + maSyntax = OString("ExcelA1"); + break; + case formula::FormulaGrammar::CONV_XL_R1C1: + maSyntax = OString("ExcelR1C1"); + break; + case formula::FormulaGrammar::CONV_A1_XL_A1: + maSyntax = OString("CalcA1ExcelA1"); + break; + case formula::FormulaGrammar::CONV_UNSPECIFIED: + case formula::FormulaGrammar::CONV_ODF: + case formula::FormulaGrammar::CONV_XL_OOX: + case formula::FormulaGrammar::CONV_LOTUS_A1: + case formula::FormulaGrammar::CONV_LAST: + maSyntax = OString("Unspecified"); + break; + } +} + +void XclExpExtCalcPr::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_ext, + FSNS(XML_xmlns, XML_loext), rStrm.getNamespaceURL(OOX_NS(loext)), + XML_uri, maURI ); + + rWorksheet->singleElementNS(XML_loext, XML_extCalcPr, XML_stringRefSyntax, maSyntax); + + rWorksheet->endElement( XML_ext ); +} + +XclExpExtCondFormat::XclExpExtCondFormat( const XclExpRoot& rRoot ): + XclExpExt( rRoot ) +{ + maURI = OString("{78C0D931-6437-407d-A8EE-F0AAD7539E65}"); +} + +void XclExpExtCondFormat::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_ext, + FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)), + XML_uri, maURI ); + + rWorksheet->startElementNS(XML_x14, XML_conditionalFormattings); + + maCF.SaveXml( rStrm ); + + rWorksheet->endElementNS( XML_x14, XML_conditionalFormattings ); + rWorksheet->endElement( XML_ext ); +} + +void XclExpExtCondFormat::AddRecord( XclExpExtConditionalFormatting* pEntry ) +{ + maCF.AppendRecord( pEntry ); +} + +void XclExtLst::SaveXml( XclExpXmlStream& rStrm ) +{ + if(maExtEntries.IsEmpty()) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_extLst); + + maExtEntries.SaveXml(rStrm); + + rWorksheet->endElement( XML_extLst ); +} + +void XclExtLst::AddRecord( XclExpExt* pEntry ) +{ + maExtEntries.AppendRecord( pEntry ); +} + +XclExpExt* XclExtLst::GetItem( XclExpExtType eType ) +{ + size_t n = maExtEntries.GetSize(); + for( size_t i = 0; i < n; ++i ) + { + if (maExtEntries.GetRecord( i )->GetType() == eType) + return maExtEntries.GetRecord( i ); + } + + return nullptr; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeformula.cxx b/sc/source/filter/excel/xeformula.cxx new file mode 100644 index 000000000..e6eabd69c --- /dev/null +++ b/sc/source/filter/excel/xeformula.cxx @@ -0,0 +1,2709 @@ +/* -*- 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 <map> +#include <addincol.hxx> +#include <compiler.hxx> +#include <document.hxx> +#include <externalrefmgr.hxx> +#include <rangelst.hxx> +#include <tokenarray.hxx> +#include <scmatrix.hxx> +#include <xeformula.hxx> +#include <xehelper.hxx> +#include <xelink.hxx> +#include <xename.hxx> +#include <xestring.hxx> +#include <xllink.hxx> +#include <xltools.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +using namespace ::formula; + +// External reference log ===================================================== + +XclExpRefLogEntry::XclExpRefLogEntry() : + mpUrl( nullptr ), + mpFirstTab( nullptr ), + mpLastTab( nullptr ), + mnFirstXclTab( EXC_TAB_DELETED ), + mnLastXclTab( EXC_TAB_DELETED ) +{ +} + +// Formula compiler =========================================================== + +namespace { + +/** Wrapper structure for a processed Calc formula token with additional + settings (whitespaces). */ +struct XclExpScToken +{ + const FormulaToken* mpScToken; /// Currently processed Calc token. + sal_uInt8 mnSpaces; /// Number of spaces before the Calc token. + + explicit XclExpScToken() : mpScToken( nullptr ), mnSpaces( 0 ) {} + bool Is() const { return mpScToken != nullptr; } + StackVar GetType() const { return mpScToken ? mpScToken->GetType() : svUnknown; } + OpCode GetOpCode() const { return mpScToken ? mpScToken->GetOpCode() : ocNone; } +}; + +/** Effective token class conversion types. */ +enum XclExpClassConv +{ + EXC_CLASSCONV_ORG, /// Keep original class of the token. + EXC_CLASSCONV_VAL, /// Convert ARR tokens to VAL class (REF remains unchanged). + EXC_CLASSCONV_ARR /// Convert VAL tokens to ARR class (REF remains unchanged). +}; + +/** Token class conversion and position of a token in the token array. */ +struct XclExpTokenConvInfo +{ + sal_uInt16 mnTokPos; /// Position of the token in the token array. + XclFuncParamConv meConv; /// Token class conversion type. + bool mbValType; /// Data type (false = REFTYPE, true = VALTYPE). +}; + +/** Vector of token position and conversion for all operands of an operator, + or for all parameters of a function. */ +struct XclExpOperandList : public std::vector< XclExpTokenConvInfo > +{ + explicit XclExpOperandList() { reserve( 2 ); } + void AppendOperand( sal_uInt16 nTokPos, XclFuncParamConv eConv, bool bValType ); +}; + +void XclExpOperandList::AppendOperand( sal_uInt16 nTokPos, XclFuncParamConv eConv, bool bValType ) +{ + resize( size() + 1 ); + XclExpTokenConvInfo& rConvInfo = back(); + rConvInfo.mnTokPos = nTokPos; + rConvInfo.meConv = eConv; + rConvInfo.mbValType = bValType; +} + +typedef std::shared_ptr< XclExpOperandList > XclExpOperandListRef; + +/** Encapsulates all data needed for a call to an external function (macro, add-in). */ +struct XclExpExtFuncData +{ + OUString maFuncName; /// Name of the function. + bool mbVBasic; /// True = Visual Basic macro call. + bool mbHidden; /// True = Create hidden defined name. + + explicit XclExpExtFuncData() : mbVBasic( false ), mbHidden( false ) {} + void Set( const OUString& rFuncName, bool bVBasic, bool bHidden ); +}; + +void XclExpExtFuncData::Set( const OUString& rFuncName, bool bVBasic, bool bHidden ) +{ + maFuncName = rFuncName; + mbVBasic = bVBasic; + mbHidden = bHidden; +} + +/** Encapsulates all data needed to process an entire function. */ +class XclExpFuncData +{ +public: + explicit XclExpFuncData( + const XclExpScToken& rTokData, + const XclFunctionInfo& rFuncInfo, + const XclExpExtFuncData& rExtFuncData ); + + const FormulaToken& GetScToken() const { return *mrTokData.mpScToken; } + OpCode GetOpCode() const { return mrFuncInfo.meOpCode; } + sal_uInt16 GetXclFuncIdx() const { return mrFuncInfo.mnXclFunc; } + bool IsVolatile() const { return mrFuncInfo.IsVolatile(); } + bool IsFixedParamCount() const { return mrFuncInfo.IsFixedParamCount(); } + bool IsAddInEquivalent() const { return mrFuncInfo.IsAddInEquivalent(); } + bool IsMacroFunc() const { return mrFuncInfo.IsMacroFunc(); } + sal_uInt8 GetSpaces() const { return mrTokData.mnSpaces; } + const XclExpExtFuncData& GetExtFuncData() const { return maExtFuncData; } + sal_uInt8 GetReturnClass() const { return mrFuncInfo.mnRetClass; } + + const XclFuncParamInfo& GetParamInfo() const; + bool IsCalcOnlyParam() const; + bool IsExcelOnlyParam() const; + void IncParamInfoIdx(); + + sal_uInt8 GetMinParamCount() const { return mrFuncInfo.mnMinParamCount; } + sal_uInt8 GetMaxParamCount() const { return mrFuncInfo.mnMaxParamCount; } + sal_uInt8 GetParamCount() const { return static_cast< sal_uInt8 >( mxOperands->size() ); } + void FinishParam( sal_uInt16 nTokPos ); + const XclExpOperandListRef& GetOperandList() const { return mxOperands; } + + ScfUInt16Vec& GetAttrPosVec() { return maAttrPosVec; } + void AppendAttrPos( sal_uInt16 nPos ) { maAttrPosVec.push_back( nPos ); } + +private: + ScfUInt16Vec maAttrPosVec; /// Token array positions of tAttr tokens. + const XclExpScToken& mrTokData; /// Data about processed function name token. + const XclFunctionInfo& mrFuncInfo; /// Constant data about processed function. + XclExpExtFuncData maExtFuncData; /// Data for external functions (macro, add-in). + XclExpOperandListRef mxOperands; /// Class conversion and position of all parameters. + const XclFuncParamInfo* mpParamInfo; /// Information for current parameter. +}; + +XclExpFuncData::XclExpFuncData( const XclExpScToken& rTokData, + const XclFunctionInfo& rFuncInfo, const XclExpExtFuncData& rExtFuncData ) : + mrTokData( rTokData ), + mrFuncInfo( rFuncInfo ), + maExtFuncData( rExtFuncData ), + mxOperands( std::make_shared<XclExpOperandList>() ), + mpParamInfo( rFuncInfo.mpParamInfos ) +{ + OSL_ENSURE( mrTokData.mpScToken, "XclExpFuncData::XclExpFuncData - missing core token" ); + // set name of an add-in function + if( (maExtFuncData.maFuncName.isEmpty()) && dynamic_cast< const FormulaExternalToken* >( mrTokData.mpScToken ) ) + maExtFuncData.Set( GetScToken().GetExternal(), true, false ); +} + +const XclFuncParamInfo& XclExpFuncData::GetParamInfo() const +{ + static const XclFuncParamInfo saInvalidInfo = { EXC_PARAM_NONE, EXC_PARAMCONV_ORG, false }; + return mpParamInfo ? *mpParamInfo : saInvalidInfo; +} + +bool XclExpFuncData::IsCalcOnlyParam() const +{ + return mpParamInfo && (mpParamInfo->meValid == EXC_PARAM_CALCONLY); +} + +bool XclExpFuncData::IsExcelOnlyParam() const +{ + return mpParamInfo && (mpParamInfo->meValid == EXC_PARAM_EXCELONLY); +} + +void XclExpFuncData::IncParamInfoIdx() +{ + if( !mpParamInfo ) + return; + + // move pointer to next entry, if something explicit follows + if( (o3tl::make_unsigned( mpParamInfo - mrFuncInfo.mpParamInfos + 1 ) < EXC_FUNCINFO_PARAMINFO_COUNT) && (mpParamInfo[ 1 ].meValid != EXC_PARAM_NONE) ) + ++mpParamInfo; + // if last parameter type is 'Excel-only' or 'Calc-only', do not repeat it + else if( IsExcelOnlyParam() || IsCalcOnlyParam() ) + mpParamInfo = nullptr; + // points to last info, but parameter pairs expected, move to previous info + else if( mrFuncInfo.IsParamPairs() ) + --mpParamInfo; + // otherwise: repeat last parameter class +} + +void XclExpFuncData::FinishParam( sal_uInt16 nTokPos ) +{ + // write token class conversion info for this parameter + const XclFuncParamInfo& rParamInfo = GetParamInfo(); + mxOperands->AppendOperand( nTokPos, rParamInfo.meConv, rParamInfo.mbValType ); + // move to next parameter info structure + IncParamInfoIdx(); +} + +// compiler configuration ----------------------------------------------------- + +/** Type of token class handling. */ +enum XclExpFmlaClassType +{ + EXC_CLASSTYPE_CELL, /// Cell formula, shared formula. + EXC_CLASSTYPE_ARRAY, /// Array formula, conditional formatting, data validation. + EXC_CLASSTYPE_NAME /// Defined name, range list. +}; + +/** Configuration data of the formula compiler. */ +struct XclExpCompConfig +{ + XclFormulaType meType; /// Type of the formula to be created. + XclExpFmlaClassType meClassType; /// Token class handling type. + bool mbLocalLinkMgr; /// True = local (per-sheet) link manager, false = global. + bool mbFromCell; /// True = Any kind of cell formula (cell, array, shared). + bool mb3DRefOnly; /// True = Only 3D references allowed (e.g. names). + bool mbAllowArrays; /// True = Allow inline arrays. +}; + +/** The table containing configuration data for all formula types. */ +const XclExpCompConfig spConfigTable[] = +{ + // formula type token class type lclLM inCell 3dOnly allowArray + { EXC_FMLATYPE_CELL, EXC_CLASSTYPE_CELL, true, true, false, true }, + { EXC_FMLATYPE_SHARED, EXC_CLASSTYPE_CELL, true, true, false, true }, + { EXC_FMLATYPE_MATRIX, EXC_CLASSTYPE_ARRAY, true, true, false, true }, + { EXC_FMLATYPE_CONDFMT, EXC_CLASSTYPE_ARRAY, true, false, false, false }, + { EXC_FMLATYPE_DATAVAL, EXC_CLASSTYPE_ARRAY, true, false, false, false }, + { EXC_FMLATYPE_NAME, EXC_CLASSTYPE_NAME, false, false, true, true }, + { EXC_FMLATYPE_CHART, EXC_CLASSTYPE_NAME, true, false, true, true }, + { EXC_FMLATYPE_CONTROL, EXC_CLASSTYPE_NAME, true, false, false, false }, + { EXC_FMLATYPE_WQUERY, EXC_CLASSTYPE_NAME, true, false, true, false }, + { EXC_FMLATYPE_LISTVAL, EXC_CLASSTYPE_NAME, true, false, false, false } +}; + +/** Working data of the formula compiler. Used to push onto a stack for recursive calls. */ +struct XclExpCompData +{ + typedef std::shared_ptr< ScTokenArray > ScTokenArrayRef; + + const XclExpCompConfig& mrCfg; /// Configuration for current formula type. + ScTokenArrayRef mxOwnScTokArr; /// Own clone of a Calc token array. + XclTokenArrayIterator maTokArrIt; /// Iterator in Calc token array. + XclExpLinkManager* mpLinkMgr; /// Link manager for current context (local/global). + XclExpRefLog* mpRefLog; /// Log for external references. + const ScAddress* mpScBasePos; /// Current cell position of the formula. + + ScfUInt8Vec maTokVec; /// Byte vector containing token data. + ScfUInt8Vec maExtDataVec; /// Byte vector containing extended data (arrays, stacked NLRs). + std::vector< XclExpOperandListRef > + maOpListVec; /// Formula structure, maps operators to their operands. + ScfUInt16Vec maOpPosStack; /// Stack with positions of operand tokens waiting for an operator. + bool mbStopAtSep; /// True = Stop subexpression creation at an ocSep token. + bool mbVolatile; /// True = Formula contains volatile function. + bool mbOk; /// Current state of the compiler. + + explicit XclExpCompData( const XclExpCompConfig* pCfg ); +}; + +XclExpCompData::XclExpCompData( const XclExpCompConfig* pCfg ) : + mrCfg( pCfg ? *pCfg : spConfigTable[ 0 ] ), + mpLinkMgr( nullptr ), + mpRefLog( nullptr ), + mpScBasePos( nullptr ), + mbStopAtSep( false ), + mbVolatile( false ), + mbOk( pCfg != nullptr ) +{ + OSL_ENSURE( pCfg, "XclExpFmlaCompImpl::Init - unknown formula type" ); +} + +} // namespace + +/** Implementation class of the export formula compiler. */ +class XclExpFmlaCompImpl : protected XclExpRoot, protected XclTokenArrayHelper +{ +public: + explicit XclExpFmlaCompImpl( const XclExpRoot& rRoot ); + + /** Creates an Excel token array from the passed Calc token array. */ + XclTokenArrayRef CreateFormula( + XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos = nullptr, XclExpRefLog* pRefLog = nullptr ); + /** Creates a single error token containing the passed error code. */ + XclTokenArrayRef CreateErrorFormula( sal_uInt8 nErrCode ); + /** Creates a single token for a special cell reference. */ + XclTokenArrayRef CreateSpecialRefFormula( sal_uInt8 nTokenId, const XclAddress& rXclPos ); + /** Creates a single tNameXR token for a reference to an external name. */ + XclTokenArrayRef CreateNameXFormula( sal_uInt16 nExtSheet, sal_uInt16 nExtName ); + + /** Returns true, if the passed formula type allows 3D references only. */ + bool Is3DRefOnly( XclFormulaType eType ) const; + + bool IsRef2D( const ScSingleRefData& rRefData, bool bCheck3DFlag ) const; + bool IsRef2D( const ScComplexRefData& rRefData, bool bCheck3DFlag ) const; + +private: + const XclExpCompConfig* GetConfigForType( XclFormulaType eType ) const; + sal_uInt16 GetSize() const { return static_cast< sal_uInt16 >( mxData->maTokVec.size() ); } + + void Init( XclFormulaType eType ); + void Init( XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos, XclExpRefLog* pRefLog ); + + void RecalcTokenClasses(); + void RecalcTokenClass( const XclExpTokenConvInfo& rConvInfo, XclFuncParamConv ePrevConv, XclExpClassConv ePrevClassConv, bool bWasRefClass ); + + void FinalizeFormula(); + XclTokenArrayRef CreateTokenArray(); + + // compiler --------------------------------------------------------------- + // XclExpScToken: pass-by-value and return-by-value is intended + + const FormulaToken* GetNextRawToken(); + const FormulaToken* PeekNextRawToken() const; + + bool GetNextToken( XclExpScToken& rTokData ); + XclExpScToken GetNextToken(); + + XclExpScToken Expression( XclExpScToken aTokData, bool bInParentheses, bool bStopAtSep ); + XclExpScToken SkipExpression( XclExpScToken aTokData, bool bStopAtSep ); + + XclExpScToken OrTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken AndTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken CompareTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken ConcatTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken AddSubTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken MulDivTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken PowTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken UnaryPostTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken UnaryPreTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken ListTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken IntersectTerm( XclExpScToken aTokData, bool& rbHasRefOp ); + XclExpScToken RangeTerm( XclExpScToken aTokData, bool& rbHasRefOp ); + XclExpScToken Factor( XclExpScToken aTokData ); + + // formula structure ------------------------------------------------------ + + void ProcessDouble( const XclExpScToken& rTokData ); + void ProcessString( const XclExpScToken& rTokData ); + void ProcessMissing( const XclExpScToken& rTokData ); + void ProcessBad( const XclExpScToken& rTokData ); + void ProcessParentheses( const XclExpScToken& rTokData ); + void ProcessBoolean( const XclExpScToken& rTokData ); + void ProcessDdeLink( const XclExpScToken& rTokData ); + void ProcessExternal( const XclExpScToken& rTokData ); + void ProcessMatrix( const XclExpScToken& rTokData ); + + void ProcessFunction( const XclExpScToken& rTokData ); + void PrepareFunction( const XclExpFuncData& rFuncData ); + void FinishFunction( XclExpFuncData& rFuncData, sal_uInt8 nCloseSpaces ); + void FinishIfFunction( XclExpFuncData& rFuncData ); + void FinishChooseFunction( XclExpFuncData& rFuncData ); + + XclExpScToken ProcessParam( XclExpScToken aTokData, XclExpFuncData& rFuncData ); + void PrepareParam( XclExpFuncData& rFuncData ); + void FinishParam( XclExpFuncData& rFuncData ); + void AppendDefaultParam( XclExpFuncData& rFuncData ); + void AppendTrailingParam( XclExpFuncData& rFuncData ); + + // reference handling ----------------------------------------------------- + + SCTAB GetScTab( const ScSingleRefData& rRefData ) const; + + void ConvertRefData( ScSingleRefData& rRefData, XclAddress& rXclPos, + bool bNatLangRef, bool bTruncMaxCol, bool bTruncMaxRow ) const; + void ConvertRefData( ScComplexRefData& rRefData, XclRange& rXclRange, + bool bNatLangRef ) const; + + XclExpRefLogEntry* GetNewRefLogEntry(); + void ProcessCellRef( const XclExpScToken& rTokData ); + void ProcessRangeRef( const XclExpScToken& rTokData ); + void ProcessExternalCellRef( const XclExpScToken& rTokData ); + void ProcessExternalRangeRef( const XclExpScToken& rTokData ); + void ProcessDefinedName( const XclExpScToken& rTokData ); + void ProcessExternalName( const XclExpScToken& rTokData ); + + // token vector ----------------------------------------------------------- + + void PushOperandPos( sal_uInt16 nTokPos ); + void PushOperatorPos( sal_uInt16 nTokPos, const XclExpOperandListRef& rxOperands ); + sal_uInt16 PopOperandPos(); + + void Append( sal_uInt8 nData ); + void Append( sal_uInt8 nData, size_t nCount ); + void Append( sal_uInt16 nData ); + void Append( sal_uInt32 nData ); + void Append( double fData ); + void Append( const OUString& rString ); + + void AppendAddress( const XclAddress& rXclPos ); + void AppendRange( const XclRange& rXclRange ); + + void AppendSpaceToken( sal_uInt8 nType, sal_uInt8 nCount ); + + void AppendOperandTokenId( sal_uInt8 nTokenId, sal_uInt8 nSpaces = 0 ); + void AppendIntToken( sal_uInt16 nValue, sal_uInt8 nSpaces = 0 ); + void AppendNumToken( double fValue, sal_uInt8 nSpaces = 0 ); + void AppendBoolToken( bool bValue, sal_uInt8 nSpaces = 0 ); + void AppendErrorToken( sal_uInt8 nErrCode, sal_uInt8 nSpaces = 0 ); + void AppendMissingToken( sal_uInt8 nSpaces = 0 ); + void AppendNameToken( sal_uInt16 nNameIdx, sal_uInt8 nSpaces = 0 ); + void AppendMissingNameToken( const OUString& rName, sal_uInt8 nSpaces = 0 ); + void AppendNameXToken( sal_uInt16 nExtSheet, sal_uInt16 nExtName, sal_uInt8 nSpaces = 0 ); + void AppendMacroCallToken( const XclExpExtFuncData& rExtFuncData ); + void AppendAddInCallToken( const XclExpExtFuncData& rExtFuncData ); + void AppendEuroToolCallToken( const XclExpExtFuncData& rExtFuncData ); + + void AppendOperatorTokenId( sal_uInt8 nTokenId, const XclExpOperandListRef& rxOperands, sal_uInt8 nSpaces = 0 ); + void AppendUnaryOperatorToken( sal_uInt8 nTokenId, sal_uInt8 nSpaces = 0 ); + void AppendBinaryOperatorToken( sal_uInt8 nTokenId, bool bValType, sal_uInt8 nSpaces = 0 ); + void AppendLogicalOperatorToken( sal_uInt16 nXclFuncIdx, sal_uInt8 nOpCount ); + void AppendFuncToken( const XclExpFuncData& rFuncData ); + + void AppendParenToken( sal_uInt8 nOpenSpaces = 0, sal_uInt8 nCloseSpaces = 0 ); + void AppendJumpToken( XclExpFuncData& rFuncData, sal_uInt8 nAttrType ); + + void InsertZeros( sal_uInt16 nInsertPos, sal_uInt16 nInsertSize ); + void Overwrite( sal_uInt16 nWriteToPos, sal_uInt16 nOffset ); + + void UpdateAttrGoto( sal_uInt16 nAttrPos ); + + bool IsSpaceToken( sal_uInt16 nPos ) const; + void RemoveTrailingParen(); + + void AppendExt( sal_uInt8 nData ); + void AppendExt( sal_uInt8 nData, size_t nCount ); + void AppendExt( sal_uInt16 nData ); + void AppendExt( double fData ); + void AppendExt( const OUString& rString ); + +private: + typedef std::map< XclFormulaType, XclExpCompConfig > XclExpCompConfigMap; + typedef std::shared_ptr< XclExpCompData > XclExpCompDataRef; + + XclExpCompConfigMap maCfgMap; /// Compiler configuration map for all formula types. + XclFunctionProvider maFuncProv; /// Excel function data provider. + XclExpCompDataRef mxData; /// Working data for current formula. + std::vector< XclExpCompDataRef > + maDataStack; /// Stack for working data, when compiler is called recursively. + const XclBiff meBiff; /// Cached BIFF version to save GetBiff() calls. + const SCCOL mnMaxAbsCol; /// Maximum column index. + const SCROW mnMaxAbsRow; /// Maximum row index. + const SCCOL mnMaxScCol; /// Maximum column index in Calc itself. + const SCROW mnMaxScRow; /// Maximum row index in Calc itself. + const sal_uInt16 mnMaxColMask; /// Mask to delete invalid bits in column fields. + const sal_uInt32 mnMaxRowMask; /// Mask to delete invalid bits in row fields. +}; + +XclExpFmlaCompImpl::XclExpFmlaCompImpl( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maFuncProv( rRoot ), + meBiff( rRoot.GetBiff() ), + mnMaxAbsCol( rRoot.GetXclMaxPos().Col() ), + mnMaxAbsRow( rRoot.GetXclMaxPos().Row() ), + mnMaxScCol( rRoot.GetScMaxPos().Col() ), + mnMaxScRow( rRoot.GetScMaxPos().Row() ), + mnMaxColMask( static_cast< sal_uInt16 >( rRoot.GetXclMaxPos().Col() ) ), + mnMaxRowMask( static_cast< sal_uInt32 >( rRoot.GetXclMaxPos().Row() ) ) +{ + // build the configuration map + for(auto const &rEntry : spConfigTable) + maCfgMap[ rEntry.meType ] = rEntry; +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateFormula( XclFormulaType eType, + const ScTokenArray& rScTokArr, const ScAddress* pScBasePos, XclExpRefLog* pRefLog ) +{ + // initialize the compiler + Init( eType, rScTokArr, pScBasePos, pRefLog ); + + // start compilation, if initialization didn't fail + if( mxData->mbOk ) + { + XclExpScToken aTokData( GetNextToken() ); + FormulaError nScError = rScTokArr.GetCodeError(); + if( (nScError != FormulaError::NONE) && (!aTokData.Is() || (aTokData.GetOpCode() == ocStop)) ) + { + // #i50253# convert simple ocStop token to error code formula (e.g. =#VALUE!) + AppendErrorToken( XclTools::GetXclErrorCode( nScError ), aTokData.mnSpaces ); + } + else if( aTokData.Is() ) + { + aTokData = Expression( aTokData, false, false ); + } + else + { + OSL_FAIL( "XclExpFmlaCompImpl::CreateFormula - empty token array" ); + mxData->mbOk = false; + } + + if( mxData->mbOk ) + { + // #i44907# auto-generated SUBTOTAL formula cells have trailing ocStop token + mxData->mbOk = !aTokData.Is() || (aTokData.GetOpCode() == ocStop); + OSL_ENSURE( mxData->mbOk, "XclExpFmlaCompImpl::CreateFormula - unknown garbage behind formula" ); + } + } + + // finalize (add tAttrVolatile token, calculate all token classes) + RecalcTokenClasses(); + FinalizeFormula(); + + // leave recursive call, create and return the final token array + return CreateTokenArray(); +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateErrorFormula( sal_uInt8 nErrCode ) +{ + Init( EXC_FMLATYPE_NAME ); + AppendErrorToken( nErrCode ); + return CreateTokenArray(); +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateSpecialRefFormula( sal_uInt8 nTokenId, const XclAddress& rXclPos ) +{ + Init( EXC_FMLATYPE_NAME ); + AppendOperandTokenId( nTokenId ); + Append( static_cast<sal_uInt16>(rXclPos.mnRow) ); + Append( rXclPos.mnCol ); // do not use AppendAddress(), we always need 16-bit column here + return CreateTokenArray(); +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateNameXFormula( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) +{ + Init( EXC_FMLATYPE_NAME ); + AppendNameXToken( nExtSheet, nExtName ); + return CreateTokenArray(); +} + +bool XclExpFmlaCompImpl::Is3DRefOnly( XclFormulaType eType ) const +{ + const XclExpCompConfig* pCfg = GetConfigForType( eType ); + return pCfg && pCfg->mb3DRefOnly; +} + +// private -------------------------------------------------------------------- + +const XclExpCompConfig* XclExpFmlaCompImpl::GetConfigForType( XclFormulaType eType ) const +{ + XclExpCompConfigMap::const_iterator aIt = maCfgMap.find( eType ); + OSL_ENSURE( aIt != maCfgMap.end(), "XclExpFmlaCompImpl::GetConfigForType - unknown formula type" ); + return (aIt == maCfgMap.end()) ? nullptr : &aIt->second; +} + +void XclExpFmlaCompImpl::Init( XclFormulaType eType ) +{ + // compiler invoked recursively? - store old working data + if( mxData ) + maDataStack.push_back( mxData ); + // new compiler working data structure + mxData = std::make_shared<XclExpCompData>( GetConfigForType( eType ) ); +} + +void XclExpFmlaCompImpl::Init( XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos, XclExpRefLog* pRefLog ) +{ + // common initialization + Init( eType ); + + // special initialization + if( mxData->mbOk ) switch( mxData->mrCfg.meType ) + { + case EXC_FMLATYPE_CELL: + case EXC_FMLATYPE_MATRIX: + case EXC_FMLATYPE_CHART: + mxData->mbOk = pScBasePos != nullptr; + OSL_ENSURE( mxData->mbOk, "XclExpFmlaCompImpl::Init - missing cell address" ); + mxData->mpScBasePos = pScBasePos; + break; + case EXC_FMLATYPE_SHARED: + mxData->mbOk = pScBasePos != nullptr; + assert(mxData->mbOk && "XclExpFmlaCompImpl::Init - missing cell address"); + if (mxData->mbOk) + { + // clone the passed token array, convert references relative to current cell position + mxData->mxOwnScTokArr = rScTokArr.Clone(); + ScCompiler::MoveRelWrap( *mxData->mxOwnScTokArr, GetDoc(), *pScBasePos, GetDoc().MaxCol(), GetDoc().MaxRow() ); + // don't remember pScBasePos in mxData->mpScBasePos, shared formulas use real relative refs + } + break; + default:; + } + + if( mxData->mbOk ) + { + // link manager to be used + mxData->mpLinkMgr = mxData->mrCfg.mbLocalLinkMgr ? &GetLocalLinkManager() : &GetGlobalLinkManager(); + + // token array iterator (use cloned token array if present) + mxData->maTokArrIt.Init( mxData->mxOwnScTokArr ? *mxData->mxOwnScTokArr : rScTokArr, false ); + mxData->mpRefLog = pRefLog; + // Only for OOXML + if (GetOutput() == EXC_OUTPUT_XML_2007) + mxData->mpScBasePos = pScBasePos; + } +} + +void XclExpFmlaCompImpl::RecalcTokenClasses() +{ + if( !mxData->mbOk ) + return; + + mxData->mbOk = mxData->maOpPosStack.size() == 1; + OSL_ENSURE( mxData->mbOk, "XclExpFmlaCompImpl::RecalcTokenClasses - position of root token expected on stack" ); + if( mxData->mbOk ) + { + /* Cell and array formulas start with VAL conversion and VALTYPE + parameter type, defined names start with ARR conversion and + REFTYPE parameter type for the root token. */ + bool bNameFmla = mxData->mrCfg.meClassType == EXC_CLASSTYPE_NAME; + XclFuncParamConv eParamConv = bNameFmla ? EXC_PARAMCONV_ARR : EXC_PARAMCONV_VAL; + XclExpClassConv eClassConv = bNameFmla ? EXC_CLASSCONV_ARR : EXC_CLASSCONV_VAL; + XclExpTokenConvInfo aConvInfo = { PopOperandPos(), eParamConv, !bNameFmla }; + RecalcTokenClass( aConvInfo, eParamConv, eClassConv, bNameFmla ); + } + + // clear operand vectors (calls to the expensive InsertZeros() may follow) + mxData->maOpListVec.clear(); + mxData->maOpPosStack.clear(); +} + +void XclExpFmlaCompImpl::RecalcTokenClass( const XclExpTokenConvInfo& rConvInfo, + XclFuncParamConv ePrevConv, XclExpClassConv ePrevClassConv, bool bWasRefClass ) +{ + OSL_ENSURE( rConvInfo.mnTokPos < GetSize(), "XclExpFmlaCompImpl::RecalcTokenClass - invalid token position" ); + sal_uInt8& rnTokenId = mxData->maTokVec[ rConvInfo.mnTokPos ]; + sal_uInt8 nTokClass = GetTokenClass( rnTokenId ); + + // REF tokens in VALTYPE parameters behave like VAL tokens + if( rConvInfo.mbValType && (nTokClass == EXC_TOKCLASS_REF) ) + { + nTokClass = EXC_TOKCLASS_VAL; + ChangeTokenClass( rnTokenId, nTokClass ); + } + + // replace RPO conversion of operator with parent conversion + XclFuncParamConv eConv = (rConvInfo.meConv == EXC_PARAMCONV_RPO) ? ePrevConv : rConvInfo.meConv; + + // find the effective token class conversion to be performed for this token + XclExpClassConv eClassConv = EXC_CLASSCONV_ORG; + switch( eConv ) + { + case EXC_PARAMCONV_ORG: + // conversion is forced independent of parent conversion + eClassConv = EXC_CLASSCONV_ORG; + break; + case EXC_PARAMCONV_VAL: + // conversion is forced independent of parent conversion + eClassConv = EXC_CLASSCONV_VAL; + break; + case EXC_PARAMCONV_ARR: + // conversion is forced independent of parent conversion + eClassConv = EXC_CLASSCONV_ARR; + break; + case EXC_PARAMCONV_RPT: + switch( ePrevConv ) + { + case EXC_PARAMCONV_ORG: + case EXC_PARAMCONV_VAL: + case EXC_PARAMCONV_ARR: + /* If parent token has REF class (REF token in REFTYPE + function parameter), then RPT does not repeat the + previous explicit ORG or ARR conversion, but always + falls back to VAL conversion. */ + eClassConv = bWasRefClass ? EXC_CLASSCONV_VAL : ePrevClassConv; + break; + case EXC_PARAMCONV_RPT: + // nested RPT repeats the previous effective conversion + eClassConv = ePrevClassConv; + break; + case EXC_PARAMCONV_RPX: + /* If parent token has REF class (REF token in REFTYPE + function parameter), then RPX repeats the previous + effective conversion (which will be either ORG or ARR, + but never VAL), otherwise falls back to ORG conversion. */ + eClassConv = bWasRefClass ? ePrevClassConv : EXC_CLASSCONV_ORG; + break; + case EXC_PARAMCONV_RPO: // does not occur + break; + } + break; + case EXC_PARAMCONV_RPX: + /* If current token still has REF class, set previous effective + conversion as current conversion. This will not have an effect + on the REF token but is needed for RPT parameters of this + function that want to repeat this conversion type. If current + token is VAL or ARR class, the previous ARR conversion will be + repeated on the token, but VAL conversion will not. */ + eClassConv = ((nTokClass == EXC_TOKCLASS_REF) || (ePrevClassConv == EXC_CLASSCONV_ARR)) ? + ePrevClassConv : EXC_CLASSCONV_ORG; + break; + case EXC_PARAMCONV_RPO: // does not occur (see above) + break; + } + + // do the token class conversion + switch( eClassConv ) + { + case EXC_CLASSCONV_ORG: + /* Cell formulas: leave the current token class. Cell formulas + are the only type of formulas where all tokens can keep + their original token class. + Array and defined name formulas: convert VAL to ARR. */ + if( (mxData->mrCfg.meClassType != EXC_CLASSTYPE_CELL) && (nTokClass == EXC_TOKCLASS_VAL) ) + { + nTokClass = EXC_TOKCLASS_ARR; + ChangeTokenClass( rnTokenId, nTokClass ); + } + break; + case EXC_CLASSCONV_VAL: + // convert ARR to VAL + if( nTokClass == EXC_TOKCLASS_ARR ) + { + nTokClass = EXC_TOKCLASS_VAL; + ChangeTokenClass( rnTokenId, nTokClass ); + } + break; + case EXC_CLASSCONV_ARR: + // convert VAL to ARR + if( nTokClass == EXC_TOKCLASS_VAL ) + { + nTokClass = EXC_TOKCLASS_ARR; + ChangeTokenClass( rnTokenId, nTokClass ); + } + break; + } + + // do conversion for nested operands, if token is an operator or function + if( rConvInfo.mnTokPos < mxData->maOpListVec.size() ) + if( const XclExpOperandList* pOperands = mxData->maOpListVec[ rConvInfo.mnTokPos ].get() ) + for( const auto& rOperand : *pOperands ) + RecalcTokenClass( rOperand, eConv, eClassConv, nTokClass == EXC_TOKCLASS_REF ); +} + +void XclExpFmlaCompImpl::FinalizeFormula() +{ + if( mxData->mbOk ) + { + // Volatile? Add a tAttrVolatile token at the beginning of the token array. + if( mxData->mbVolatile ) + { + // tAttrSpace token can be extended with volatile flag + if( !IsSpaceToken( 0 ) ) + { + InsertZeros( 0, 4 ); + mxData->maTokVec[ 0 ] = EXC_TOKID_ATTR; + } + mxData->maTokVec[ 1 ] |= EXC_TOK_ATTR_VOLATILE; + } + + // Token array too long? -> error + mxData->mbOk = mxData->maTokVec.size() <= EXC_TOKARR_MAXLEN; + } + + if( !mxData->mbOk ) + { + // Any unrecoverable error? -> Create a =#NA formula. + mxData->maTokVec.clear(); + mxData->maExtDataVec.clear(); + mxData->mbVolatile = false; + AppendErrorToken( EXC_ERR_NA ); + } +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateTokenArray() +{ + // create the Excel token array from working data before resetting mxData + OSL_ENSURE( mxData->mrCfg.mbAllowArrays || mxData->maExtDataVec.empty(), "XclExpFmlaCompImpl::CreateTokenArray - unexpected extended data" ); + if( !mxData->mrCfg.mbAllowArrays ) + mxData->maExtDataVec.clear(); + XclTokenArrayRef xTokArr = std::make_shared<XclTokenArray>( mxData->maTokVec, mxData->maExtDataVec, mxData->mbVolatile ); + mxData.reset(); + + // compiler invoked recursively? - restore old working data + if( !maDataStack.empty() ) + { + mxData = maDataStack.back(); + maDataStack.pop_back(); + } + + return xTokArr; +} + +// compiler ------------------------------------------------------------------- + +const FormulaToken* XclExpFmlaCompImpl::GetNextRawToken() +{ + const FormulaToken* pScToken = mxData->maTokArrIt.Get(); + ++mxData->maTokArrIt; + return pScToken; +} + +const FormulaToken* XclExpFmlaCompImpl::PeekNextRawToken() const +{ + /* Returns pointer to next raw token in the token array. The token array + iterator already points to the next token (A call to GetNextToken() + always increases the iterator), so this function just returns the token + the iterator points to. To skip space tokens, a copy of the iterator is + created and set to the passed skip-spaces mode. If spaces have to be + skipped, and the iterator currently points to a space token, the + constructor will move it to the next non-space token. */ + XclTokenArrayIterator aTempIt( mxData->maTokArrIt, true/*bSkipSpaces*/ ); + return aTempIt.Get(); +} + +bool XclExpFmlaCompImpl::GetNextToken( XclExpScToken& rTokData ) +{ + rTokData.mpScToken = GetNextRawToken(); + rTokData.mnSpaces = 0; + /* TODO: handle ocWhitespace characters? */ + while (rTokData.GetOpCode() == ocSpaces || rTokData.GetOpCode() == ocWhitespace) + { + rTokData.mnSpaces += rTokData.mpScToken->GetByte(); + rTokData.mpScToken = GetNextRawToken(); + } + return rTokData.Is(); +} + +XclExpScToken XclExpFmlaCompImpl::GetNextToken() +{ + XclExpScToken aTokData; + GetNextToken( aTokData ); + return aTokData; +} + +namespace { + +/** Returns the Excel token ID of a comparison operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetCompareTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocLess: return EXC_TOKID_LT; + case ocLessEqual: return EXC_TOKID_LE; + case ocEqual: return EXC_TOKID_EQ; + case ocGreaterEqual: return EXC_TOKID_GE; + case ocGreater: return EXC_TOKID_GT; + case ocNotEqual: return EXC_TOKID_NE; + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a string concatenation operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetConcatTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocAmpersand) ? EXC_TOKID_CONCAT : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of an addition/subtraction operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetAddSubTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocAdd: return EXC_TOKID_ADD; + case ocSub: return EXC_TOKID_SUB; + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a multiplication/division operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetMulDivTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocMul: return EXC_TOKID_MUL; + case ocDiv: return EXC_TOKID_DIV; + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a power operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetPowTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocPow) ? EXC_TOKID_POWER : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a trailing unary operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetUnaryPostTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocPercentSign) ? EXC_TOKID_PERCENT : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a leading unary operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetUnaryPreTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocAdd: return EXC_TOKID_UPLUS; // +(1) + case ocNeg: return EXC_TOKID_UMINUS; // NEG(1) + case ocNegSub: return EXC_TOKID_UMINUS; // -(1) + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a reference list operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetListTokenId( OpCode eOpCode, bool bStopAtSep ) +{ + return ((eOpCode == ocUnion) || (!bStopAtSep && (eOpCode == ocSep))) ? EXC_TOKID_LIST : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a reference intersection operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetIntersectTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocIntersect) ? EXC_TOKID_ISECT : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a reference range operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetRangeTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocRange) ? EXC_TOKID_RANGE : EXC_TOKID_NONE; +} + +} // namespace + +XclExpScToken XclExpFmlaCompImpl::Expression( XclExpScToken aTokData, bool bInParentheses, bool bStopAtSep ) +{ + if( mxData->mbOk && aTokData.Is() ) + { + // remember old stop-at-ocSep mode, restored below + bool bOldStopAtSep = mxData->mbStopAtSep; + mxData->mbStopAtSep = bStopAtSep; + // start compilation of the subexpression + aTokData = OrTerm( aTokData, bInParentheses ); + // restore old stop-at-ocSep mode + mxData->mbStopAtSep = bOldStopAtSep; + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::SkipExpression( XclExpScToken aTokData, bool bStopAtSep ) +{ + while( mxData->mbOk && aTokData.Is() && (aTokData.GetOpCode() != ocClose) && (!bStopAtSep || (aTokData.GetOpCode() != ocSep)) ) + { + if( aTokData.GetOpCode() == ocOpen ) + { + aTokData = SkipExpression( GetNextToken(), false ); + if( mxData->mbOk ) mxData->mbOk = aTokData.GetOpCode() == ocClose; + } + aTokData = GetNextToken(); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::OrTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = AndTerm( aTokData, bInParentheses ); + sal_uInt8 nParamCount = 1; + while( mxData->mbOk && (aTokData.GetOpCode() == ocOr) ) + { + RemoveTrailingParen(); + aTokData = AndTerm( GetNextToken(), bInParentheses ); + RemoveTrailingParen(); + ++nParamCount; + if( mxData->mbOk ) mxData->mbOk = nParamCount <= EXC_FUNC_MAXPARAM; + } + if( mxData->mbOk && (nParamCount > 1) ) + AppendLogicalOperatorToken( EXC_FUNCID_OR, nParamCount ); + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::AndTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = CompareTerm( aTokData, bInParentheses ); + sal_uInt8 nParamCount = 1; + while( mxData->mbOk && (aTokData.GetOpCode() == ocAnd) ) + { + RemoveTrailingParen(); + aTokData = CompareTerm( GetNextToken(), bInParentheses ); + RemoveTrailingParen(); + ++nParamCount; + if( mxData->mbOk ) mxData->mbOk = nParamCount <= EXC_FUNC_MAXPARAM; + } + if( mxData->mbOk && (nParamCount > 1) ) + AppendLogicalOperatorToken( EXC_FUNCID_AND, nParamCount ); + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::CompareTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = ConcatTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetConcatTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = ConcatTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::ConcatTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = AddSubTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetCompareTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = AddSubTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::AddSubTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = MulDivTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetAddSubTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = MulDivTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::MulDivTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = PowTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetMulDivTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = PowTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::PowTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = UnaryPostTerm( aTokData, bInParentheses ); + sal_uInt8 nOpTokenId = EXC_TOKID_NONE; + while( mxData->mbOk ) + { + nOpTokenId = lclGetPowTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = UnaryPostTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::UnaryPostTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = UnaryPreTerm( aTokData, bInParentheses ); + sal_uInt8 nOpTokenId = EXC_TOKID_NONE; + while( mxData->mbOk ) + { + nOpTokenId = lclGetUnaryPostTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + AppendUnaryOperatorToken( nOpTokenId, aTokData.mnSpaces ); + GetNextToken( aTokData ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::UnaryPreTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + sal_uInt8 nOpTokenId = mxData->mbOk ? lclGetUnaryPreTokenId( aTokData.GetOpCode() ) : EXC_TOKID_NONE; + if( nOpTokenId != EXC_TOKID_NONE ) + { + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = UnaryPreTerm( GetNextToken(), bInParentheses ); + AppendUnaryOperatorToken( nOpTokenId, nSpaces ); + } + else + { + aTokData = ListTerm( aTokData, bInParentheses ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::ListTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + sal_uInt16 nSubExprPos = GetSize(); + bool bHasAnyRefOp = false; + bool bHasListOp = false; + aTokData = IntersectTerm( aTokData, bHasAnyRefOp ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetListTokenId( aTokData.GetOpCode(), mxData->mbStopAtSep ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = IntersectTerm( GetNextToken(), bHasAnyRefOp ); + AppendBinaryOperatorToken( nOpTokenId, false, nSpaces ); + bHasAnyRefOp = bHasListOp = true; + } + if( bHasAnyRefOp ) + { + // add a tMemFunc token enclosing the entire reference subexpression + sal_uInt16 nSubExprSize = GetSize() - nSubExprPos; + InsertZeros( nSubExprPos, 3 ); + mxData->maTokVec[ nSubExprPos ] = GetTokenId( EXC_TOKID_MEMFUNC, EXC_TOKCLASS_REF ); + Overwrite( nSubExprPos + 1, nSubExprSize ); + // update the operand/operator stack (set the list expression as operand of the tMemFunc) + XclExpOperandListRef xOperands = std::make_shared<XclExpOperandList>(); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_VAL, false ); + PushOperatorPos( nSubExprPos, xOperands ); + } + // #i86439# enclose list operator into parentheses, e.g. Calc's =AREAS(A1~A2) to Excel's =AREAS((A1;A2)) + if( bHasListOp && !bInParentheses ) + AppendParenToken(); + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::IntersectTerm( XclExpScToken aTokData, bool& rbHasRefOp ) +{ + aTokData = RangeTerm( aTokData, rbHasRefOp ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetIntersectTokenId( aTokData.GetOpCode() ); + if (nOpTokenId ==EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = RangeTerm( GetNextToken(), rbHasRefOp ); + AppendBinaryOperatorToken( nOpTokenId, false, nSpaces ); + rbHasRefOp = true; + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::RangeTerm( XclExpScToken aTokData, bool& rbHasRefOp ) +{ + aTokData = Factor( aTokData ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetRangeTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = Factor( GetNextToken() ); + AppendBinaryOperatorToken( nOpTokenId, false, nSpaces ); + rbHasRefOp = true; + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::Factor( XclExpScToken aTokData ) +{ + if( !mxData->mbOk || !aTokData.Is() ) return XclExpScToken(); + + switch( aTokData.GetType() ) + { + case svUnknown: mxData->mbOk = false; break; + case svDouble: ProcessDouble( aTokData ); break; + case svString: ProcessString( aTokData ); break; + case svSingleRef: ProcessCellRef( aTokData ); break; + case svDoubleRef: ProcessRangeRef( aTokData ); break; + case svExternalSingleRef: ProcessExternalCellRef( aTokData ); break; + case svExternalDoubleRef: ProcessExternalRangeRef( aTokData ); break; + case svExternalName: ProcessExternalName( aTokData ); break; + case svMatrix: ProcessMatrix( aTokData ); break; + case svExternal: ProcessExternal( aTokData ); break; + + default: switch( aTokData.GetOpCode() ) + { + case ocNone: /* do nothing */ break; + case ocMissing: ProcessMissing( aTokData ); break; + case ocBad: ProcessBad( aTokData ); break; + case ocOpen: ProcessParentheses( aTokData ); break; + case ocName: ProcessDefinedName( aTokData ); break; + case ocFalse: + case ocTrue: ProcessBoolean( aTokData ); break; + case ocDde: ProcessDdeLink( aTokData ); break; + default: ProcessFunction( aTokData ); + } + } + + return GetNextToken(); +} + +// formula structure ---------------------------------------------------------- + +void XclExpFmlaCompImpl::ProcessDouble( const XclExpScToken& rTokData ) +{ + double fValue = rTokData.mpScToken->GetDouble(); + double fInt; + double fFrac = modf( fValue, &fInt ); + if( (fFrac == 0.0) && (0.0 <= fInt) && (fInt <= 65535.0) ) + AppendIntToken( static_cast< sal_uInt16 >( fInt ), rTokData.mnSpaces ); + else + AppendNumToken( fValue, rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessString( const XclExpScToken& rTokData ) +{ + AppendOperandTokenId( EXC_TOKID_STR, rTokData.mnSpaces ); + Append( rTokData.mpScToken->GetString().getString() ); +} + +void XclExpFmlaCompImpl::ProcessMissing( const XclExpScToken& rTokData ) +{ + AppendMissingToken( rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessBad( const XclExpScToken& rTokData ) +{ + AppendErrorToken( EXC_ERR_NA, rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessParentheses( const XclExpScToken& rTokData ) +{ + XclExpScToken aTokData = Expression( GetNextToken(), true, false ); + mxData->mbOk = aTokData.GetOpCode() == ocClose; + AppendParenToken( rTokData.mnSpaces, aTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessBoolean( const XclExpScToken& rTokData ) +{ + mxData->mbOk = GetNextToken().GetOpCode() == ocOpen; + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocClose; + if( mxData->mbOk ) + AppendBoolToken( rTokData.GetOpCode() == ocTrue, rTokData.mnSpaces ); +} + +namespace { + +bool lclGetTokenString( OUString& rString, const XclExpScToken& rTokData ) +{ + bool bIsStr = (rTokData.GetType() == svString) && (rTokData.GetOpCode() == ocPush); + if( bIsStr ) + rString = rTokData.mpScToken->GetString().getString(); + return bIsStr; +} + +} // namespace + +void XclExpFmlaCompImpl::ProcessDdeLink( const XclExpScToken& rTokData ) +{ + OUString aApplic, aTopic, aItem; + + mxData->mbOk = GetNextToken().GetOpCode() == ocOpen; + if( mxData->mbOk ) mxData->mbOk = lclGetTokenString( aApplic, GetNextToken() ); + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocSep; + if( mxData->mbOk ) mxData->mbOk = lclGetTokenString( aTopic, GetNextToken() ); + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocSep; + if( mxData->mbOk ) mxData->mbOk = lclGetTokenString( aItem, GetNextToken() ); + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocClose; + if( mxData->mbOk ) mxData->mbOk = !aApplic.isEmpty() && !aTopic.isEmpty() && !aItem.isEmpty(); + if( mxData->mbOk ) + { + sal_uInt16 nExtSheet(0), nExtName(0); + if( mxData->mpLinkMgr && mxData->mpLinkMgr->InsertDde( nExtSheet, nExtName, aApplic, aTopic, aItem ) ) + AppendNameXToken( nExtSheet, nExtName, rTokData.mnSpaces ); + else + AppendErrorToken( EXC_ERR_NA, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessExternal( const XclExpScToken& rTokData ) +{ + /* #i47228# Excel import generates svExternal/ocMacro tokens for invalid + names and for external/invalid function calls. This function looks for + the next token in the token array. If it is an opening parenthesis, the + token is processed as external function call, otherwise as undefined name. */ + const FormulaToken* pNextScToken = PeekNextRawToken(); + if( !pNextScToken || (pNextScToken->GetOpCode() != ocOpen) ) + AppendMissingNameToken( rTokData.mpScToken->GetExternal(), rTokData.mnSpaces ); + else + ProcessFunction( rTokData ); +} + +void XclExpFmlaCompImpl::ProcessMatrix( const XclExpScToken& rTokData ) +{ + const ScMatrix* pMatrix = rTokData.mpScToken->GetMatrix(); + if( pMatrix && mxData->mrCfg.mbAllowArrays ) + { + SCSIZE nScCols, nScRows; + pMatrix->GetDimensions( nScCols, nScRows ); + OSL_ENSURE( (nScCols > 0) && (nScRows > 0), "XclExpFmlaCompImpl::ProcessMatrix - invalid matrix size" ); + sal_uInt16 nCols = ::limit_cast< sal_uInt16 >( nScCols, 0, 256 ); + sal_uInt16 nRows = ::limit_cast< sal_uInt16 >( nScRows, 0, 1024 ); + + // create the tArray token + AppendOperandTokenId( GetTokenId( EXC_TOKID_ARRAY, EXC_TOKCLASS_ARR ), rTokData.mnSpaces ); + Append( static_cast< sal_uInt8 >( (meBiff == EXC_BIFF8) ? (nCols - 1) : nCols ) ); + Append( static_cast< sal_uInt16 >( (meBiff == EXC_BIFF8) ? (nRows - 1) : nRows ) ); + Append( static_cast< sal_uInt32 >( 0 ) ); + + // create the extended data containing the array values + AppendExt( static_cast< sal_uInt8 >( (meBiff == EXC_BIFF8) ? (nCols - 1) : nCols ) ); + AppendExt( static_cast< sal_uInt16 >( (meBiff == EXC_BIFF8) ? (nRows - 1) : nRows ) ); + for( SCSIZE nScRow = 0; nScRow < nScRows; ++nScRow ) + { + for( SCSIZE nScCol = 0; nScCol < nScCols; ++nScCol ) + { + ScMatrixValue nMatVal = pMatrix->Get( nScCol, nScRow ); + if( ScMatrix::IsValueType( nMatVal.nType ) ) // value, boolean, or error + { + FormulaError nErr; + if( ScMatrix::IsBooleanType( nMatVal.nType ) ) + { + AppendExt( EXC_CACHEDVAL_BOOL ); + AppendExt( static_cast< sal_uInt8 >( nMatVal.GetBoolean() ? 1 : 0 ) ); + AppendExt( 0, 7 ); + } + else if( (nErr = nMatVal.GetError()) != FormulaError::NONE ) + { + AppendExt( EXC_CACHEDVAL_ERROR ); + AppendExt( XclTools::GetXclErrorCode( nErr ) ); + AppendExt( 0, 7 ); + } + else + { + AppendExt( EXC_CACHEDVAL_DOUBLE ); + AppendExt( nMatVal.fVal ); + } + } + else // string or empty + { + const OUString aStr( nMatVal.GetString().getString()); + if( aStr.isEmpty() ) + { + AppendExt( EXC_CACHEDVAL_EMPTY ); + AppendExt( 0, 8 ); + } + else + { + AppendExt( EXC_CACHEDVAL_STRING ); + AppendExt( aStr ); + } + } + } + } + } + else + { + // array in places that do not allow it (cond fmts, data validation) + AppendErrorToken( EXC_ERR_NA, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessFunction( const XclExpScToken& rTokData ) +{ + OpCode eOpCode = rTokData.GetOpCode(); + const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromOpCode( eOpCode ); + + XclExpExtFuncData aExtFuncData; + + // no exportable function found - try to create an external macro call + if( !pFuncInfo && (eOpCode >= SC_OPCODE_START_NO_PAR) ) + { + const OUString& rFuncName = ScCompiler::GetNativeSymbol( eOpCode ); + if( !rFuncName.isEmpty() ) + { + aExtFuncData.Set( rFuncName, true, false ); + pFuncInfo = maFuncProv.GetFuncInfoFromOpCode( ocMacro ); + } + } + + mxData->mbOk = pFuncInfo != nullptr; + if( !mxData->mbOk ) return; + + // internal functions equivalent to an existing add-in + if( pFuncInfo->IsAddInEquivalent() ) + aExtFuncData.Set( pFuncInfo->GetAddInEquivalentFuncName(), true, false ); + // functions simulated by a macro call in file format + else if( pFuncInfo->IsMacroFunc() ) + aExtFuncData.Set( pFuncInfo->GetMacroFuncName(), false, true ); + + XclExpFuncData aFuncData( rTokData, *pFuncInfo, aExtFuncData ); + XclExpScToken aTokData; + + // preparations for special functions, before function processing starts + PrepareFunction( aFuncData ); + + enum { STATE_START, STATE_OPEN, STATE_PARAM, STATE_SEP, STATE_CLOSE, STATE_END } + eState = STATE_START; + while( eState != STATE_END ) switch( eState ) + { + case STATE_START: + mxData->mbOk = GetNextToken( aTokData ) && (aTokData.GetOpCode() == ocOpen); + eState = mxData->mbOk ? STATE_OPEN : STATE_END; + break; + case STATE_OPEN: + mxData->mbOk = GetNextToken( aTokData ); + eState = mxData->mbOk ? ((aTokData.GetOpCode() == ocClose) ? STATE_CLOSE : STATE_PARAM) : STATE_END; + break; + case STATE_PARAM: + aTokData = ProcessParam( aTokData, aFuncData ); + switch( aTokData.GetOpCode() ) + { + case ocSep: eState = STATE_SEP; break; + case ocClose: eState = STATE_CLOSE; break; + default: mxData->mbOk = false; + } + if( !mxData->mbOk ) eState = STATE_END; + break; + case STATE_SEP: + mxData->mbOk = (aFuncData.GetParamCount() < EXC_FUNC_MAXPARAM) && GetNextToken( aTokData ); + eState = mxData->mbOk ? STATE_PARAM : STATE_END; + break; + case STATE_CLOSE: + FinishFunction( aFuncData, aTokData.mnSpaces ); + eState = STATE_END; + break; + default:; + } +} + +void XclExpFmlaCompImpl::PrepareFunction( const XclExpFuncData& rFuncData ) +{ + // For OOXML these are not rewritten anymore. + if (GetOutput() == EXC_OUTPUT_XML_2007) + return; + + switch( rFuncData.GetOpCode() ) + { + case ocCosecant: // simulate CSC(x) by (1/SIN(x)) + case ocSecant: // simulate SEC(x) by (1/COS(x)) + case ocCot: // simulate COT(x) by (1/TAN(x)) + case ocCosecantHyp: // simulate CSCH(x) by (1/SINH(x)) + case ocSecantHyp: // simulate SECH(x) by (1/COSH(x)) + case ocCotHyp: // simulate COTH(x) by (1/TANH(x)) + AppendIntToken( 1 ); + break; + case ocArcCot: // simulate ACOT(x) by (PI/2-ATAN(x)) + AppendNumToken( M_PI_2 ); + break; + default:; + } +} + +void XclExpFmlaCompImpl::FinishFunction( XclExpFuncData& rFuncData, sal_uInt8 nCloseSpaces ) +{ + // append missing parameters required in Excel, may modify param count + AppendTrailingParam( rFuncData ); + + // check if parameter count fits into the limits of the function + sal_uInt8 nParamCount = rFuncData.GetParamCount(); + if( (rFuncData.GetMinParamCount() <= nParamCount) && (nParamCount <= rFuncData.GetMaxParamCount()) ) + { + // first put the tAttrSpace tokens, they must not be included in tAttrGoto handling + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP_CLOSE, nCloseSpaces ); + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP, rFuncData.GetSpaces() ); + + // add tAttrGoto tokens for IF or CHOOSE functions + switch( rFuncData.GetOpCode() ) + { + case ocIf: + case ocChoose: + AppendJumpToken( rFuncData, EXC_TOK_ATTR_GOTO ); + break; + default:; + } + + // put the tFunc or tFuncVar token (or another special token, e.g. tAttrSum) + AppendFuncToken( rFuncData ); + + // update volatile flag - is set if at least one used function is volatile + mxData->mbVolatile |= rFuncData.IsVolatile(); + + // update jump tokens for specific functions, add additional tokens + switch( rFuncData.GetOpCode() ) + { + case ocIf: + FinishIfFunction( rFuncData ); + break; + case ocChoose: + FinishChooseFunction( rFuncData ); + break; + + case ocCosecant: // simulate CSC(x) by (1/SIN(x)) + case ocSecant: // simulate SEC(x) by (1/COS(x)) + case ocCot: // simulate COT(x) by (1/TAN(x)) + case ocCosecantHyp: // simulate CSCH(x) by (1/SINH(x)) + case ocSecantHyp: // simulate SECH(x) by (1/COSH(x)) + case ocCotHyp: // simulate COTH(x) by (1/TANH(x)) + // For OOXML not rewritten anymore. + if (GetOutput() != EXC_OUTPUT_XML_2007) + { + AppendBinaryOperatorToken( EXC_TOKID_DIV, true ); + AppendParenToken(); + } + break; + case ocArcCot: // simulate ACOT(x) by (PI/2-ATAN(x)) + // For OOXML not rewritten anymore. + if (GetOutput() != EXC_OUTPUT_XML_2007) + { + AppendBinaryOperatorToken( EXC_TOKID_SUB, true ); + AppendParenToken(); + } + break; + + default:; + } + } + else + mxData->mbOk = false; +} + +void XclExpFmlaCompImpl::FinishIfFunction( XclExpFuncData& rFuncData ) +{ + sal_uInt16 nParamCount = rFuncData.GetParamCount(); + OSL_ENSURE( (nParamCount == 2) || (nParamCount == 3), "XclExpFmlaCompImpl::FinishIfFunction - wrong parameter count" ); + const ScfUInt16Vec& rAttrPos = rFuncData.GetAttrPosVec(); + OSL_ENSURE( nParamCount == rAttrPos.size(), "XclExpFmlaCompImpl::FinishIfFunction - wrong number of tAttr tokens" ); + // update tAttrIf token following the condition parameter + Overwrite( rAttrPos[ 0 ] + 2, static_cast< sal_uInt16 >( rAttrPos[ 1 ] - rAttrPos[ 0 ] ) ); + // update the tAttrGoto tokens following true and false parameters + UpdateAttrGoto( rAttrPos[ 1 ] ); + if( nParamCount == 3 ) + UpdateAttrGoto( rAttrPos[ 2 ] ); +} + +void XclExpFmlaCompImpl::FinishChooseFunction( XclExpFuncData& rFuncData ) +{ + sal_uInt16 nParamCount = rFuncData.GetParamCount(); + ScfUInt16Vec& rAttrPos = rFuncData.GetAttrPosVec(); + OSL_ENSURE( nParamCount == rAttrPos.size(), "XclExpFmlaCompImpl::FinishChooseFunction - wrong number of tAttr tokens" ); + // number of choices is parameter count minus 1 + sal_uInt16 nChoices = nParamCount - 1; + // tAttrChoose token contains number of choices + Overwrite( rAttrPos[ 0 ] + 2, nChoices ); + // cache position of the jump table (follows number of choices in tAttrChoose token) + sal_uInt16 nJumpArrPos = rAttrPos[ 0 ] + 4; + // size of jump table: number of choices, plus 1 for error position + sal_uInt16 nJumpArrSize = 2 * (nChoices + 1); + // insert the jump table into the tAttrChoose token + InsertZeros( nJumpArrPos, nJumpArrSize ); + // update positions of tAttrGoto tokens after jump table insertion + sal_uInt16 nIdx; + for( nIdx = 1; nIdx < nParamCount; ++nIdx ) + rAttrPos[ nIdx ] = rAttrPos[ nIdx ] + nJumpArrSize; + // update the tAttrGoto tokens (they contain a value one-less to real distance) + for( nIdx = 1; nIdx < nParamCount; ++nIdx ) + UpdateAttrGoto( rAttrPos[ nIdx ] ); + // update the distances in the jump table + Overwrite( nJumpArrPos, nJumpArrSize ); + for( nIdx = 1; nIdx < nParamCount; ++nIdx ) + Overwrite( nJumpArrPos + 2 * nIdx, static_cast< sal_uInt16 >( rAttrPos[ nIdx ] + 4 - nJumpArrPos ) ); +} + +XclExpScToken XclExpFmlaCompImpl::ProcessParam( XclExpScToken aTokData, XclExpFuncData& rFuncData ) +{ + if( rFuncData.IsCalcOnlyParam() ) + { + // skip Calc-only parameter, stop at next ocClose or ocSep + aTokData = SkipExpression( aTokData, true ); + rFuncData.IncParamInfoIdx(); + } + else + { + // insert Excel-only parameters, modifies param count and class in rFuncData + while( rFuncData.IsExcelOnlyParam() ) + AppendDefaultParam( rFuncData ); + + // process the parameter, stop at next ocClose or ocSep + PrepareParam( rFuncData ); + /* #i37355# insert tMissArg token for missing parameters -- + Excel import filter adds ocMissing token (handled in Factor()), + but Calc itself does not do this if a new formula is entered. */ + switch( aTokData.GetOpCode() ) + { + case ocSep: + case ocClose: AppendMissingToken(); break; // empty parameter + default: aTokData = Expression( aTokData, false, true ); + } + // finalize the parameter and add special tokens, e.g. for IF or CHOOSE parameters + if( mxData->mbOk ) FinishParam( rFuncData ); + } + return aTokData; +} + +void XclExpFmlaCompImpl::PrepareParam( XclExpFuncData& rFuncData ) +{ + // index of this parameter is equal to number of already finished parameters + sal_uInt8 nParamIdx = rFuncData.GetParamCount(); + + switch( rFuncData.GetOpCode() ) + { + case ocIf: + switch( nParamIdx ) + { + // add a tAttrIf token before true-parameter (second parameter) + case 1: AppendJumpToken( rFuncData, EXC_TOK_ATTR_IF ); break; + // add a tAttrGoto token before false-parameter (third parameter) + case 2: AppendJumpToken( rFuncData, EXC_TOK_ATTR_GOTO ); break; + } + break; + + case ocChoose: + switch( nParamIdx ) + { + // do nothing for first parameter + case 0: break; + // add a tAttrChoose token before first value parameter (second parameter) + case 1: AppendJumpToken( rFuncData, EXC_TOK_ATTR_CHOOSE ); break; + // add a tAttrGoto token before other value parameters + default: AppendJumpToken( rFuncData, EXC_TOK_ATTR_GOTO ); + } + break; + + case ocArcCotHyp: // simulate ACOTH(x) by ATANH(1/(x)) + if( nParamIdx == 0 ) + AppendIntToken( 1 ); + break; + default:; + } +} + +void XclExpFmlaCompImpl::FinishParam( XclExpFuncData& rFuncData ) +{ + // increase parameter count, update operand stack + rFuncData.FinishParam( PopOperandPos() ); + + // append more tokens for parameters of some special functions + sal_uInt8 nParamIdx = rFuncData.GetParamCount() - 1; + switch( rFuncData.GetOpCode() ) + { + case ocArcCotHyp: // simulate ACOTH(x) by ATANH(1/(x)) + if( nParamIdx == 0 ) + { + AppendParenToken(); + AppendBinaryOperatorToken( EXC_TOKID_DIV, true ); + } + break; + default:; + } +} + +void XclExpFmlaCompImpl::AppendDefaultParam( XclExpFuncData& rFuncData ) +{ + // prepare parameters of some special functions + PrepareParam( rFuncData ); + + switch( rFuncData.GetOpCode() ) + { + case ocExternal: + AppendAddInCallToken( rFuncData.GetExtFuncData() ); + break; + case ocEuroConvert: + AppendEuroToolCallToken( rFuncData.GetExtFuncData() ); + break; + case ocMacro: + // Do not write the OOXML <definedName> element. + if (GetOutput() == EXC_OUTPUT_XML_2007) + AppendNameToken( 0 ); // dummy to keep parameter count valid + else + AppendMacroCallToken( rFuncData.GetExtFuncData() ); + break; + default: + { + if( rFuncData.IsAddInEquivalent() ) + { + AppendAddInCallToken( rFuncData.GetExtFuncData() ); + } + else if( rFuncData.IsMacroFunc() ) + { + // Do not write the OOXML <definedName> element for new _xlfn. + // prefixed functions. + if (GetOutput() == EXC_OUTPUT_XML_2007) + AppendNameToken( 0 ); // dummy to keep parameter count valid + else + AppendMacroCallToken( rFuncData.GetExtFuncData() ); + } + else + { + SAL_WARN( "sc.filter", "XclExpFmlaCompImpl::AppendDefaultParam - unknown opcode" ); + AppendMissingToken(); // to keep parameter count valid + } + } + } + + // update parameter count, add special parameter tokens + FinishParam( rFuncData ); +} + +void XclExpFmlaCompImpl::AppendTrailingParam( XclExpFuncData& rFuncData ) +{ + sal_uInt8 nParamCount = rFuncData.GetParamCount(); + switch( rFuncData.GetOpCode() ) + { + case ocIf: + if( nParamCount == 1 ) + { + // Excel needs at least two parameters in IF function + PrepareParam( rFuncData ); + AppendBoolToken( true ); + FinishParam( rFuncData ); + } + break; + + case ocRound: + case ocRoundUp: + case ocRoundDown: + if( nParamCount == 1 ) + { + // ROUND, ROUNDUP, ROUNDDOWN functions are fixed to 2 parameters in Excel + PrepareParam( rFuncData ); + AppendIntToken( 0 ); + FinishParam( rFuncData ); + } + break; + + case ocIndex: + if( nParamCount == 1 ) + { + // INDEX function needs at least 2 parameters in Excel + PrepareParam( rFuncData ); + AppendMissingToken(); + FinishParam( rFuncData ); + } + break; + + case ocExternal: + case ocMacro: + // external or macro call without parameters needs the external name reference + if( nParamCount == 0 ) + AppendDefaultParam( rFuncData ); + break; + + case ocGammaDist: + if( nParamCount == 3 ) + { + // GAMMADIST function needs 4 parameters in Excel + PrepareParam( rFuncData ); + AppendIntToken( 1 ); + FinishParam( rFuncData ); + } + break; + + case ocPoissonDist: + if( nParamCount == 2 ) + { + // POISSON function needs 3 parameters in Excel + PrepareParam( rFuncData ); + AppendIntToken( 1 ); + FinishParam( rFuncData ); + } + break; + + case ocNormDist: + if( nParamCount == 3 ) + { + // NORMDIST function needs 4 parameters in Excel + PrepareParam( rFuncData ); + AppendBoolToken( true ); + FinishParam( rFuncData ); + } + break; + + case ocLogNormDist: + case ocLogInv: + switch( nParamCount ) + { + // LOGNORMDIST function needs 3 parameters in Excel + case 1: + PrepareParam( rFuncData ); + AppendIntToken( 0 ); + FinishParam( rFuncData ); + [[fallthrough]]; // add next default parameter + case 2: + PrepareParam( rFuncData ); + AppendIntToken( 1 ); + FinishParam( rFuncData ); + break; + default:; + } + + break; + + default: + // #i108420# function without parameters stored as macro call needs the external name reference + if( (nParamCount == 0) && rFuncData.IsMacroFunc() ) + AppendDefaultParam( rFuncData ); + + } +} + +// reference handling --------------------------------------------------------- + +namespace { + +bool lclIsRefRel2D( const ScSingleRefData& rRefData ) +{ + return rRefData.IsColRel() || rRefData.IsRowRel(); +} + +bool lclIsRefDel2D( const ScSingleRefData& rRefData ) +{ + return rRefData.IsColDeleted() || rRefData.IsRowDeleted(); +} + +bool lclIsRefRel2D( const ScComplexRefData& rRefData ) +{ + return lclIsRefRel2D( rRefData.Ref1 ) || lclIsRefRel2D( rRefData.Ref2 ); +} + +bool lclIsRefDel2D( const ScComplexRefData& rRefData ) +{ + return lclIsRefDel2D( rRefData.Ref1 ) || lclIsRefDel2D( rRefData.Ref2 ); +} + +} // namespace + +SCTAB XclExpFmlaCompImpl::GetScTab( const ScSingleRefData& rRefData ) const +{ + if (rRefData.IsTabDeleted()) + return SCTAB_INVALID; + + if (!rRefData.IsTabRel()) + // absolute address + return rRefData.Tab(); + + if (!mxData->mpScBasePos) + return SCTAB_INVALID; + + return rRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos).Tab(); +} + +bool XclExpFmlaCompImpl::IsRef2D( const ScSingleRefData& rRefData, bool bCheck3DFlag ) const +{ + /* rRefData.IsFlag3D() determines if sheet name is always visible, even on + the own sheet. If 3D references are allowed, the passed reference does + not count as 2D reference. */ + + // conditional formatting does not allow 3D refs in xls + if (mxData && mxData->mrCfg.meType == EXC_FMLATYPE_CONDFMT) + return true; + + if (bCheck3DFlag && rRefData.IsFlag3D()) + return false; + + if (rRefData.IsTabDeleted()) + return false; + + if (rRefData.IsTabRel()) + return rRefData.Tab() == 0; + else + return rRefData.Tab() == GetCurrScTab(); +} + +bool XclExpFmlaCompImpl::IsRef2D( const ScComplexRefData& rRefData, bool bCheck3DFlag ) const +{ + return IsRef2D(rRefData.Ref1, bCheck3DFlag) && IsRef2D(rRefData.Ref2, bCheck3DFlag); +} + +void XclExpFmlaCompImpl::ConvertRefData( + ScSingleRefData& rRefData, XclAddress& rXclPos, + bool bNatLangRef, bool bTruncMaxCol, bool bTruncMaxRow ) const +{ + if( mxData->mpScBasePos ) + { + // *** reference position exists (cell, matrix) - convert to absolute *** + ScAddress aAbs = rRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos); + + // convert column index + if (bTruncMaxCol && (aAbs.Col() == mnMaxScCol)) + aAbs.SetCol(mnMaxAbsCol); + else if ((aAbs.Col() < 0) || (aAbs.Col() > mnMaxAbsCol)) + rRefData.SetColDeleted(true); + rXclPos.mnCol = static_cast<sal_uInt16>(aAbs.Col()) & mnMaxColMask; + + // convert row index + if (bTruncMaxRow && (aAbs.Row() == mnMaxScRow)) + aAbs.SetRow(mnMaxAbsRow); + else if ((aAbs.Row() < 0) || (aAbs.Row() > mnMaxAbsRow)) + rRefData.SetRowDeleted(true); + rXclPos.mnRow = static_cast<sal_uInt32>(aAbs.Row()) & mnMaxRowMask; + + // Update the reference. + rRefData.SetAddress(GetRoot().GetDoc().GetSheetLimits(), aAbs, *mxData->mpScBasePos); + } + else + { + // *** no reference position (shared, names, condfmt) - use relative values *** + + // convert column index (2-step-cast ScsCOL->sal_Int16->sal_uInt16 to get all bits correctly) + sal_Int16 nXclRelCol = static_cast<sal_Int16>(rRefData.Col()); + rXclPos.mnCol = static_cast< sal_uInt16 >( nXclRelCol ) & mnMaxColMask; + + // convert row index (2-step-cast ScsROW->sal_Int32->sal_uInt32 to get all bits correctly) + sal_Int32 nXclRelRow = static_cast<sal_Int32>(rRefData.Row()); + rXclPos.mnRow = static_cast< sal_uInt32 >( nXclRelRow ) & mnMaxRowMask; + } + + // flags for relative column and row + if( bNatLangRef ) + { + OSL_ENSURE( meBiff == EXC_BIFF8, "XclExpFmlaCompImpl::ConvertRefData - NLRs only for BIFF8" ); + // Calc does not support absolute reference mode in natural language references + ::set_flag( rXclPos.mnCol, EXC_TOK_NLR_REL ); + } + else + { + sal_uInt16 rnRelRow = rXclPos.mnRow; + sal_uInt16& rnRelField = (meBiff <= EXC_BIFF5) ? rnRelRow : rXclPos.mnCol; + ::set_flag( rnRelField, EXC_TOK_REF_COLREL, rRefData.IsColRel() ); + ::set_flag( rnRelField, EXC_TOK_REF_ROWREL, rRefData.IsRowRel() ); + } +} + +void XclExpFmlaCompImpl::ConvertRefData( + ScComplexRefData& rRefData, XclRange& rXclRange, bool bNatLangRef ) const +{ + // convert start and end of the range + ConvertRefData( rRefData.Ref1, rXclRange.maFirst, bNatLangRef, false, false ); + bool bTruncMaxCol = !rRefData.Ref1.IsColDeleted() && (rXclRange.maFirst.mnCol == 0); + bool bTruncMaxRow = !rRefData.Ref1.IsRowDeleted() && (rXclRange.maFirst.mnRow == 0); + ConvertRefData( rRefData.Ref2, rXclRange.maLast, bNatLangRef, bTruncMaxCol, bTruncMaxRow ); +} + +XclExpRefLogEntry* XclExpFmlaCompImpl::GetNewRefLogEntry() +{ + if( mxData->mpRefLog ) + { + mxData->mpRefLog->emplace_back(); + return &mxData->mpRefLog->back(); + } + return nullptr; +} + +void XclExpFmlaCompImpl::ProcessCellRef( const XclExpScToken& rTokData ) +{ + // get the Excel address components, adjust internal data in aRefData + bool bNatLangRef = (meBiff == EXC_BIFF8) && mxData->mpScBasePos && (rTokData.GetOpCode() == ocColRowName); + ScSingleRefData aRefData = *rTokData.mpScToken->GetSingleRef(); + XclAddress aXclPos( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclPos, bNatLangRef, false, false ); + + if( bNatLangRef ) + { + OSL_ENSURE( aRefData.IsColRel() != aRefData.IsRowRel(), + "XclExpFmlaCompImpl::ProcessCellRef - broken natural language reference" ); + // create tNlr token for natural language reference + sal_uInt8 nSubId = aRefData.IsColRel() ? EXC_TOK_NLR_COLV : EXC_TOK_NLR_ROWV; + AppendOperandTokenId( EXC_TOKID_NLR, rTokData.mnSpaces ); + Append( nSubId ); + AppendAddress( aXclPos ); + } + else + { + // store external cell contents in CRN records + if( mxData->mrCfg.mbFromCell && mxData->mpLinkMgr && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCell(aRefData, *mxData->mpScBasePos); + + // create the tRef, tRefErr, tRefN, tRef3d, or tRefErr3d token + if (!mxData->mrCfg.mb3DRefOnly && IsRef2D(aRefData, mxData->mpLinkMgr != nullptr)) + { + // 2D reference (not in defined names, but allowed in range lists) + sal_uInt8 nBaseId = (!mxData->mpScBasePos && lclIsRefRel2D( aRefData )) ? EXC_TOKID_REFN : + (lclIsRefDel2D( aRefData ) ? EXC_TOKID_REFERR : EXC_TOKID_REF); + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + AppendAddress( aXclPos ); + } + else if( mxData->mpLinkMgr ) // 3D reference + { + // 1-based EXTERNSHEET index and 0-based Excel sheet index + sal_uInt16 nExtSheet, nXclTab; + mxData->mpLinkMgr->FindExtSheet( nExtSheet, nXclTab, GetScTab( aRefData ), GetNewRefLogEntry() ); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_REFERR3D : EXC_TOKID_REF3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nXclTab ); + Append( nXclTab ); + } + AppendAddress( aXclPos ); + } + else + { + // 3D ref in cond. format, or 2D ref in name + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } + } +} + +void XclExpFmlaCompImpl::ProcessRangeRef( const XclExpScToken& rTokData ) +{ + // get the Excel address components, adjust internal data in aRefData + ScComplexRefData aRefData = *rTokData.mpScToken->GetDoubleRef(); + XclRange aXclRange( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclRange, false ); + + // store external cell contents in CRN records + if( mxData->mrCfg.mbFromCell && mxData->mpLinkMgr && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCellRange(aRefData, *mxData->mpScBasePos); + + // create the tArea, tAreaErr, tAreaN, tArea3d, or tAreaErr3d token + if (!mxData->mrCfg.mb3DRefOnly && IsRef2D(aRefData, mxData->mpLinkMgr != nullptr)) + { + // 2D reference (not in name formulas, but allowed in range lists) + sal_uInt8 nBaseId = (!mxData->mpScBasePos && lclIsRefRel2D( aRefData )) ? EXC_TOKID_AREAN : + (lclIsRefDel2D( aRefData ) ? EXC_TOKID_AREAERR : EXC_TOKID_AREA); + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + AppendRange( aXclRange ); + } + else if( mxData->mpLinkMgr ) // 3D reference + { + // 1-based EXTERNSHEET index and 0-based Excel sheet indexes + sal_uInt16 nExtSheet, nFirstXclTab, nLastXclTab; + mxData->mpLinkMgr->FindExtSheet( nExtSheet, nFirstXclTab, nLastXclTab, + GetScTab( aRefData.Ref1 ), GetScTab( aRefData.Ref2 ), GetNewRefLogEntry() ); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_AREAERR3D : EXC_TOKID_AREA3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nFirstXclTab ); + Append( nLastXclTab ); + } + AppendRange( aXclRange ); + } + else + { + // 3D ref in cond. format, or 2D ref in name + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessExternalCellRef( const XclExpScToken& rTokData ) +{ + if( mxData->mpLinkMgr ) + { + // get the Excel address components, adjust internal data in aRefData + ScSingleRefData aRefData = *rTokData.mpScToken->GetSingleRef(); + XclAddress aXclPos( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclPos, false, false, false ); + + // store external cell contents in CRN records + sal_uInt16 nFileId = rTokData.mpScToken->GetIndex(); + OUString aTabName = rTokData.mpScToken->GetString().getString(); + if( mxData->mrCfg.mbFromCell && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCell(nFileId, aTabName, aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + + // 1-based EXTERNSHEET index and 0-based Excel sheet indexes + sal_uInt16 nExtSheet, nFirstSBTab, nLastSBTab; + mxData->mpLinkMgr->FindExtSheet( nFileId, aTabName, 1, nExtSheet, nFirstSBTab, nLastSBTab, GetNewRefLogEntry() ); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_REFERR3D : EXC_TOKID_REF3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nFirstSBTab ); + Append( nLastSBTab ); + } + AppendAddress( aXclPos ); + } + else + { + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessExternalRangeRef( const XclExpScToken& rTokData ) +{ + if( mxData->mpLinkMgr ) + { + // get the Excel address components, adjust internal data in aRefData + ScComplexRefData aRefData = *rTokData.mpScToken->GetDoubleRef(); + XclRange aXclRange( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclRange, false ); + + // store external cell contents in CRN records + sal_uInt16 nFileId = rTokData.mpScToken->GetIndex(); + OUString aTabName = rTokData.mpScToken->GetString().getString(); + if( mxData->mrCfg.mbFromCell && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCellRange(nFileId, aTabName, aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + + // 1-based EXTERNSHEET index and 0-based Excel sheet indexes + sal_uInt16 nExtSheet, nFirstSBTab, nLastSBTab; + sal_uInt16 nTabSpan = static_cast<sal_uInt16>(aRefData.Ref2.Tab() - aRefData.Ref1.Tab() + 1); + mxData->mpLinkMgr->FindExtSheet( + nFileId, aTabName, nTabSpan, nExtSheet, nFirstSBTab, nLastSBTab, GetNewRefLogEntry()); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_AREAERR3D : EXC_TOKID_AREA3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nFirstSBTab ); + Append( nLastSBTab ); + } + AppendRange( aXclRange ); + } + else + { + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessDefinedName( const XclExpScToken& rTokData ) +{ + sal_Int16 nSheet = rTokData.mpScToken->GetSheet(); + SCTAB nTab = (nSheet < 0 ? SCTAB_GLOBAL : nSheet); + + XclExpNameManager& rNameMgr = GetNameManager(); + sal_uInt16 nNameIdx = rNameMgr.InsertName(nTab, rTokData.mpScToken->GetIndex(), GetCurrScTab()); + if( nNameIdx != 0 ) + { + // global names always with tName token, local names dependent on config + SCTAB nScTab = rNameMgr.GetScTab( nNameIdx ); + if( (nScTab == SCTAB_GLOBAL) || (!mxData->mrCfg.mb3DRefOnly && (nScTab == GetCurrScTab())) ) + { + AppendNameToken( nNameIdx, rTokData.mnSpaces ); + } + else if( mxData->mpLinkMgr ) + { + // use the same special EXTERNNAME to refer to any local name + sal_uInt16 nExtSheet = mxData->mpLinkMgr->FindExtSheet( EXC_EXTSH_OWNDOC ); + AppendNameXToken( nExtSheet, nNameIdx, rTokData.mnSpaces ); + } + else + AppendErrorToken( EXC_ERR_NAME, rTokData.mnSpaces ); + // volatile names (containing volatile functions) + mxData->mbVolatile |= rNameMgr.IsVolatile( nNameIdx ); + } + else + AppendErrorToken( EXC_ERR_NAME, rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessExternalName( const XclExpScToken& rTokData ) +{ + if( mxData->mpLinkMgr ) + { + ScExternalRefManager& rExtRefMgr = *GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = rTokData.mpScToken->GetIndex(); + OUString aName = rTokData.mpScToken->GetString().getString(); + ScExternalRefCache::TokenArrayRef xArray = rExtRefMgr.getRangeNameTokens( nFileId, aName ); + if( xArray ) + { + // store external cell contents in CRN records + if( mxData->mpScBasePos ) + { + FormulaTokenArrayPlainIterator aIter(*xArray); + for( FormulaToken* pScToken = aIter.First(); pScToken; pScToken = aIter.Next() ) + { + if( pScToken->IsExternalRef() ) + { + switch( pScToken->GetType() ) + { + case svExternalSingleRef: + { + ScSingleRefData aRefData = *pScToken->GetSingleRef(); + mxData->mpLinkMgr->StoreCell( + nFileId, pScToken->GetString().getString(), aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + } + break; + case svExternalDoubleRef: + { + ScComplexRefData aRefData = *pScToken->GetDoubleRef(); + mxData->mpLinkMgr->StoreCellRange( + nFileId, pScToken->GetString().getString(), aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + } + break; + default: + ; // nothing, avoid compiler warning + } + } + } + } + + // insert the new external name and create the tNameX token + sal_uInt16 nExtSheet = 0, nExtName = 0; + const OUString* pFile = rExtRefMgr.getExternalFileName( nFileId ); + if( pFile && mxData->mpLinkMgr->InsertExtName( nExtSheet, nExtName, *pFile, aName, xArray ) ) + { + AppendNameXToken( nExtSheet, nExtName, rTokData.mnSpaces ); + return; + } + } + } + + // on any error: create a #NAME? error + AppendErrorToken( EXC_ERR_NAME, rTokData.mnSpaces ); +} + +// token vector --------------------------------------------------------------- + +void XclExpFmlaCompImpl::PushOperandPos( sal_uInt16 nTokPos ) +{ + mxData->maOpPosStack.push_back( nTokPos ); +} + +void XclExpFmlaCompImpl::PushOperatorPos( sal_uInt16 nTokPos, const XclExpOperandListRef& rxOperands ) +{ + PushOperandPos( nTokPos ); + OSL_ENSURE( rxOperands, "XclExpFmlaCompImpl::AppendOperatorTokenId - missing operand list" ); + if( mxData->maOpListVec.size() <= nTokPos ) + mxData->maOpListVec.resize( nTokPos + 1, XclExpOperandListRef() ); + mxData->maOpListVec[ nTokPos ] = rxOperands; +} + +sal_uInt16 XclExpFmlaCompImpl::PopOperandPos() +{ + OSL_ENSURE( !mxData->mbOk || !mxData->maOpPosStack.empty(), "XclExpFmlaCompImpl::PopOperandPos - token stack broken" ); + mxData->mbOk &= !mxData->maOpPosStack.empty(); + if( mxData->mbOk ) + { + sal_uInt16 nTokPos = mxData->maOpPosStack.back(); + mxData->maOpPosStack.pop_back(); + return nTokPos; + } + return 0; +} + +namespace { + +void lclAppend( ScfUInt8Vec& orVector, sal_uInt16 nData ) +{ + orVector.resize( orVector.size() + 2 ); + ShortToSVBT16( nData, &*(orVector.end() - 2) ); +} + +void lclAppend( ScfUInt8Vec& orVector, sal_uInt32 nData ) +{ + orVector.resize( orVector.size() + 4 ); + UInt32ToSVBT32( nData, &*(orVector.end() - 4) ); +} + +void lclAppend( ScfUInt8Vec& orVector, double fData ) +{ + orVector.resize( orVector.size() + 8 ); + DoubleToSVBT64( fData, &*(orVector.end() - 8) ); +} + +void lclAppend( ScfUInt8Vec& orVector, const XclExpRoot& rRoot, const OUString& rString, XclStrFlags nStrFlags ) +{ + XclExpStringRef xXclStr = XclExpStringHelper::CreateString( rRoot, rString, nStrFlags, EXC_TOK_STR_MAXLEN ); + size_t nSize = orVector.size(); + orVector.resize( nSize + xXclStr->GetSize() ); + xXclStr->WriteToMem( &orVector[ nSize ] ); +} + +} // namespace + +void XclExpFmlaCompImpl::Append( sal_uInt8 nData ) +{ + mxData->maTokVec.push_back( nData ); +} + +void XclExpFmlaCompImpl::Append( sal_uInt8 nData, size_t nCount ) +{ + mxData->maTokVec.resize( mxData->maTokVec.size() + nCount, nData ); +} + +void XclExpFmlaCompImpl::Append( sal_uInt16 nData ) +{ + lclAppend( mxData->maTokVec, nData ); +} + +void XclExpFmlaCompImpl::Append( sal_uInt32 nData ) +{ + lclAppend( mxData->maTokVec, nData ); +} + +void XclExpFmlaCompImpl::Append( double fData ) +{ + lclAppend( mxData->maTokVec, fData ); +} + +void XclExpFmlaCompImpl::Append( const OUString& rString ) +{ + lclAppend( mxData->maTokVec, GetRoot(), rString, XclStrFlags::EightBitLength ); +} + +void XclExpFmlaCompImpl::AppendAddress( const XclAddress& rXclPos ) +{ + Append( static_cast<sal_uInt16>(rXclPos.mnRow) ); + if( meBiff <= EXC_BIFF5 ) + Append( static_cast< sal_uInt8 >( rXclPos.mnCol ) ); + else + Append( rXclPos.mnCol ); +} + +void XclExpFmlaCompImpl::AppendRange( const XclRange& rXclRange ) +{ + Append( static_cast<sal_uInt16>(rXclRange.maFirst.mnRow) ); + Append( static_cast<sal_uInt16>(rXclRange.maLast.mnRow) ); + if( meBiff <= EXC_BIFF5 ) + { + Append( static_cast< sal_uInt8 >( rXclRange.maFirst.mnCol ) ); + Append( static_cast< sal_uInt8 >( rXclRange.maLast.mnCol ) ); + } + else + { + Append( rXclRange.maFirst.mnCol ); + Append( rXclRange.maLast.mnCol ); + } +} + +void XclExpFmlaCompImpl::AppendSpaceToken( sal_uInt8 nType, sal_uInt8 nCount ) +{ + if( nCount > 0 ) + { + Append( EXC_TOKID_ATTR ); + Append( EXC_TOK_ATTR_SPACE ); + Append( nType ); + Append( nCount ); + } +} + +void XclExpFmlaCompImpl::AppendOperandTokenId( sal_uInt8 nTokenId, sal_uInt8 nSpaces ) +{ + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP, nSpaces ); + PushOperandPos( GetSize() ); + Append( nTokenId ); +} + +void XclExpFmlaCompImpl::AppendIntToken( sal_uInt16 nValue, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_INT, nSpaces ); + Append( nValue ); +} + +void XclExpFmlaCompImpl::AppendNumToken( double fValue, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_NUM, nSpaces ); + Append( fValue ); +} + +void XclExpFmlaCompImpl::AppendBoolToken( bool bValue, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_BOOL, nSpaces ); + Append( bValue ? EXC_TOK_BOOL_TRUE : EXC_TOK_BOOL_FALSE ); +} + +void XclExpFmlaCompImpl::AppendErrorToken( sal_uInt8 nErrCode, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_ERR, nSpaces ); + Append( nErrCode ); +} + +void XclExpFmlaCompImpl::AppendMissingToken( sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_MISSARG, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendNameToken( sal_uInt16 nNameIdx, sal_uInt8 nSpaces ) +{ + if( nNameIdx > 0 ) + { + AppendOperandTokenId( GetTokenId( EXC_TOKID_NAME, EXC_TOKCLASS_REF ), nSpaces ); + Append( nNameIdx ); + Append( 0, (meBiff <= EXC_BIFF5) ? 12 : 2 ); + } + else + AppendErrorToken( EXC_ERR_NAME ); +} + +void XclExpFmlaCompImpl::AppendMissingNameToken( const OUString& rName, sal_uInt8 nSpaces ) +{ + sal_uInt16 nNameIdx = GetNameManager().InsertRawName( rName ); + AppendNameToken( nNameIdx, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendNameXToken( sal_uInt16 nExtSheet, sal_uInt16 nExtName, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ), nSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + Append( 0, 8 ); + Append( nExtName ); + Append( 0, (meBiff <= EXC_BIFF5) ? 12 : 2 ); +} + +void XclExpFmlaCompImpl::AppendMacroCallToken( const XclExpExtFuncData& rExtFuncData ) +{ + sal_uInt16 nNameIdx = GetNameManager().InsertMacroCall( rExtFuncData.maFuncName, rExtFuncData.mbVBasic, true, rExtFuncData.mbHidden ); + AppendNameToken( nNameIdx ); +} + +void XclExpFmlaCompImpl::AppendAddInCallToken( const XclExpExtFuncData& rExtFuncData ) +{ + OUString aXclFuncName; + if( mxData->mpLinkMgr && ScGlobal::GetAddInCollection()->GetExcelName( rExtFuncData.maFuncName, GetUILanguage(), aXclFuncName ) ) + { + sal_uInt16 nExtSheet, nExtName; + if( mxData->mpLinkMgr->InsertAddIn( nExtSheet, nExtName, aXclFuncName ) ) + { + AppendNameXToken( nExtSheet, nExtName ); + return; + } + } + AppendMacroCallToken( rExtFuncData ); +} + +void XclExpFmlaCompImpl::AppendEuroToolCallToken( const XclExpExtFuncData& rExtFuncData ) +{ + sal_uInt16 nExtSheet(0), nExtName(0); + if( mxData->mpLinkMgr && mxData->mpLinkMgr->InsertEuroTool( nExtSheet, nExtName, rExtFuncData.maFuncName ) ) + AppendNameXToken( nExtSheet, nExtName ); + else + AppendMacroCallToken( rExtFuncData ); +} + +void XclExpFmlaCompImpl::AppendOperatorTokenId( sal_uInt8 nTokenId, const XclExpOperandListRef& rxOperands, sal_uInt8 nSpaces ) +{ + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP, nSpaces ); + PushOperatorPos( GetSize(), rxOperands ); + Append( nTokenId ); +} + +void XclExpFmlaCompImpl::AppendUnaryOperatorToken( sal_uInt8 nTokenId, sal_uInt8 nSpaces ) +{ + XclExpOperandListRef xOperands = std::make_shared<XclExpOperandList>(); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPO, true ); + AppendOperatorTokenId( nTokenId, xOperands, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendBinaryOperatorToken( sal_uInt8 nTokenId, bool bValType, sal_uInt8 nSpaces ) +{ + XclExpOperandListRef xOperands = std::make_shared<XclExpOperandList>(); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPO, bValType ); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPO, bValType ); + AppendOperatorTokenId( nTokenId, xOperands, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendLogicalOperatorToken( sal_uInt16 nXclFuncIdx, sal_uInt8 nOpCount ) +{ + XclExpOperandListRef xOperands = std::make_shared<XclExpOperandList>(); + for( sal_uInt8 nOpIdx = 0; nOpIdx < nOpCount; ++nOpIdx ) + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPX, false ); + AppendOperatorTokenId( GetTokenId( EXC_TOKID_FUNCVAR, EXC_TOKCLASS_VAL ), xOperands ); + Append( nOpCount ); + Append( nXclFuncIdx ); +} + +void XclExpFmlaCompImpl::AppendFuncToken( const XclExpFuncData& rFuncData ) +{ + sal_uInt16 nXclFuncIdx = rFuncData.GetXclFuncIdx(); + sal_uInt8 nParamCount = rFuncData.GetParamCount(); + sal_uInt8 nRetClass = rFuncData.GetReturnClass(); + + if( (nXclFuncIdx == EXC_FUNCID_SUM) && (nParamCount == 1) ) + { + // SUM with only one parameter + AppendOperatorTokenId( EXC_TOKID_ATTR, rFuncData.GetOperandList() ); + Append( EXC_TOK_ATTR_SUM ); + Append( sal_uInt16( 0 ) ); + } + else if( rFuncData.IsFixedParamCount() ) + { + // fixed number of parameters + AppendOperatorTokenId( GetTokenId( EXC_TOKID_FUNC, nRetClass ), rFuncData.GetOperandList() ); + Append( nXclFuncIdx ); + } + else + { + // variable number of parameters + AppendOperatorTokenId( GetTokenId( EXC_TOKID_FUNCVAR, nRetClass ), rFuncData.GetOperandList() ); + Append( nParamCount ); + Append( nXclFuncIdx ); + } +} + +void XclExpFmlaCompImpl::AppendParenToken( sal_uInt8 nOpenSpaces, sal_uInt8 nCloseSpaces ) +{ + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP_OPEN, nOpenSpaces ); + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP_CLOSE, nCloseSpaces ); + Append( EXC_TOKID_PAREN ); +} + +void XclExpFmlaCompImpl::AppendJumpToken( XclExpFuncData& rFuncData, sal_uInt8 nAttrType ) +{ + // store the start position of the token + rFuncData.AppendAttrPos( GetSize() ); + // create the tAttr token + Append( EXC_TOKID_ATTR ); + Append( nAttrType ); + Append( sal_uInt16( 0 ) ); // placeholder that will be updated later +} + +void XclExpFmlaCompImpl::InsertZeros( sal_uInt16 nInsertPos, sal_uInt16 nInsertSize ) +{ + // insert zeros into the token array + OSL_ENSURE( nInsertPos < mxData->maTokVec.size(), "XclExpFmlaCompImpl::Insert - invalid position" ); + mxData->maTokVec.insert( mxData->maTokVec.begin() + nInsertPos, nInsertSize, 0 ); + + // update positions of operands waiting for an operator + for( auto& rOpPos : mxData->maOpPosStack ) + if( nInsertPos <= rOpPos ) + rOpPos += nInsertSize; + + // update operand lists of all operator tokens + if( nInsertPos < mxData->maOpListVec.size() ) + mxData->maOpListVec.insert( mxData->maOpListVec.begin() + nInsertPos, nInsertSize, XclExpOperandListRef() ); + for( auto& rxOpList : mxData->maOpListVec ) + if( rxOpList ) + for( auto& rOp : *rxOpList ) + if( nInsertPos <= rOp.mnTokPos ) + rOp.mnTokPos += nInsertSize; +} + +void XclExpFmlaCompImpl::Overwrite( sal_uInt16 nWriteToPos, sal_uInt16 nOffset ) +{ + OSL_ENSURE( o3tl::make_unsigned( nWriteToPos + 1 ) < mxData->maTokVec.size(), "XclExpFmlaCompImpl::Overwrite - invalid position" ); + ShortToSVBT16( nOffset, &mxData->maTokVec[ nWriteToPos ] ); +} + +void XclExpFmlaCompImpl::UpdateAttrGoto( sal_uInt16 nAttrPos ) +{ + /* tAttrGoto contains distance from end of tAttr token to position behind + the function token (for IF or CHOOSE function), which is currently at + the end of the token array. Additionally this distance is decreased by + one, for whatever reason. So we have to subtract 4 and 1 from the + distance between the tAttr token start and the end of the token array. */ + Overwrite( nAttrPos + 2, static_cast< sal_uInt16 >( GetSize() - nAttrPos - 5 ) ); +} + +bool XclExpFmlaCompImpl::IsSpaceToken( sal_uInt16 nPos ) const +{ + return + (o3tl::make_unsigned( nPos + 4 ) <= mxData->maTokVec.size()) && + (mxData->maTokVec[ nPos ] == EXC_TOKID_ATTR) && + (mxData->maTokVec[ nPos + 1 ] == EXC_TOK_ATTR_SPACE); +} + +void XclExpFmlaCompImpl::RemoveTrailingParen() +{ + // remove trailing tParen token + if( !mxData->maTokVec.empty() && (mxData->maTokVec.back() == EXC_TOKID_PAREN) ) + mxData->maTokVec.pop_back(); + // remove remaining tAttrSpace tokens + while( (mxData->maTokVec.size() >= 4) && IsSpaceToken( GetSize() - 4 ) ) + mxData->maTokVec.erase( mxData->maTokVec.end() - 4, mxData->maTokVec.end() ); +} + +void XclExpFmlaCompImpl::AppendExt( sal_uInt8 nData ) +{ + mxData->maExtDataVec.push_back( nData ); +} + +void XclExpFmlaCompImpl::AppendExt( sal_uInt8 nData, size_t nCount ) +{ + mxData->maExtDataVec.resize( mxData->maExtDataVec.size() + nCount, nData ); +} + +void XclExpFmlaCompImpl::AppendExt( sal_uInt16 nData ) +{ + lclAppend( mxData->maExtDataVec, nData ); +} + +void XclExpFmlaCompImpl::AppendExt( double fData ) +{ + lclAppend( mxData->maExtDataVec, fData ); +} + +void XclExpFmlaCompImpl::AppendExt( const OUString& rString ) +{ + lclAppend( mxData->maExtDataVec, GetRoot(), rString, (meBiff == EXC_BIFF8) ? XclStrFlags::NONE : XclStrFlags::EightBitLength ); +} + +namespace { + +void lclInitOwnTab( ScSingleRefData& rRef, const ScAddress& rScPos, SCTAB nCurrScTab, bool b3DRefOnly ) +{ + if( b3DRefOnly ) + { + // no reduction to 2D reference, if global link manager is used + rRef.SetFlag3D(true); + } + else if( rScPos.Tab() == nCurrScTab ) + { + rRef.SetRelTab(0); + } +} + +void lclPutCellToTokenArray( ScTokenArray& rScTokArr, const ScAddress& rScPos, SCTAB nCurrScTab, bool b3DRefOnly ) +{ + ScSingleRefData aRef; + aRef.InitAddress( rScPos ); + lclInitOwnTab( aRef, rScPos, nCurrScTab, b3DRefOnly ); + rScTokArr.AddSingleReference( aRef ); +} + +void lclPutRangeToTokenArray( ScTokenArray& rScTokArr, const ScRange& rScRange, SCTAB nCurrScTab, bool b3DRefOnly ) +{ + if( rScRange.aStart == rScRange.aEnd ) + { + lclPutCellToTokenArray( rScTokArr, rScRange.aStart, nCurrScTab, b3DRefOnly ); + } + else + { + ScComplexRefData aRef; + aRef.InitRange( rScRange ); + lclInitOwnTab( aRef.Ref1, rScRange.aStart, nCurrScTab, b3DRefOnly ); + lclInitOwnTab( aRef.Ref2, rScRange.aEnd, nCurrScTab, b3DRefOnly ); + rScTokArr.AddDoubleReference( aRef ); + } +} + +} // namespace + +XclExpFormulaCompiler::XclExpFormulaCompiler( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxImpl( std::make_shared<XclExpFmlaCompImpl>( rRoot ) ) +{ +} + +XclExpFormulaCompiler::~XclExpFormulaCompiler() +{ +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( + XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos, XclExpRefLog* pRefLog ) +{ + return mxImpl->CreateFormula( eType, rScTokArr, pScBasePos, pRefLog ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( XclFormulaType eType, const ScAddress& rScPos ) +{ + ScTokenArray aScTokArr(GetRoot().GetDoc()); + lclPutCellToTokenArray( aScTokArr, rScPos, GetCurrScTab(), mxImpl->Is3DRefOnly( eType ) ); + return mxImpl->CreateFormula( eType, aScTokArr ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( XclFormulaType eType, const ScRange& rScRange ) +{ + ScTokenArray aScTokArr(GetRoot().GetDoc()); + lclPutRangeToTokenArray( aScTokArr, rScRange, GetCurrScTab(), mxImpl->Is3DRefOnly( eType ) ); + return mxImpl->CreateFormula( eType, aScTokArr ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( XclFormulaType eType, const ScRangeList& rScRanges ) +{ + size_t nCount = rScRanges.size(); + if( nCount == 0 ) + return XclTokenArrayRef(); + + ScTokenArray aScTokArr(GetRoot().GetDoc()); + SCTAB nCurrScTab = GetCurrScTab(); + bool b3DRefOnly = mxImpl->Is3DRefOnly( eType ); + for( size_t nIdx = 0; nIdx < nCount; ++nIdx ) + { + if( nIdx > 0 ) + aScTokArr.AddOpCode( ocUnion ); + lclPutRangeToTokenArray( aScTokArr, rScRanges[ nIdx ], nCurrScTab, b3DRefOnly ); + } + return mxImpl->CreateFormula( eType, aScTokArr ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateErrorFormula( sal_uInt8 nErrCode ) +{ + return mxImpl->CreateErrorFormula( nErrCode ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateSpecialRefFormula( + sal_uInt8 nTokenId, const XclAddress& rXclPos ) +{ + return mxImpl->CreateSpecialRefFormula( nTokenId, rXclPos ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateNameXFormula( + sal_uInt16 nExtSheet, sal_uInt16 nExtName ) +{ + return mxImpl->CreateNameXFormula( nExtSheet, nExtName ); +} + +bool XclExpFormulaCompiler::IsRef2D( const ScSingleRefData& rRefData ) const +{ + return mxImpl->IsRef2D(rRefData, true); +} + +bool XclExpFormulaCompiler::IsRef2D( const ScComplexRefData& rRefData ) const +{ + return mxImpl->IsRef2D(rRefData, true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xehelper.cxx b/sc/source/filter/excel/xehelper.cxx new file mode 100644 index 000000000..038ec8f71 --- /dev/null +++ b/sc/source/filter/excel/xehelper.cxx @@ -0,0 +1,1071 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <sfx2/objsh.hxx> +#include <vcl/font.hxx> +#include <tools/urlobj.hxx> +#include <svl/itemset.hxx> +#include <svtools/ctrltool.hxx> +#include <svx/svdotext.hxx> +#include <editeng/outlobj.hxx> +#include <scitems.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/flstitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/svxfont.hxx> +#include <editeng/editids.hrc> + +#include <document.hxx> +#include <docpool.hxx> +#include <editutil.hxx> +#include <patattr.hxx> +#include <scmatrix.hxx> +#include <xestyle.hxx> +#include <fprogressbar.hxx> +#include <globstr.hrc> +#include <xltracer.hxx> +#include <xltools.hxx> +#include <xecontent.hxx> +#include <xelink.hxx> +#include <xehelper.hxx> + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::i18n::XBreakIterator; + +// Export progress bar ======================================================== + +XclExpProgressBar::XclExpProgressBar( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxProgress( new ScfProgressBar( rRoot.GetDocShell(), STR_SAVE_DOC ) ), + mpSubProgress( nullptr ), + mpSubRowCreate( nullptr ), + mpSubRowFinal( nullptr ), + mnSegRowFinal( SCF_INV_SEGMENT ), + mnRowCount( 0 ) +{ +} + +XclExpProgressBar::~XclExpProgressBar() +{ +} + +void XclExpProgressBar::Initialize() +{ + const ScDocument& rDoc = GetDoc(); + const XclExpTabInfo& rTabInfo = GetTabInfo(); + SCTAB nScTabCount = rTabInfo.GetScTabCount(); + + // *** segment: creation of ROW records *** ------------------------------- + + sal_Int32 nSegRowCreate = mxProgress->AddSegment( 2000 ); + mpSubRowCreate = &mxProgress->GetSegmentProgressBar( nSegRowCreate ); + maSubSegRowCreate.resize( nScTabCount, SCF_INV_SEGMENT ); + + for( SCTAB nScTab = 0; nScTab < nScTabCount; ++nScTab ) + { + if( rTabInfo.IsExportTab( nScTab ) ) + { + SCCOL nLastUsedScCol; + SCROW nLastUsedScRow; + rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow ); + std::size_t nSegSize = static_cast< std::size_t >( nLastUsedScRow + 1 ); + maSubSegRowCreate[ nScTab ] = mpSubRowCreate->AddSegment( nSegSize ); + } + } + + // *** segment: writing all ROW records *** ------------------------------- + + mnSegRowFinal = mxProgress->AddSegment( 1000 ); + // sub progress bar and segment are created later in ActivateFinalRowsSegment() +} + +void XclExpProgressBar::IncRowRecordCount() +{ + ++mnRowCount; +} + +void XclExpProgressBar::ActivateCreateRowsSegment() +{ + OSL_ENSURE( (0 <= GetCurrScTab()) && (GetCurrScTab() < GetTabInfo().GetScTabCount()), + "XclExpProgressBar::ActivateCreateRowsSegment - invalid sheet" ); + sal_Int32 nSeg = maSubSegRowCreate[ GetCurrScTab() ]; + OSL_ENSURE( nSeg != SCF_INV_SEGMENT, "XclExpProgressBar::ActivateCreateRowsSegment - invalid segment" ); + if( nSeg != SCF_INV_SEGMENT ) + { + mpSubProgress = mpSubRowCreate; + mpSubProgress->ActivateSegment( nSeg ); + } + else + mpSubProgress = nullptr; +} + +void XclExpProgressBar::ActivateFinalRowsSegment() +{ + if( !mpSubRowFinal && (mnRowCount > 0) ) + { + mpSubRowFinal = &mxProgress->GetSegmentProgressBar( mnSegRowFinal ); + mpSubRowFinal->AddSegment( mnRowCount ); + } + mpSubProgress = mpSubRowFinal; + if( mpSubProgress ) + mpSubProgress->Activate(); +} + +void XclExpProgressBar::Progress() +{ + if( mpSubProgress && !mpSubProgress->IsFull() ) + mpSubProgress->Progress(); +} + +// Calc->Excel cell address/range conversion ================================== + +namespace { + +/** Fills the passed Excel address with the passed Calc cell coordinates without checking any limits. */ +void lclFillAddress( XclAddress& rXclPos, SCCOL nScCol, SCROW nScRow ) +{ + rXclPos.mnCol = static_cast< sal_uInt16 >( nScCol ); + rXclPos.mnRow = static_cast< sal_uInt32 >( nScRow ); +} + +} // namespace + +XclExpAddressConverter::XclExpAddressConverter( const XclExpRoot& rRoot ) : + XclAddressConverterBase( rRoot.GetTracer(), rRoot.GetXclMaxPos() ) +{ +} + +// cell address --------------------------------------------------------------- + +bool XclExpAddressConverter::CheckAddress( const ScAddress& rScPos, bool bWarn ) +{ + // ScAddress::operator<=() doesn't do what we want here + bool bValidCol = (0 <= rScPos.Col()) && (rScPos.Col() <= maMaxPos.Col()); + bool bValidRow = (0 <= rScPos.Row()) && (rScPos.Row() <= maMaxPos.Row()); + bool bValidTab = (0 <= rScPos.Tab()) && (rScPos.Tab() <= maMaxPos.Tab()); + + bool bValid = bValidCol && bValidRow && bValidTab; + if( !bValid ) + { + mbColTrunc |= !bValidCol; + mbRowTrunc |= !bValidRow; + } + if( !bValid && bWarn ) + { + mbTabTrunc |= (rScPos.Tab() > maMaxPos.Tab()); // do not warn for deleted refs + mrTracer.TraceInvalidAddress( rScPos, maMaxPos ); + } + return bValid; +} + +bool XclExpAddressConverter::ConvertAddress( XclAddress& rXclPos, + const ScAddress& rScPos, bool bWarn ) +{ + bool bValid = CheckAddress( rScPos, bWarn ); + if( bValid ) + lclFillAddress( rXclPos, rScPos.Col(), rScPos.Row() ); + return bValid; +} + +XclAddress XclExpAddressConverter::CreateValidAddress( const ScAddress& rScPos, bool bWarn ) +{ + XclAddress aXclPos( ScAddress::UNINITIALIZED ); + if( !ConvertAddress( aXclPos, rScPos, bWarn ) ) + lclFillAddress( aXclPos, ::std::min( rScPos.Col(), maMaxPos.Col() ), ::std::min( rScPos.Row(), maMaxPos.Row() ) ); + return aXclPos; +} + +// cell range ----------------------------------------------------------------- + +bool XclExpAddressConverter::CheckRange( const ScRange& rScRange, bool bWarn ) +{ + return CheckAddress( rScRange.aStart, bWarn ) && CheckAddress( rScRange.aEnd, bWarn ); +} + +bool XclExpAddressConverter::ValidateRange( ScRange& rScRange, bool bWarn ) +{ + rScRange.PutInOrder(); + + // check start position + bool bValidStart = CheckAddress( rScRange.aStart, bWarn ); + if( bValidStart ) + { + // check & correct end position + ScAddress& rScEnd = rScRange.aEnd; + if( !CheckAddress( rScEnd, bWarn ) ) + { + rScEnd.SetCol( ::std::min( rScEnd.Col(), maMaxPos.Col() ) ); + rScEnd.SetRow( ::std::min( rScEnd.Row(), maMaxPos.Row() ) ); + rScEnd.SetTab( ::std::min( rScEnd.Tab(), maMaxPos.Tab() ) ); + } + } + + return bValidStart; +} + +bool XclExpAddressConverter::ConvertRange( XclRange& rXclRange, + const ScRange& rScRange, bool bWarn ) +{ + // check start position + bool bValidStart = CheckAddress( rScRange.aStart, bWarn ); + if( bValidStart ) + { + lclFillAddress( rXclRange.maFirst, rScRange.aStart.Col(), rScRange.aStart.Row() ); + + // check & correct end position + SCCOL nScCol2 = rScRange.aEnd.Col(); + SCROW nScRow2 = rScRange.aEnd.Row(); + if( !CheckAddress( rScRange.aEnd, bWarn ) ) + { + nScCol2 = ::std::min( nScCol2, maMaxPos.Col() ); + nScRow2 = ::std::min( nScRow2, maMaxPos.Row() ); + } + lclFillAddress( rXclRange.maLast, nScCol2, nScRow2 ); + } + return bValidStart; +} + +// cell range list ------------------------------------------------------------ + +void XclExpAddressConverter::ValidateRangeList( ScRangeList& rScRanges, bool bWarn ) +{ + for ( size_t nRange = rScRanges.size(); nRange > 0; ) + { + ScRange & rScRange = rScRanges[ --nRange ]; + if( !CheckRange( rScRange, bWarn ) ) + rScRanges.Remove(nRange); + } +} + +void XclExpAddressConverter::ConvertRangeList( XclRangeList& rXclRanges, + const ScRangeList& rScRanges, bool bWarn ) +{ + rXclRanges.clear(); + for( size_t nPos = 0, nCount = rScRanges.size(); nPos < nCount; ++nPos ) + { + const ScRange & rScRange = rScRanges[ nPos ]; + XclRange aXclRange( ScAddress::UNINITIALIZED ); + if( ConvertRange( aXclRange, rScRange, bWarn ) ) + rXclRanges.push_back( aXclRange ); + } +} + +// EditEngine->String conversion ============================================== + +namespace { + +OUString lclGetUrlRepresentation( const SvxURLField& rUrlField ) +{ + const OUString& aRepr = rUrlField.GetRepresentation(); + // no representation -> use URL + return aRepr.isEmpty() ? rUrlField.GetURL() : aRepr; +} + +} // namespace + +XclExpHyperlinkHelper::XclExpHyperlinkHelper( const XclExpRoot& rRoot, const ScAddress& rScPos ) : + XclExpRoot( rRoot ), + maScPos( rScPos ), + mbMultipleUrls( false ) +{ +} + +XclExpHyperlinkHelper::~XclExpHyperlinkHelper() +{ +} + +OUString XclExpHyperlinkHelper::ProcessUrlField( const SvxURLField& rUrlField ) +{ + OUString aUrlRepr; + + if( GetBiff() == EXC_BIFF8 ) // no HLINK records in BIFF2-BIFF7 + { + // there was/is already a HLINK record + mbMultipleUrls = static_cast< bool >(mxLinkRec); + + mxLinkRec = new XclExpHyperlink( GetRoot(), rUrlField, maScPos ); + + if( const OUString* pRepr = mxLinkRec->GetRepr() ) + aUrlRepr = *pRepr; + + // add URL to note text + maUrlList = ScGlobal::addToken( maUrlList, rUrlField.GetURL(), '\n' ); + } + + // no hyperlink representation from Excel HLINK record -> use it from text field + return aUrlRepr.isEmpty() ? lclGetUrlRepresentation(rUrlField) : aUrlRepr; +} + +bool XclExpHyperlinkHelper::HasLinkRecord() const +{ + return !mbMultipleUrls && mxLinkRec; +} + +XclExpHyperlinkHelper::XclExpHyperlinkRef XclExpHyperlinkHelper::GetLinkRecord() const +{ + if( HasLinkRecord() ) + return mxLinkRec; + return XclExpHyperlinkRef(); +} + +namespace { + +/** Creates a new formatted string from the passed unformatted string. + + Creates a Unicode string or a byte string, depending on the current BIFF + version contained in the passed XclExpRoot object. May create a formatted + string object, if the text contains different script types. + + @param pCellAttr + Cell attributes used for font formatting. + @param nFlags + Modifiers for string export. + @param nMaxLen + The maximum number of characters to store in this string. + @return + The new string object. + */ +XclExpStringRef lclCreateFormattedString( + const XclExpRoot& rRoot, const OUString& rText, const ScPatternAttr* pCellAttr, + XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + /* Create an empty Excel string object with correctly initialized BIFF mode, + because this function only uses Append() functions that require this. */ + XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen ); + + // script type handling + Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator(); + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + // #i63255# get script type for leading weak characters + sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rText ); + + // font buffer and cell item set + XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer(); + const SfxItemSet& rItemSet = pCellAttr ? pCellAttr->GetItemSet() : rRoot.GetDoc().GetDefPattern()->GetItemSet(); + + // process all script portions + sal_Int32 nPortionPos = 0; + sal_Int32 nTextLen = rText.getLength(); + while( nPortionPos < nTextLen ) + { + // get script type and end position of next script portion + sal_Int16 nScript = xBreakIt->getScriptType( rText, nPortionPos ); + sal_Int32 nPortionEnd = xBreakIt->endOfScript( rText, nPortionPos, nScript ); + + // reuse previous script for following weak portions + if( nScript == ApiScriptType::WEAK ) + nScript = nLastScript; + + // construct font from current text portion + SvxFont aFont( XclExpFontHelper::GetFontFromItemSet( rRoot, rItemSet, nScript ) ); + + // Excel start position of this portion + sal_Int32 nXclPortionStart = xString->Len(); + // add portion text to Excel string + XclExpStringHelper::AppendString( *xString, rRoot, rText.subView( nPortionPos, nPortionEnd - nPortionPos ) ); + if( nXclPortionStart < xString->Len() ) + { + // insert font into buffer + sal_uInt16 nFontIdx = rFontBuffer.Insert( aFont, EXC_COLOR_CELLTEXT ); + // insert font index into format run vector + xString->AppendFormat( nXclPortionStart, nFontIdx ); + } + + // go to next script portion + nLastScript = nScript; + nPortionPos = nPortionEnd; + } + + return xString; +} + +/** Creates a new formatted string from an edit engine text object. + + Creates a Unicode string or a byte string, depending on the current BIFF + version contained in the passed XclExpRoot object. + + @param rEE + The edit engine in use. The text object must already be set. + @param nFlags + Modifiers for string export. + @param nMaxLen + The maximum number of characters to store in this string. + @return + The new string object. + */ +XclExpStringRef lclCreateFormattedString( + const XclExpRoot& rRoot, EditEngine& rEE, XclExpHyperlinkHelper* pLinkHelper, + XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + /* Create an empty Excel string object with correctly initialized BIFF mode, + because this function only uses Append() functions that require this. */ + XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen ); + + // font buffer and helper item set for edit engine -> Calc item conversion + XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer(); + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *rRoot.GetDoc().GetPool() ); + + // script type handling + Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator(); + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + // #i63255# get script type for leading weak characters + sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rEE.GetText() ); + + // process all paragraphs + sal_Int32 nParaCount = rEE.GetParagraphCount(); + for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara ) + { + ESelection aSel( nPara, 0 ); + OUString aParaText( rEE.GetText( nPara ) ); + + std::vector<sal_Int32> aPosList; + rEE.GetPortions( nPara, aPosList ); + + // process all portions in the paragraph + for( const auto& rPos : aPosList ) + { + aSel.nEndPos = rPos; + OUString aXclPortionText = aParaText.copy( aSel.nStartPos, aSel.nEndPos - aSel.nStartPos ); + + aItemSet.ClearItem(); + SfxItemSet aEditSet( rEE.GetAttribs( aSel ) ); + ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet ); + + // get escapement value + short nEsc = aEditSet.Get( EE_CHAR_ESCAPEMENT ).GetEsc(); + + // process text fields + bool bIsHyperlink = false; + if( aSel.nStartPos + 1 == aSel.nEndPos ) + { + // test if the character is a text field + if( const SvxFieldItem* pItem = aEditSet.GetItemIfSet( EE_FEATURE_FIELD, false ) ) + { + const SvxFieldData* pField = pItem->GetField(); + if( const SvxURLField* pUrlField = dynamic_cast<const SvxURLField*>( pField ) ) + { + // convert URL field to string representation + aXclPortionText = pLinkHelper ? + pLinkHelper->ProcessUrlField( *pUrlField ) : + lclGetUrlRepresentation( *pUrlField ); + bIsHyperlink = true; + } + else + { + OSL_FAIL( "lclCreateFormattedString - unknown text field" ); + aXclPortionText.clear(); + } + } + } + + // Excel start position of this portion + sal_Int32 nXclPortionStart = xString->Len(); + // add portion text to Excel string + XclExpStringHelper::AppendString( *xString, rRoot, aXclPortionText ); + if( (nXclPortionStart < xString->Len()) || (aParaText.isEmpty()) ) + { + /* Construct font from current edit engine text portion. Edit engine + creates different portions for different script types, no need to loop. */ + sal_Int16 nScript = xBreakIt->getScriptType( aXclPortionText, 0 ); + if( nScript == ApiScriptType::WEAK ) + nScript = nLastScript; + SvxFont aFont( XclExpFontHelper::GetFontFromItemSet( rRoot, aItemSet, nScript ) ); + nLastScript = nScript; + + // add escapement + aFont.SetEscapement( nEsc ); + // modify automatic font color for hyperlinks + if( bIsHyperlink && aItemSet.Get( ATTR_FONT_COLOR ).GetValue() == COL_AUTO) + aFont.SetColor( COL_LIGHTBLUE ); + + // insert font into buffer + sal_uInt16 nFontIdx = rFontBuffer.Insert( aFont, EXC_COLOR_CELLTEXT ); + // insert font index into format run vector + xString->AppendFormat( nXclPortionStart, nFontIdx ); + } + + aSel.nStartPos = aSel.nEndPos; + } + + // add trailing newline (important for correct character index calculation) + if( nPara + 1 < nParaCount ) + XclExpStringHelper::AppendChar( *xString, rRoot, '\n' ); + } + + return xString; +} + +} // namespace + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + XclExpStringRef xString = std::make_shared<XclExpString>(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + xString->Assign( rString, nFlags, nMaxLen ); + else + xString->AssignByte( rString, rRoot.GetTextEncoding(), nFlags, nMaxLen ); + return xString; +} + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, sal_Unicode cChar, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + XclExpStringRef xString = CreateString( rRoot, OUString(), nFlags, nMaxLen ); + AppendChar( *xString, rRoot, cChar ); + return xString; +} + +void XclExpStringHelper::AppendString( XclExpString& rXclString, const XclExpRoot& rRoot, std::u16string_view rString ) +{ + if( rRoot.GetBiff() == EXC_BIFF8 ) + rXclString.Append( rString ); + else + rXclString.AppendByte( rString, rRoot.GetTextEncoding() ); +} + +void XclExpStringHelper::AppendChar( XclExpString& rXclString, const XclExpRoot& rRoot, sal_Unicode cChar ) +{ + if( rRoot.GetBiff() == EXC_BIFF8 ) + rXclString.Append( rtl::OUStringChar(cChar) ); + else + rXclString.AppendByte( cChar, rRoot.GetTextEncoding() ); +} + +XclExpStringRef XclExpStringHelper::CreateCellString( + const XclExpRoot& rRoot, const OUString& rString, const ScPatternAttr* pCellAttr, + XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + return lclCreateFormattedString(rRoot, rString, pCellAttr, nFlags, nMaxLen); +} + +XclExpStringRef XclExpStringHelper::CreateCellString( + const XclExpRoot& rRoot, const EditTextObject& rEditText, const ScPatternAttr* pCellAttr, + XclExpHyperlinkHelper& rLinkHelper, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + XclExpStringRef xString; + + // formatted cell + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + bool bOldUpdateMode = rEE.SetUpdateLayout( true ); + + // default items + const SfxItemSet& rItemSet = pCellAttr ? pCellAttr->GetItemSet() : rRoot.GetDoc().GetDefPattern()->GetItemSet(); + auto pEEItemSet = std::make_unique<SfxItemSet>( rEE.GetEmptyItemSet() ); + ScPatternAttr::FillToEditItemSet( *pEEItemSet, rItemSet ); + rEE.SetDefaults( std::move(pEEItemSet) ); // edit engine takes ownership + + // create the string + rEE.SetTextCurrentDefaults(rEditText); + xString = lclCreateFormattedString( rRoot, rEE, &rLinkHelper, nFlags, nMaxLen ); + rEE.SetUpdateLayout( bOldUpdateMode ); + + return xString; +} + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, const SdrTextObj& rTextObj, + XclStrFlags nFlags ) +{ + XclExpStringRef xString; + if( const OutlinerParaObject* pParaObj = rTextObj.GetOutlinerParaObject() ) + { + EditEngine& rEE = rRoot.GetDrawEditEngine(); + bool bOldUpdateMode = rEE.SetUpdateLayout( true ); + // create the string + rEE.SetText( pParaObj->GetTextObject() ); + xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN ); + rEE.SetUpdateLayout( bOldUpdateMode ); + // limit formats - TODO: BIFF dependent + if( !xString->IsEmpty() ) + { + xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 ); + xString->AppendTrailingFormat( EXC_FONT_APP ); + } + } + else + { + OSL_FAIL( "XclExpStringHelper::CreateString - textbox without para object" ); + // create BIFF dependent empty Excel string + xString = CreateString( rRoot, OUString(), nFlags ); + } + return xString; +} + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, const EditTextObject& rEditObj, + XclStrFlags nFlags ) +{ + XclExpStringRef xString; + EditEngine& rEE = rRoot.GetDrawEditEngine(); + bool bOldUpdateMode = rEE.SetUpdateLayout( true ); + rEE.SetText( rEditObj ); + xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN ); + rEE.SetUpdateLayout( bOldUpdateMode ); + // limit formats - TODO: BIFF dependent + if( !xString->IsEmpty() ) + { + xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 ); + xString->AppendTrailingFormat( EXC_FONT_APP ); + } + return xString; +} + +sal_Int16 XclExpStringHelper::GetLeadingScriptType( const XclExpRoot& rRoot, const OUString& rString ) +{ + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator(); + sal_Int32 nStrPos = 0; + sal_Int32 nStrLen = rString.getLength(); + sal_Int16 nScript = ApiScriptType::WEAK; + while( (nStrPos < nStrLen) && (nScript == ApiScriptType::WEAK) ) + { + nScript = xBreakIt->getScriptType( rString, nStrPos ); + nStrPos = xBreakIt->endOfScript( rString, nStrPos, nScript ); + } + return (nScript == ApiScriptType::WEAK) ? rRoot.GetDefApiScript() : nScript; +} + +// Header/footer conversion =================================================== + +XclExpHFConverter::XclExpHFConverter( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mrEE( rRoot.GetHFEditEngine() ), + mnTotalHeight( 0 ) +{ +} + +void XclExpHFConverter::GenerateString( + const EditTextObject* pLeftObj, + const EditTextObject* pCenterObj, + const EditTextObject* pRightObj ) +{ + maHFString.clear(); + mnTotalHeight = 0; + AppendPortion( pLeftObj, 'L' ); + AppendPortion( pCenterObj, 'C' ); + AppendPortion( pRightObj, 'R' ); +} + +void XclExpHFConverter::AppendPortion( const EditTextObject* pTextObj, sal_Unicode cPortionCode ) +{ + if( !pTextObj ) return; + + OUString aText; + sal_Int32 nHeight = 0; + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *GetDoc().GetPool() ); + + // edit engine + bool bOldUpdateMode = mrEE.SetUpdateLayout( true ); + mrEE.SetText( *pTextObj ); + + // font information + XclFontData aFontData, aNewData; + if( const XclExpFont* pFirstFont = GetFontBuffer().GetFont( EXC_FONT_APP ) ) + { + aFontData = pFirstFont->GetFontData(); + aFontData.mnHeight = (aFontData.mnHeight + 10) / 20; // using pt here, not twips + } + else + aFontData.mnHeight = 10; + + const FontList* pFontList = nullptr; + if( SfxObjectShell* pDocShell = GetDocShell() ) + { + if( const SvxFontListItem* pInfoItem = static_cast< const SvxFontListItem* >( + pDocShell->GetItem( SID_ATTR_CHAR_FONTLIST ) ) ) + pFontList = pInfoItem->GetFontList(); + } + + sal_Int32 nParaCount = mrEE.GetParagraphCount(); + for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara ) + { + ESelection aSel( nPara, 0 ); + OUStringBuffer aParaText; + sal_Int32 nParaHeight = 0; + std::vector<sal_Int32> aPosList; + mrEE.GetPortions( nPara, aPosList ); + + for( const auto& rPos : aPosList ) + { + aSel.nEndPos = rPos; + if( aSel.nStartPos < aSel.nEndPos ) + { + +// --- font attributes --- + + vcl::Font aFont; + aItemSet.ClearItem(); + SfxItemSet aEditSet( mrEE.GetAttribs( aSel ) ); + ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet ); + ScPatternAttr::GetFont( aFont, aItemSet, SC_AUTOCOL_RAW ); + + // font name and style + aNewData.maName = XclTools::GetXclFontName( aFont.GetFamilyName() ); + aNewData.mnWeight = (aFont.GetWeight() > WEIGHT_NORMAL) ? EXC_FONTWGHT_BOLD : EXC_FONTWGHT_NORMAL; + aNewData.mbItalic = (aFont.GetItalic() != ITALIC_NONE); + bool bNewFont = (aFontData.maName != aNewData.maName); + bool bNewStyle = (aFontData.mnWeight != aNewData.mnWeight) || + (aFontData.mbItalic != aNewData.mbItalic); + if( bNewFont || (bNewStyle && pFontList) ) + { + aParaText.append("&\"" + aNewData.maName); + if( pFontList ) + { + FontMetric aFontMetric( pFontList->Get( + aNewData.maName, + (aNewData.mnWeight > EXC_FONTWGHT_NORMAL) ? WEIGHT_BOLD : WEIGHT_NORMAL, + aNewData.mbItalic ? ITALIC_NORMAL : ITALIC_NONE ) ); + aNewData.maStyle = pFontList->GetStyleName( aFontMetric ); + if( !aNewData.maStyle.isEmpty() ) + aParaText.append("," + aNewData.maStyle); + } + aParaText.append("\""); + } + + // height + // is calculated wrong in ScPatternAttr::GetFromEditItemSet, because already in twips and not 100thmm + // -> get it directly from edit engine item set + aNewData.mnHeight = ulimit_cast< sal_uInt16 >( aEditSet.Get( EE_CHAR_FONTHEIGHT ).GetHeight() ); + aNewData.mnHeight = (aNewData.mnHeight + 10) / 20; + bool bFontHtChanged = (aFontData.mnHeight != aNewData.mnHeight); + if( bFontHtChanged ) + aParaText.append("&" + OUString::number(aNewData.mnHeight)); + // update maximum paragraph height, convert to twips + nParaHeight = ::std::max< sal_Int32 >( nParaHeight, aNewData.mnHeight * 20 ); + + // underline + aNewData.mnUnderline = EXC_FONTUNDERL_NONE; + switch( aFont.GetUnderline() ) + { + case LINESTYLE_NONE: aNewData.mnUnderline = EXC_FONTUNDERL_NONE; break; + case LINESTYLE_SINGLE: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE; break; + case LINESTYLE_DOUBLE: aNewData.mnUnderline = EXC_FONTUNDERL_DOUBLE; break; + default: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE; + } + if( aFontData.mnUnderline != aNewData.mnUnderline ) + { + sal_uInt8 nTmpUnderl = (aNewData.mnUnderline == EXC_FONTUNDERL_NONE) ? + aFontData.mnUnderline : aNewData.mnUnderline; + (nTmpUnderl == EXC_FONTUNDERL_SINGLE)? aParaText.append("&U") : aParaText.append("&E"); + } + + // font color + aNewData.maColor = aFont.GetColor(); + if ( !aFontData.maColor.IsRGBEqual( aNewData.maColor ) ) + { + aParaText.append("&K" + aNewData.maColor.AsRGBHexString()); + } + + // strikeout + aNewData.mbStrikeout = (aFont.GetStrikeout() != STRIKEOUT_NONE); + if( aFontData.mbStrikeout != aNewData.mbStrikeout ) + aParaText.append("&S"); + + // super/sub script + const SvxEscapementItem& rEscapeItem = aEditSet.Get( EE_CHAR_ESCAPEMENT ); + aNewData.SetScEscapement( rEscapeItem.GetEsc() ); + if( aFontData.mnEscapem != aNewData.mnEscapem ) + { + switch(aNewData.mnEscapem) + { + // close the previous super/sub script. + case EXC_FONTESC_NONE: (aFontData.mnEscapem == EXC_FONTESC_SUPER) ? aParaText.append("&X") : aParaText.append("&Y"); break; + case EXC_FONTESC_SUPER: aParaText.append("&X"); break; + case EXC_FONTESC_SUB: aParaText.append("&Y"); break; + default: break; + } + } + + aFontData = aNewData; + +// --- text content or text fields --- + + const SvxFieldItem* pItem; + if( (aSel.nStartPos + 1 == aSel.nEndPos) && // fields are single characters + (pItem = aEditSet.GetItemIfSet( EE_FEATURE_FIELD, false )) ) + { + if( const SvxFieldData* pFieldData = pItem->GetField() ) + { + if( dynamic_cast<const SvxPageField*>( pFieldData) != nullptr ) + aParaText.append("&P"); + else if( dynamic_cast<const SvxPagesField*>( pFieldData) != nullptr ) + aParaText.append("&N"); + else if( dynamic_cast<const SvxDateField*>( pFieldData) != nullptr ) + aParaText.append("&D"); + else if( dynamic_cast<const SvxTimeField*>( pFieldData) != nullptr || dynamic_cast<const SvxExtTimeField*>( pFieldData) != nullptr ) + aParaText.append("&T"); + else if( dynamic_cast<const SvxTableField*>( pFieldData) != nullptr ) + aParaText.append("&A"); + else if( dynamic_cast<const SvxFileField*>( pFieldData) != nullptr ) // title -> file name + aParaText.append("&F"); + else if( const SvxExtFileField* pFileField = dynamic_cast<const SvxExtFileField*>( pFieldData ) ) + { + switch( pFileField->GetFormat() ) + { + case SvxFileFormat::NameAndExt: + case SvxFileFormat::NameOnly: + aParaText.append("&F"); + break; + case SvxFileFormat::PathOnly: + aParaText.append("&Z"); + break; + case SvxFileFormat::PathFull: + aParaText.append("&Z&F"); + break; + default: + OSL_FAIL( "XclExpHFConverter::AppendPortion - unknown file field" ); + } + } + } + } + else + { + OUString aPortionText( mrEE.GetText( aSel ) ); + aPortionText = aPortionText.replaceAll( "&", "&&" ); + // #i17440# space between font height and numbers in text + if( bFontHtChanged && aParaText.getLength() && !aPortionText.isEmpty() ) + { + sal_Unicode cLast = aParaText[ aParaText.getLength() - 1 ]; + sal_Unicode cFirst = aPortionText[0]; + if( ('0' <= cLast) && (cLast <= '9') && ('0' <= cFirst) && (cFirst <= '9') ) + aParaText.append(" "); + } + aParaText.append(aPortionText); + } + } + + aSel.nStartPos = aSel.nEndPos; + } + + aText = ScGlobal::addToken( aText, aParaText, '\n' ); + aParaText.setLength(0); + if( nParaHeight == 0 ) + nParaHeight = aFontData.mnHeight * 20; // points -> twips + nHeight += nParaHeight; + } + + mrEE.SetUpdateLayout( bOldUpdateMode ); + + if( !aText.isEmpty() ) + { + maHFString += "&" + OUStringChar(cPortionCode) + aText; + mnTotalHeight = ::std::max( mnTotalHeight, nHeight ); + } +} + +// URL conversion ============================================================= + +namespace { + +/** Encodes special parts of the URL, i.e. directory separators and volume names. + @param pTableName Pointer to a table name to be encoded in this URL, or 0. */ +OUString lclEncodeDosUrl( + XclBiff eBiff, const OUString& rUrl, std::u16string_view rBase, const OUString* pTableName) +{ + OUStringBuffer aBuf; + + if (!rUrl.isEmpty()) + { + OUString aOldUrl = rUrl; + aBuf.append(EXC_URLSTART_ENCODED); + + if ( aOldUrl.getLength() > 2 && aOldUrl.startsWith("\\\\") ) + { + // UNC + aBuf.append(EXC_URL_DOSDRIVE).append('@'); + aOldUrl = aOldUrl.copy(2); + } + else if ( aOldUrl.getLength() > 2 && aOldUrl.match(":\\", 1) ) + { + // drive letter + sal_Unicode cThisDrive = rBase.empty() ? ' ' : rBase[0]; + sal_Unicode cDrive = aOldUrl[0]; + if (cThisDrive == cDrive) + // This document and the referenced document are under the same drive. + aBuf.append(EXC_URL_DRIVEROOT); + else + aBuf.append(EXC_URL_DOSDRIVE).append(cDrive); + aOldUrl = aOldUrl.copy(3); + } + else + { + // URL probably points to a document on a Unix-like file system + aBuf.append(EXC_URL_DRIVEROOT); + } + + // directories + sal_Int32 nPos = -1; + while((nPos = aOldUrl.indexOf('\\')) != -1) + { + if ( aOldUrl.startsWith("..") ) + // parent dir (NOTE: the MS-XLS spec doesn't mention this, and + // Excel seems confused by this token). + aBuf.append(EXC_URL_PARENTDIR); + else + aBuf.append(aOldUrl.subView(0,nPos)).append(EXC_URL_SUBDIR); + + aOldUrl = aOldUrl.copy(nPos + 1); + } + + // file name + if (pTableName) // enclose file name in brackets if table name follows + aBuf.append('[').append(aOldUrl).append(']'); + else + aBuf.append(aOldUrl); + } + else // empty URL -> self reference + { + switch( eBiff ) + { + case EXC_BIFF5: + aBuf.append(pTableName ? EXC_URLSTART_SELFENCODED : EXC_URLSTART_SELF); + break; + case EXC_BIFF8: + DBG_ASSERT( pTableName, "lclEncodeDosUrl - sheet name required for BIFF8" ); + aBuf.append(EXC_URLSTART_SELF); + break; + default: + DBG_ERROR_BIFF(); + } + } + + // table name + if (pTableName) + aBuf.append(*pTableName); + + // VirtualPath must be shorter than 255 chars ([MS-XLS].pdf 2.5.277) + // It's better to truncate, than generate invalid file that Excel cannot open. + if (aBuf.getLength() > 255) + aBuf.setLength(255); + + return aBuf.makeStringAndClear(); +} + +} // namespace + +OUString XclExpUrlHelper::EncodeUrl( const XclExpRoot& rRoot, std::u16string_view rAbsUrl, const OUString* pTableName ) +{ + OUString aDosUrl = INetURLObject(rAbsUrl).getFSysPath(FSysStyle::Dos); + OUString aDosBase = INetURLObject(rRoot.GetBasePath()).getFSysPath(FSysStyle::Dos); + return lclEncodeDosUrl(rRoot.GetBiff(), aDosUrl, aDosBase, pTableName); +} + +OUString XclExpUrlHelper::EncodeDde( std::u16string_view rApplic, std::u16string_view rTopic ) +{ + return rApplic + OUStringChar(EXC_DDE_DELIM) + rTopic; +} + +// Cached Value Lists ========================================================= + +XclExpCachedMatrix::XclExpCachedMatrix( const ScMatrix& rMatrix ) + : mrMatrix( rMatrix ) +{ + mrMatrix.IncRef(); +} +XclExpCachedMatrix::~XclExpCachedMatrix() +{ + mrMatrix.DecRef(); +} + +void XclExpCachedMatrix::GetDimensions( SCSIZE & nCols, SCSIZE & nRows ) const +{ + mrMatrix.GetDimensions( nCols, nRows ); + + OSL_ENSURE( nCols && nRows, "XclExpCachedMatrix::GetDimensions - empty matrix" ); + OSL_ENSURE( nCols <= 256, "XclExpCachedMatrix::GetDimensions - too many columns" ); +} + +std::size_t XclExpCachedMatrix::GetSize() const +{ + SCSIZE nCols, nRows; + + GetDimensions( nCols, nRows ); + + /* The returned size may be wrong if the matrix contains strings. The only + effect is that the export stream has to update a wrong record size which is + faster than to iterate through all cached values and calculate their sizes. */ + return 3 + 9 * (nCols * nRows); +} + +void XclExpCachedMatrix::Save( XclExpStream& rStrm ) const +{ + SCSIZE nCols, nRows; + + GetDimensions( nCols, nRows ); + + if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 ) + // in BIFF2-BIFF7: 256 columns represented by 0 columns + rStrm << static_cast< sal_uInt8 >( nCols ) << static_cast< sal_uInt16 >( nRows ); + else + // in BIFF8: columns and rows decreased by 1 + rStrm << static_cast< sal_uInt8 >( nCols - 1 ) << static_cast< sal_uInt16 >( nRows - 1 ); + + for( SCSIZE nRow = 0; nRow < nRows; ++nRow ) + { + for( SCSIZE nCol = 0; nCol < nCols; ++nCol ) + { + ScMatrixValue nMatVal = mrMatrix.Get( nCol, nRow ); + + FormulaError nScError; + if( ScMatValType::Empty == nMatVal.nType ) + { + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_EMPTY; + rStrm.WriteZeroBytes( 8 ); + } + else if( ScMatrix::IsNonValueType( nMatVal.nType ) ) + { + XclExpString aStr( nMatVal.GetString().getString(), XclStrFlags::NONE ); + rStrm.SetSliceSize( 6 ); + rStrm << EXC_CACHEDVAL_STRING << aStr; + } + else if( ScMatValType::Boolean == nMatVal.nType ) + { + sal_Int8 nBool = sal_Int8(nMatVal.GetBoolean()); + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_BOOL << nBool; + rStrm.WriteZeroBytes( 7 ); + } + else if( (nScError = nMatVal.GetError()) != FormulaError::NONE ) + { + sal_Int8 nError ( XclTools::GetXclErrorCode( nScError ) ); + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_ERROR << nError; + rStrm.WriteZeroBytes( 7 ); + } + else + { + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_DOUBLE << nMatVal.fVal; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xelink.cxx b/sc/source/filter/excel/xelink.cxx new file mode 100644 index 000000000..4bdc24335 --- /dev/null +++ b/sc/source/filter/excel/xelink.cxx @@ -0,0 +1,2660 @@ +/* -*- 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 <xelink.hxx> + +#include <algorithm> +#include <formula/errorcodes.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/relationship.hxx> +#include <unotools/collatorwrapper.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <sal/log.hxx> +#include <document.hxx> +#include <scextopt.hxx> +#include <externalrefmgr.hxx> +#include <tokenarray.hxx> +#include <xecontent.hxx> +#include <xeformula.hxx> +#include <xehelper.hxx> +#include <xllink.hxx> +#include <xltools.hxx> + +#include <vector> +#include <memory> +#include <string_view> + +using ::std::unique_ptr; +using ::std::vector; +using ::com::sun::star::uno::Any; + +using namespace oox; + +// *** Helper classes *** + +// External names ============================================================= + +namespace { + +/** This is a base class for any external name (i.e. add-in names or DDE links). + @descr Derived classes implement creation and export of the external names. */ +class XclExpExtNameBase : public XclExpRecord, protected XclExpRoot +{ +public: + /** @param nFlags The flags to export. */ + explicit XclExpExtNameBase( const XclExpRoot& rRoot, + const OUString& rName, sal_uInt16 nFlags = 0 ); + + /** Returns the name string of the external name. */ + const OUString& GetName() const { return maName; } + +private: + /** Writes the start of the record that is equal in all EXTERNNAME records and calls WriteAddData(). */ + virtual void WriteBody( XclExpStream& rStrm ) override; + /** Called to write additional data following the common record contents. + @descr Derived classes should overwrite this function to write their data. */ + virtual void WriteAddData( XclExpStream& rStrm ); + +protected: + OUString maName; /// Calc name (title) of the external name. + XclExpStringRef mxName; /// Excel name (title) of the external name. + sal_uInt16 mnFlags; /// Flags for record export. +}; + +/** Represents an EXTERNNAME record for an add-in function name. */ +class XclExpExtNameAddIn : public XclExpExtNameBase +{ +public: + explicit XclExpExtNameAddIn( const XclExpRoot& rRoot, const OUString& rName ); + +private: + /** Writes additional record contents. */ + virtual void WriteAddData( XclExpStream& rStrm ) override; +}; + +/** Represents an EXTERNNAME record for a DDE link. */ +class XclExpExtNameDde : public XclExpExtNameBase +{ +public: + explicit XclExpExtNameDde( const XclExpRoot& rRoot, const OUString& rName, + sal_uInt16 nFlags, const ScMatrix* pResults = nullptr ); + +private: + /** Writes additional record contents. */ + virtual void WriteAddData( XclExpStream& rStrm ) override; + +private: + typedef std::shared_ptr< XclExpCachedMatrix > XclExpCachedMatRef; + XclExpCachedMatRef mxMatrix; /// Cached results of the DDE link. +}; + +class XclExpSupbook; + +class XclExpExtName : public XclExpExtNameBase +{ +public: + explicit XclExpExtName( const XclExpRoot& rRoot, const XclExpSupbook& rSupbook, const OUString& rName, + const ScExternalRefCache::TokenArrayRef& rArray ); + + virtual void SaveXml(XclExpXmlStream& rStrm) override; + +private: + /** Writes additional record contents. */ + virtual void WriteAddData( XclExpStream& rStrm ) override; + +private: + const XclExpSupbook& mrSupbook; + unique_ptr<ScTokenArray> mpArray; +}; + +// List of external names ===================================================== + +/** List of all external names of a sheet. */ +class XclExpExtNameBuffer : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit XclExpExtNameBuffer( const XclExpRoot& rRoot ); + + /** Inserts an add-in function name + @return The 1-based (Excel-like) list index of the name. */ + sal_uInt16 InsertAddIn( const OUString& rName ); + /** InsertEuroTool */ + sal_uInt16 InsertEuroTool( const OUString& rName ); + /** Inserts a DDE link. + @return The 1-based (Excel-like) list index of the DDE link. */ + sal_uInt16 InsertDde( std::u16string_view rApplic, std::u16string_view rTopic, const OUString& rItem ); + + sal_uInt16 InsertExtName( const XclExpSupbook& rSupbook, const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ); + + /** Writes the EXTERNNAME record list. */ + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml(XclExpXmlStream& rStrm) override; + +private: + /** Returns the 1-based (Excel-like) list index of the external name or 0, if not found. */ + sal_uInt16 GetIndex( std::u16string_view rName ) const; + /** Appends the passed newly crested external name. + @return The 1-based (Excel-like) list index of the appended name. */ + sal_uInt16 AppendNew( XclExpExtNameBase* pExtName ); + + XclExpRecordList< XclExpExtNameBase > maNameList; /// The list with all EXTERNNAME records. +}; + +// Cached external cells ====================================================== + +/** Stores the contents of a consecutive row of external cells (record CRN). */ +class XclExpCrn : public XclExpRecord +{ +public: + explicit XclExpCrn( SCCOL nScCol, SCROW nScRow, const Any& rValue ); + + /** Returns true, if the passed value could be appended to this record. */ + bool InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ); + + /** Writes the row and child elements. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + virtual void WriteBody( XclExpStream& rStrm ) override; + + static void WriteBool( XclExpStream& rStrm, bool bValue ); + static void WriteDouble( XclExpStream& rStrm, double fValue ); + static void WriteString( XclExpStream& rStrm, const OUString& rValue ); + static void WriteError( XclExpStream& rStrm, sal_uInt8 nErrCode ); + static void WriteEmpty( XclExpStream& rStrm ); + +private: + typedef ::std::vector< Any > CachedValues; + + CachedValues maValues; /// All cached values. + SCCOL mnScCol; /// Column index of the first external cell. + SCROW mnScRow; /// Row index of the external cells. +}; + +class XclExpCrnList; + +/** Represents the record XCT which is the header record of a CRN record list. + */ +class XclExpXct : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit XclExpXct( const XclExpRoot& rRoot, + const OUString& rTabName, sal_uInt16 nSBTab, + ScExternalRefCache::TableTypeRef const & xCacheTable ); + + /** Returns the external sheet name. */ + const XclExpString& GetTabName() const { return maTabName; } + + /** Stores all cells in the given range in the CRN list. */ + void StoreCellRange( const ScRange& rRange ); + + void StoreCell_( const ScAddress& rCell ); + void StoreCellRange_( const ScRange& rRange ); + + /** Writes the XCT and all CRN records. */ + virtual void Save( XclExpStream& rStrm ) override; + + /** Writes the sheetDataSet and child elements. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + ScExternalRefCache::TableTypeRef mxCacheTable; + ScMarkData maUsedCells; /// Contains addresses of all stored cells. + ScRange maBoundRange; /// Bounding box of maUsedCells. + XclExpString maTabName; /// Sheet name of the external sheet. + sal_uInt16 mnSBTab; /// Referred sheet index in SUPBOOK record. + + /** Build the internal representation of records to be saved as BIFF or OOXML. */ + bool BuildCrnList( XclExpCrnList& rCrnRecs ); +}; + +// External documents (EXTERNSHEET/SUPBOOK), base class ======================= + +/** Base class for records representing external sheets/documents. + + In BIFF5/BIFF7, this record is the EXTERNSHEET record containing one sheet + of the own or an external document. In BIFF8, this record is the SUPBOOK + record representing the entire own or external document with all referenced + sheets. + */ +class XclExpExternSheetBase : public XclExpRecord, protected XclExpRoot +{ +public: + explicit XclExpExternSheetBase( const XclExpRoot& rRoot, + sal_uInt16 nRecId, sal_uInt32 nRecSize = 0 ); + +protected: + /** Creates and returns the list of EXTERNNAME records. */ + XclExpExtNameBuffer& GetExtNameBuffer(); + /** Writes the list of EXTERNNAME records. */ + void WriteExtNameBuffer( XclExpStream& rStrm ); + /** Writes the list of externalName elements. */ + void WriteExtNameBufferXml( XclExpXmlStream& rStrm ); + +protected: + typedef std::shared_ptr< XclExpExtNameBuffer > XclExpExtNameBfrRef; + XclExpExtNameBfrRef mxExtNameBfr; /// List of EXTERNNAME records. +}; + +// External documents (EXTERNSHEET, BIFF5/BIFF7) ============================== + +/** Represents an EXTERNSHEET record containing the URL and sheet name of a sheet. + @descr This class is used up to BIFF7 only, writing a BIFF8 EXTERNSHEET + record is implemented directly in the link manager. */ +class XclExpExternSheet : public XclExpExternSheetBase +{ +public: + /** Creates an EXTERNSHEET record containing a special code (i.e. own document or sheet). */ + explicit XclExpExternSheet( const XclExpRoot& rRoot, sal_Unicode cCode ); + /** Creates an EXTERNSHEET record referring to an internal sheet. */ + explicit XclExpExternSheet( const XclExpRoot& rRoot, std::u16string_view rTabName ); + + /** Finds or inserts an EXTERNNAME record for add-ins. + @return The 1-based EXTERNNAME record index; or 0, if the record list is full. */ + sal_uInt16 InsertAddIn( const OUString& rName ); + + /** Writes the EXTERNSHEET and all EXTERNNAME, XCT and CRN records. */ + virtual void Save( XclExpStream& rStrm ) override; + +private: + /** Initializes the record data with the passed encoded URL. */ + void Init( std::u16string_view rEncUrl ); + /** Writes the contents of the EXTERNSHEET record. */ + virtual void WriteBody( XclExpStream& rStrm ) override; + +private: + XclExpString maTabName; /// The name of the sheet. +}; + +// External documents (SUPBOOK, BIFF8) ======================================== + +/** The SUPBOOK record contains data for an external document (URL, sheet names, external values). */ +class XclExpSupbook : public XclExpExternSheetBase +{ +public: + /** Creates a SUPBOOK record for internal references. */ + explicit XclExpSupbook( const XclExpRoot& rRoot, sal_uInt16 nXclTabCount ); + /** Creates a SUPBOOK record for add-in functions. */ + explicit XclExpSupbook( const XclExpRoot& rRoot ); + /** EUROTOOL SUPBOOK */ + explicit XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl, XclSupbookType ); + /** Creates a SUPBOOK record for an external document. */ + explicit XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl ); + /** Creates a SUPBOOK record for a DDE link. */ + explicit XclExpSupbook( const XclExpRoot& rRoot, const OUString& rApplic, const OUString& rTopic ); + + /** Returns true, if this SUPBOOK contains the passed URL of an external document. */ + bool IsUrlLink( std::u16string_view rUrl ) const; + /** Returns true, if this SUPBOOK contains the passed DDE link. */ + bool IsDdeLink( std::u16string_view rApplic, std::u16string_view rTopic ) const; + /** Fills the passed reference log entry with the URL and sheet names. */ + void FillRefLogEntry( XclExpRefLogEntry& rRefLogEntry, + sal_uInt16 nFirstSBTab, sal_uInt16 nLastSBTab ) const; + + /** Stores all cells in the given range in the CRN list of the specified SUPBOOK sheet. */ + void StoreCellRange( const ScRange& rRange, sal_uInt16 nSBTab ); + + void StoreCell_( sal_uInt16 nSBTab, const ScAddress& rCell ); + void StoreCellRange_( sal_uInt16 nSBTab, const ScRange& rRange ); + + sal_uInt16 GetTabIndex( const OUString& rTabName ) const; + sal_uInt16 GetTabCount() const; + + /** Inserts a new sheet name into the SUPBOOK and returns the SUPBOOK internal sheet index. */ + sal_uInt16 InsertTabName( const OUString& rTabName, ScExternalRefCache::TableTypeRef const & xCacheTable ); + /** Finds or inserts an EXTERNNAME record for add-ins. + @return The 1-based EXTERNNAME record index; or 0, if the record list is full. */ + sal_uInt16 InsertAddIn( const OUString& rName ); + /** InsertEuroTool */ + sal_uInt16 InsertEuroTool( const OUString& rName ); + /** Finds or inserts an EXTERNNAME record for DDE links. + @return The 1-based EXTERNNAME record index; or 0, if the record list is full. */ + sal_uInt16 InsertDde( const OUString& rItem ); + + sal_uInt16 InsertExtName( const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ); + + /** Get the type of record. */ + XclSupbookType GetType() const; + + /** For references to an external document, 1-based OOXML file ID. */ + sal_uInt16 GetFileId() const; + + /** For references to an external document. */ + const OUString& GetUrl() const; + + /** Writes the SUPBOOK and all EXTERNNAME, XCT and CRN records. */ + virtual void Save( XclExpStream& rStrm ) override; + + /** Writes the externalBook and all child elements. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + /** Returns the sheet name inside of this SUPBOOK. */ + const XclExpString* GetTabName( sal_uInt16 nSBTab ) const; + + /** Writes the SUPBOOK record contents. */ + virtual void WriteBody( XclExpStream& rStrm ) override; + +private: + typedef XclExpRecordList< XclExpXct > XclExpXctList; + typedef XclExpXctList::RecordRefType XclExpXctRef; + + XclExpXctList maXctList; /// List of XCT records (which contain CRN records). + OUString maUrl; /// URL of the external document or application name for DDE. + OUString maDdeTopic; /// Topic of a DDE link. + XclExpString maUrlEncoded; /// Document name encoded for Excel. + XclSupbookType meType; /// Type of this SUPBOOK record. + sal_uInt16 mnXclTabCount; /// Number of internal sheets. + sal_uInt16 mnFileId; /// 1-based external reference file ID for OOXML +}; + +// All SUPBOOKS in a document ================================================= + +/** This struct contains a sheet index range for 3D references. + @descr This reference consists of an index to a SUPBOOK record and indexes + to SUPBOOK sheet names. */ +struct XclExpXti +{ + sal_uInt16 mnSupbook; /// Index to SUPBOOK record. + sal_uInt16 mnFirstSBTab; /// Index to the first sheet of the range in the SUPBOOK. + sal_uInt16 mnLastSBTab; /// Index to the last sheet of the range in the SUPBOOK. + + explicit XclExpXti() : mnSupbook( 0 ), mnFirstSBTab( 0 ), mnLastSBTab( 0 ) {} + explicit XclExpXti( sal_uInt16 nSupbook, sal_uInt16 nFirstSBTab, sal_uInt16 nLastSBTab ) : + mnSupbook( nSupbook ), mnFirstSBTab( nFirstSBTab ), mnLastSBTab( nLastSBTab ) {} + + /** Writes this XTI structure (inside of the EXTERNSHEET record). */ + void Save( XclExpStream& rStrm ) const + { rStrm << mnSupbook << mnFirstSBTab << mnLastSBTab; } +}; + +bool operator==( const XclExpXti& rLeft, const XclExpXti& rRight ) +{ + return + (rLeft.mnSupbook == rRight.mnSupbook) && + (rLeft.mnFirstSBTab == rRight.mnFirstSBTab) && + (rLeft.mnLastSBTab == rRight.mnLastSBTab); +} + +/** Contains a list of all SUPBOOK records and index arrays of external sheets. */ +class XclExpSupbookBuffer : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit XclExpSupbookBuffer( const XclExpRoot& rRoot ); + + /** Finds SUPBOOK index and SUPBOOK sheet range from given Excel sheet range. + @return An XTI structure containing SUPBOOK and sheet indexes. */ + XclExpXti GetXti( sal_uInt16 nFirstXclTab, sal_uInt16 nLastXclTab, + XclExpRefLogEntry* pRefLogEntry = nullptr ) const; + + /** Stores all cells in the given range in a CRN record list. */ + void StoreCellRange( const ScRange& rRange ); + + void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell ); + void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ); + + /** Finds or inserts an EXTERNNAME record for an add-in function name. + @param rnSupbook Returns the index of the SUPBOOK record which contains the add-in function name. + @param rnExtName Returns the 1-based EXTERNNAME record index. */ + bool InsertAddIn( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rName ); + /** InsertEuroTool */ + bool InsertEuroTool( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rName ); + /** Finds or inserts an EXTERNNAME record for DDE links. + @param rnSupbook Returns the index of the SUPBOOK record which contains the DDE link. + @param rnExtName Returns the 1-based EXTERNNAME record index. */ + bool InsertDde( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ); + + bool InsertExtName( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ); + + XclExpXti GetXti( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + XclExpRefLogEntry* pRefLogEntry ); + + /** Writes all SUPBOOK records with their sub records. */ + virtual void Save( XclExpStream& rStrm ) override; + + /** Writes all externalBook elements with their child elements to OOXML. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + + /** Whether we need to write externalReferences or not. */ + bool HasExternalReferences() const; + + struct XclExpSBIndex + { + sal_uInt16 mnSupbook; /// SUPBOOK index for an Excel sheet. + sal_uInt16 mnSBTab; /// Sheet name index in SUPBOOK for an Excel sheet. + void Set( sal_uInt16 nSupbook, sal_uInt16 nSBTab ) + { mnSupbook = nSupbook; mnSBTab = nSBTab; } + }; + +private: + typedef XclExpRecordList< XclExpSupbook > XclExpSupbookList; + typedef XclExpSupbookList::RecordRefType XclExpSupbookRef; + +private: + /** Searches for the SUPBOOK record containing the passed document URL. + @param rxSupbook (out-param) Returns a reference to the SUPBOOK record, or 0. + @param rnIndex (out-param) Returns the list index, if the SUPBOOK exists. + @return True, if the SUPBOOK record exists (out-parameters are valid). */ + bool GetSupbookUrl( XclExpSupbookRef& rxSupbook, sal_uInt16& rnIndex, + std::u16string_view rUrl ) const; + /** Searches for the SUPBOOK record containing the passed DDE link. + @param rxSupbook (out-param) Returns a reference to the SUPBOOK record, or 0. + @param rnIndex (out-param) Returns the list index, if the SUPBOOK exists. + @return True, if the SUPBOOK record exists (out-parameters are valid). */ + bool GetSupbookDde( XclExpSupbookRef& rxSupbook, sal_uInt16& rnIndex, + std::u16string_view rApplic, std::u16string_view rTopic ) const; + + /** Appends a new SUPBOOK to the list. + @return The list index of the SUPBOOK record. */ + sal_uInt16 Append( XclExpSupbookRef const & xSupbook ); + +private: + XclExpSupbookList maSupbookList; /// List of all SUPBOOK records. + std::vector< XclExpSBIndex > + maSBIndexVec; /// SUPBOOK and sheet name index for each Excel sheet. + sal_uInt16 mnOwnDocSB; /// Index to SUPBOOK for own document. + sal_uInt16 mnAddInSB; /// Index to add-in SUPBOOK. +}; + +} + +// Export link manager ======================================================== + +/** Abstract base class for implementation classes of the link manager. */ +class XclExpLinkManagerImpl : protected XclExpRoot +{ +public: + /** Derived classes search for an EXTSHEET structure for the given Calc sheet range. */ + virtual void FindExtSheet( sal_uInt16& rnExtSheet, + sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, + XclExpRefLogEntry* pRefLogEntry ) = 0; + /** Derived classes search for a special EXTERNSHEET index for the own document. */ + virtual sal_uInt16 FindExtSheet( sal_Unicode cCode ) = 0; + + virtual void FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) = 0; + + /** Derived classes store all cells in the given range in a CRN record list. */ + virtual void StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) = 0; + + virtual void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) = 0; + virtual void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) = 0; + + /** Derived classes find or insert an EXTERNNAME record for an add-in function name. */ + virtual bool InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) = 0; + /** InsertEuroTool */ + virtual bool InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) = 0; + + /** Derived classes find or insert an EXTERNNAME record for DDE links. */ + virtual bool InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) = 0; + + virtual bool InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) = 0; + + /** Derived classes write the entire link table to the passed stream. */ + virtual void Save( XclExpStream& rStrm ) = 0; + + /** Derived classes write the entire link table to the passed OOXML stream. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) = 0; + +protected: + explicit XclExpLinkManagerImpl( const XclExpRoot& rRoot ); +}; + +namespace { + +/** Implementation of the link manager for BIFF5/BIFF7. */ +class XclExpLinkManagerImpl5 : public XclExpLinkManagerImpl +{ +public: + explicit XclExpLinkManagerImpl5( const XclExpRoot& rRoot ); + + virtual void FindExtSheet( sal_uInt16& rnExtSheet, + sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, + XclExpRefLogEntry* pRefLogEntry ) override; + virtual sal_uInt16 FindExtSheet( sal_Unicode cCode ) override; + + virtual void FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) override; + + virtual void StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) override; + + virtual void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) override; + virtual void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) override; + + virtual bool InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + + /** InsertEuroTool */ + virtual bool InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + + virtual bool InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) override; + + virtual bool InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) override; + + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + typedef XclExpRecordList< XclExpExternSheet > XclExpExtSheetList; + typedef XclExpExtSheetList::RecordRefType XclExpExtSheetRef; + typedef ::std::map< SCTAB, sal_uInt16 > XclExpIntTabMap; + typedef ::std::map< sal_Unicode, sal_uInt16 > XclExpCodeMap; + +private: + /** Returns the number of EXTERNSHEET records. */ + sal_uInt16 GetExtSheetCount() const; + + /** Appends an internal EXTERNSHEET record and returns the one-based index. */ + sal_uInt16 AppendInternal( XclExpExtSheetRef const & xExtSheet ); + /** Creates all EXTERNSHEET records for internal sheets on first call. */ + void CreateInternal(); + + /** Returns the specified internal EXTERNSHEET record. */ + XclExpExtSheetRef GetInternal( sal_uInt16 nExtSheet ); + /** Returns the EXTERNSHEET index of an internal Calc sheet, or a deleted reference. */ + XclExpExtSheetRef FindInternal( sal_uInt16& rnExtSheet, sal_uInt16& rnXclTab, SCTAB nScTab ); + /** Finds or creates the EXTERNSHEET index of an internal special EXTERNSHEET. */ + XclExpExtSheetRef FindInternal( sal_uInt16& rnExtSheet, sal_Unicode cCode ); + +private: + XclExpExtSheetList maExtSheetList; /// List with EXTERNSHEET records. + XclExpIntTabMap maIntTabMap; /// Maps internal Calc sheets to EXTERNSHEET records. + XclExpCodeMap maCodeMap; /// Maps special external codes to EXTERNSHEET records. +}; + +/** Implementation of the link manager for BIFF8 and OOXML. */ +class XclExpLinkManagerImpl8 : public XclExpLinkManagerImpl +{ +public: + explicit XclExpLinkManagerImpl8( const XclExpRoot& rRoot ); + + virtual void FindExtSheet( sal_uInt16& rnExtSheet, + sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, + XclExpRefLogEntry* pRefLogEntry ) override; + virtual sal_uInt16 FindExtSheet( sal_Unicode cCode ) override; + + virtual void FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) override; + + virtual void StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) override; + + virtual void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) override; + virtual void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) override; + + virtual bool InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + /** InsertEuroTool */ + virtual bool InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + + virtual bool InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) override; + + virtual bool InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) override; + + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + /** Searches for or inserts a new XTI structure. + @return The 0-based list index of the XTI structure. */ + sal_uInt16 InsertXti( const XclExpXti& rXti ); + +private: + + XclExpSupbookBuffer maSBBuffer; /// List of all SUPBOOK records. + std::vector< XclExpXti > maXtiVec; /// List of XTI structures for the EXTERNSHEET record. +}; + +} + +// *** Implementation *** + +// Excel sheet indexes ======================================================== + + +XclExpTabInfo::XclExpTabInfo( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnScCnt( 0 ), + mnXclCnt( 0 ), + mnXclExtCnt( 0 ), + mnXclSelCnt( 0 ), + mnDisplXclTab( 0 ), + mnFirstVisXclTab( 0 ) +{ + ScDocument& rDoc = GetDoc(); + ScExtDocOptions& rDocOpt = GetExtDocOptions(); + + mnScCnt = rDoc.GetTableCount(); + + SCTAB nScTab; + SCTAB nFirstVisScTab = SCTAB_INVALID; // first visible sheet + SCTAB nFirstExpScTab = SCTAB_INVALID; // first exported sheet + + // --- initialize the flags in the index buffer --- + + maTabInfoVec.resize( mnScCnt ); + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + // ignored sheets (skipped by export, with invalid Excel sheet index) + if( rDoc.IsScenario( nScTab ) ) + { + SetFlag( nScTab, ExcTabBufFlags::Ignore ); + } + + // external sheets (skipped, but with valid Excel sheet index for ref's) + else if( rDoc.GetLinkMode( nScTab ) == ScLinkMode::VALUE ) + { + SetFlag( nScTab, ExcTabBufFlags::Extern ); + } + + // exported sheets + else + { + // sheet name + rDoc.GetName( nScTab, maTabInfoVec[ nScTab ].maScName ); + + // remember first exported sheet + if( nFirstExpScTab == SCTAB_INVALID ) + nFirstExpScTab = nScTab; + // remember first visible exported sheet + if( (nFirstVisScTab == SCTAB_INVALID) && rDoc.IsVisible( nScTab ) ) + nFirstVisScTab = nScTab; + + // sheet visible (only exported sheets) + SetFlag( nScTab, ExcTabBufFlags::Visible, rDoc.IsVisible( nScTab ) ); + + // sheet selected (only exported sheets) + if( const ScExtTabSettings* pTabSett = rDocOpt.GetTabSettings( nScTab ) ) + SetFlag( nScTab, ExcTabBufFlags::Selected, pTabSett->mbSelected ); + + // sheet mirrored (only exported sheets) + SetFlag( nScTab, ExcTabBufFlags::Mirrored, rDoc.IsLayoutRTL( nScTab ) ); + } + } + + // --- visible/selected sheets --- + + SCTAB nDisplScTab = rDocOpt.GetDocSettings().mnDisplTab; + + // missing viewdata at embedded XLSX OLE objects + if (nDisplScTab == -1 ) + nDisplScTab = rDoc.GetVisibleTab(); + + // find first visible exported sheet + if( (nFirstVisScTab == SCTAB_INVALID) || !IsExportTab( nFirstVisScTab ) ) + { + // no exportable visible sheet -> use first exportable sheet + nFirstVisScTab = nFirstExpScTab; + if( (nFirstVisScTab == SCTAB_INVALID) || !IsExportTab( nFirstVisScTab ) ) + { + // no exportable sheet at all -> use active sheet and export it + nFirstVisScTab = nDisplScTab; + SetFlag( nFirstVisScTab, ExcTabBufFlags::SkipMask, false ); // clear skip flags + } + SetFlag( nFirstVisScTab, ExcTabBufFlags::Visible ); // must be visible, even if originally hidden + } + + // find currently displayed sheet + if( !IsExportTab( nDisplScTab ) ) // selected sheet not exported (i.e. scenario) -> use first visible + nDisplScTab = nFirstVisScTab; + SetFlag( nDisplScTab, ExcTabBufFlags::Visible | ExcTabBufFlags::Selected ); + + // number of selected sheets + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + if( IsSelectedTab( nScTab ) ) + ++mnXclSelCnt; + + // --- calculate resulting Excel sheet indexes --- + + CalcXclIndexes(); + mnFirstVisXclTab = GetXclTab( nFirstVisScTab ); + mnDisplXclTab = GetXclTab( nDisplScTab ); + + // --- sorted vectors for index lookup --- + + CalcSortedIndexes(); +} + +bool XclExpTabInfo::IsExportTab( SCTAB nScTab ) const +{ + /* Check sheet index before to avoid assertion in GetFlag(). */ + return (nScTab < mnScCnt && nScTab >= 0) && !GetFlag( nScTab, ExcTabBufFlags::SkipMask ); +} + +bool XclExpTabInfo::IsExternalTab( SCTAB nScTab ) const +{ + /* Check sheet index before to avoid assertion (called from formula + compiler also for deleted references). */ + return (nScTab < mnScCnt && nScTab >= 0) && GetFlag( nScTab, ExcTabBufFlags::Extern ); +} + +bool XclExpTabInfo::IsVisibleTab( SCTAB nScTab ) const +{ + return GetFlag( nScTab, ExcTabBufFlags::Visible ); +} + +bool XclExpTabInfo::IsSelectedTab( SCTAB nScTab ) const +{ + return GetFlag( nScTab, ExcTabBufFlags::Selected ); +} + +bool XclExpTabInfo::IsDisplayedTab( SCTAB nScTab ) const +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::IsActiveTab - sheet out of range" ); + return GetXclTab( nScTab ) == mnDisplXclTab; +} + +bool XclExpTabInfo::IsMirroredTab( SCTAB nScTab ) const +{ + return GetFlag( nScTab, ExcTabBufFlags::Mirrored ); +} + +OUString XclExpTabInfo::GetScTabName( SCTAB nScTab ) const +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::IsActiveTab - sheet out of range" ); + return (nScTab < mnScCnt && nScTab >= 0) ? maTabInfoVec[ nScTab ].maScName : OUString(); +} + +sal_uInt16 XclExpTabInfo::GetXclTab( SCTAB nScTab ) const +{ + return (nScTab < mnScCnt && nScTab >= 0) ? maTabInfoVec[ nScTab ].mnXclTab : EXC_TAB_DELETED; +} + +SCTAB XclExpTabInfo::GetRealScTab( SCTAB nSortedScTab ) const +{ + OSL_ENSURE( nSortedScTab < mnScCnt && nSortedScTab >= 0, "XclExpTabInfo::GetRealScTab - sheet out of range" ); + return (nSortedScTab < mnScCnt && nSortedScTab >= 0) ? maFromSortedVec[ nSortedScTab ] : SCTAB_INVALID; +} + +bool XclExpTabInfo::GetFlag( SCTAB nScTab, ExcTabBufFlags nFlags ) const +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::GetFlag - sheet out of range" ); + return (nScTab < mnScCnt && nScTab >= 0) && (maTabInfoVec[ nScTab ].mnFlags & nFlags); +} + +void XclExpTabInfo::SetFlag( SCTAB nScTab, ExcTabBufFlags nFlags, bool bSet ) +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::SetFlag - sheet out of range" ); + if( nScTab < mnScCnt && nScTab >= 0 ) + { + if (bSet) + maTabInfoVec[ nScTab ].mnFlags |= nFlags; + else + maTabInfoVec[ nScTab ].mnFlags &= ~nFlags; + } +} + +void XclExpTabInfo::CalcXclIndexes() +{ + sal_uInt16 nXclTab = 0; + SCTAB nScTab = 0; + + // --- pass 1: process regular sheets --- + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + if( IsExportTab( nScTab ) ) + { + maTabInfoVec[ nScTab ].mnXclTab = nXclTab; + ++nXclTab; + } + else + maTabInfoVec[ nScTab ].mnXclTab = EXC_TAB_DELETED; + } + mnXclCnt = nXclTab; + + // --- pass 2: process external sheets (nXclTab continues) --- + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + if( IsExternalTab( nScTab ) ) + { + maTabInfoVec[ nScTab ].mnXclTab = nXclTab; + ++nXclTab; + ++mnXclExtCnt; + } + } + + // result: first occur all exported sheets, followed by all external sheets +} + +typedef ::std::pair< OUString, SCTAB > XclExpTabName; + +namespace { + +struct XclExpTabNameSort { + bool operator ()( const XclExpTabName& rArg1, const XclExpTabName& rArg2 ) + { + // compare the sheet names only + return ScGlobal::GetCollator().compareString( rArg1.first, rArg2.first ) < 0; + } +}; + +} + +void XclExpTabInfo::CalcSortedIndexes() +{ + ScDocument& rDoc = GetDoc(); + ::std::vector< XclExpTabName > aVec( mnScCnt ); + SCTAB nScTab; + + // fill with sheet names + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + rDoc.GetName( nScTab, aVec[ nScTab ].first ); + aVec[ nScTab ].second = nScTab; + } + ::std::sort( aVec.begin(), aVec.end(), XclExpTabNameSort() ); + + // fill index vectors from sorted sheet name vector + maFromSortedVec.resize( mnScCnt ); + maToSortedVec.resize( mnScCnt ); + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + maFromSortedVec[ nScTab ] = aVec[ nScTab ].second; + maToSortedVec[ aVec[ nScTab ].second ] = nScTab; + } +} + +// External names ============================================================= + +XclExpExtNameBase::XclExpExtNameBase( + const XclExpRoot& rRoot, const OUString& rName, sal_uInt16 nFlags ) : + XclExpRecord( EXC_ID_EXTERNNAME ), + XclExpRoot( rRoot ), + maName( rName ), + mxName( XclExpStringHelper::CreateString( rRoot, rName, XclStrFlags::EightBitLength ) ), + mnFlags( nFlags ) +{ + OSL_ENSURE( maName.getLength() <= 255, "XclExpExtNameBase::XclExpExtNameBase - string too long" ); + SetRecSize( 6 + mxName->GetSize() ); +} + +void XclExpExtNameBase::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnFlags + << sal_uInt32( 0 ) + << *mxName; + WriteAddData( rStrm ); +} + +void XclExpExtNameBase::WriteAddData( XclExpStream& /*rStrm*/ ) +{ +} + +XclExpExtNameAddIn::XclExpExtNameAddIn( const XclExpRoot& rRoot, const OUString& rName ) : + XclExpExtNameBase( rRoot, rName ) +{ + AddRecSize( 4 ); +} + +void XclExpExtNameAddIn::WriteAddData( XclExpStream& rStrm ) +{ + // write a #REF! error formula + rStrm << sal_uInt16( 2 ) << EXC_TOKID_ERR << EXC_ERR_REF; +} + +XclExpExtNameDde::XclExpExtNameDde( const XclExpRoot& rRoot, + const OUString& rName, sal_uInt16 nFlags, const ScMatrix* pResults ) : + XclExpExtNameBase( rRoot, rName, nFlags ) +{ + if( pResults ) + { + mxMatrix = std::make_shared<XclExpCachedMatrix>( *pResults ); + AddRecSize( mxMatrix->GetSize() ); + } +} + +void XclExpExtNameDde::WriteAddData( XclExpStream& rStrm ) +{ + if( mxMatrix ) + mxMatrix->Save( rStrm ); +} + +XclExpExtName::XclExpExtName( const XclExpRoot& rRoot, const XclExpSupbook& rSupbook, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) : + XclExpExtNameBase( rRoot, rName ), + mrSupbook(rSupbook), + mpArray(rArray->Clone()) +{ +} + +void XclExpExtName::WriteAddData( XclExpStream& rStrm ) +{ + // Write only if it only has a single token that is either a cell or cell + // range address. Excel just writes '02 00 1C 17' for all the other types + // of external names. + + using namespace ::formula; + do + { + if (mpArray->GetLen() != 1) + break; + + const formula::FormulaToken* p = mpArray->FirstToken(); + if (!p->IsExternalRef()) + break; + + switch (p->GetType()) + { + case svExternalSingleRef: + { + const ScSingleRefData& rRef = *p->GetSingleRef(); + if (rRef.IsTabRel()) + break; + + bool bColRel = rRef.IsColRel(); + bool bRowRel = rRef.IsRowRel(); + sal_uInt16 nCol = static_cast<sal_uInt16>(rRef.Col()); + sal_uInt16 nRow = static_cast<sal_uInt16>(rRef.Row()); + if (bColRel) nCol |= 0x4000; + if (bRowRel) nCol |= 0x8000; + + OUString aTabName = p->GetString().getString(); + sal_uInt16 nSBTab = mrSupbook.GetTabIndex(aTabName); + + // size is always 9 + rStrm << static_cast<sal_uInt16>(9); + // operator token (3A for cell reference) + rStrm << static_cast<sal_uInt8>(0x3A); + // cell address (Excel's address has 2 sheet IDs.) + rStrm << nSBTab << nSBTab << nRow << nCol; + return; + } + case svExternalDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + const ScSingleRefData& r1 = rRef.Ref1; + const ScSingleRefData& r2 = rRef.Ref2; + if (r1.IsTabRel() || r2.IsTabRel()) + break; + + sal_uInt16 nTab1 = r1.Tab(); + sal_uInt16 nTab2 = r2.Tab(); + bool bCol1Rel = r1.IsColRel(); + bool bRow1Rel = r1.IsRowRel(); + bool bCol2Rel = r2.IsColRel(); + bool bRow2Rel = r2.IsRowRel(); + + sal_uInt16 nCol1 = static_cast<sal_uInt16>(r1.Col()); + sal_uInt16 nCol2 = static_cast<sal_uInt16>(r2.Col()); + sal_uInt16 nRow1 = static_cast<sal_uInt16>(r1.Row()); + sal_uInt16 nRow2 = static_cast<sal_uInt16>(r2.Row()); + if (bCol1Rel) nCol1 |= 0x4000; + if (bRow1Rel) nCol1 |= 0x8000; + if (bCol2Rel) nCol2 |= 0x4000; + if (bRow2Rel) nCol2 |= 0x8000; + + OUString aTabName = p->GetString().getString(); + sal_uInt16 nSBTab = mrSupbook.GetTabIndex(aTabName); + + // size is always 13 (0x0D) + rStrm << static_cast<sal_uInt16>(13); + // operator token (3B for area reference) + rStrm << static_cast<sal_uInt8>(0x3B); + // range (area) address + sal_uInt16 nSBTab2 = nSBTab + nTab2 - nTab1; + rStrm << nSBTab << nSBTab2 << nRow1 << nRow2 << nCol1 << nCol2; + return; + } + default: + ; // nothing + } + } + while (false); + + // special value for #REF! (02 00 1C 17) + rStrm << static_cast<sal_uInt16>(2) << EXC_TOKID_ERR << EXC_ERR_REF; +} + +void XclExpExtName::SaveXml(XclExpXmlStream& rStrm) +{ + sax_fastparser::FSHelperPtr pExternalLink = rStrm.GetCurrentStream(); + + /* TODO: mpArray contains external references. It doesn't cause any problems, but it's enough + to export it without the external document identifier. */ + if (mpArray->GetLen()) + { + const OUString aFormula = XclXmlUtils::ToOUString(GetCompileFormulaContext(), ScAddress(0, 0, 0), mpArray.get()); + pExternalLink->startElement(XML_definedName, + XML_name, maName.toUtf8(), + XML_refersTo, aFormula.toUtf8(), + XML_sheetId, nullptr); + } + else + { + pExternalLink->startElement(XML_definedName, + XML_name, maName.toUtf8(), + XML_refersTo, nullptr, + XML_sheetId, nullptr); + } + + pExternalLink->endElement(XML_definedName); +} + +// List of external names ===================================================== + +XclExpExtNameBuffer::XclExpExtNameBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +sal_uInt16 XclExpExtNameBuffer::InsertAddIn( const OUString& rName ) +{ + sal_uInt16 nIndex = GetIndex( rName ); + return nIndex ? nIndex : AppendNew( new XclExpExtNameAddIn( GetRoot(), rName ) ); +} + +sal_uInt16 XclExpExtNameBuffer::InsertEuroTool( const OUString& rName ) +{ + sal_uInt16 nIndex = GetIndex( rName ); + return nIndex ? nIndex : AppendNew( new XclExpExtNameBase( GetRoot(), rName ) ); +} + +sal_uInt16 XclExpExtNameBuffer::InsertDde( + std::u16string_view rApplic, std::u16string_view rTopic, const OUString& rItem ) +{ + sal_uInt16 nIndex = GetIndex( rItem ); + if( nIndex == 0 ) + { + size_t nPos; + if( GetDoc().FindDdeLink( rApplic, rTopic, rItem, SC_DDE_IGNOREMODE, nPos ) ) + { + // create the leading 'StdDocumentName' EXTERNNAME record + if( maNameList.IsEmpty() ) + AppendNew( new XclExpExtNameDde( + GetRoot(), "StdDocumentName", EXC_EXTN_EXPDDE_STDDOC ) ); + + // try to find DDE result array, but create EXTERNNAME record without them too + const ScMatrix* pScMatrix = GetDoc().GetDdeLinkResultMatrix( nPos ); + nIndex = AppendNew( new XclExpExtNameDde( GetRoot(), rItem, EXC_EXTN_EXPDDE, pScMatrix ) ); + } + } + return nIndex; +} + +sal_uInt16 XclExpExtNameBuffer::InsertExtName( const XclExpSupbook& rSupbook, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + sal_uInt16 nIndex = GetIndex( rName ); + return nIndex ? nIndex : AppendNew( new XclExpExtName( GetRoot(), rSupbook, rName, rArray ) ); +} + +void XclExpExtNameBuffer::Save( XclExpStream& rStrm ) +{ + maNameList.Save( rStrm ); +} + +void XclExpExtNameBuffer::SaveXml(XclExpXmlStream& rStrm) +{ + maNameList.SaveXml(rStrm); +} + +sal_uInt16 XclExpExtNameBuffer::GetIndex( std::u16string_view rName ) const +{ + for( size_t nPos = 0, nSize = maNameList.GetSize(); nPos < nSize; ++nPos ) + if( maNameList.GetRecord( nPos )->GetName() == rName ) + return static_cast< sal_uInt16 >( nPos + 1 ); + return 0; +} + +sal_uInt16 XclExpExtNameBuffer::AppendNew( XclExpExtNameBase* pExtName ) +{ + size_t nSize = maNameList.GetSize(); + if( nSize < 0x7FFF ) + { + maNameList.AppendRecord( pExtName ); + return static_cast< sal_uInt16 >( nSize + 1 ); + } + return 0; +} + +// Cached external cells ====================================================== + +XclExpCrn::XclExpCrn( SCCOL nScCol, SCROW nScRow, const Any& rValue ) : + XclExpRecord( EXC_ID_CRN, 4 ), + mnScCol( nScCol ), + mnScRow( nScRow ) +{ + maValues.push_back( rValue ); +} + +bool XclExpCrn::InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ) +{ + if( (nScRow != mnScRow) || (nScCol != static_cast< SCCOL >( mnScCol + maValues.size() )) ) + return false; + maValues.push_back( rValue ); + return true; +} + +void XclExpCrn::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt8 >( mnScCol + maValues.size() - 1 ) + << static_cast< sal_uInt8 >( mnScCol ) + << static_cast< sal_uInt16 >( mnScRow ); + for( const auto& rValue : maValues ) + { + if( rValue.has< bool >() ) + WriteBool( rStrm, rValue.get< bool >() ); + else if( rValue.has< double >() ) + WriteDouble( rStrm, rValue.get< double >() ); + else if( rValue.has< OUString >() ) + WriteString( rStrm, rValue.get< OUString >() ); + else + WriteEmpty( rStrm ); + } +} + +void XclExpCrn::WriteBool( XclExpStream& rStrm, bool bValue ) +{ + rStrm << EXC_CACHEDVAL_BOOL << sal_uInt8( bValue ? 1 : 0); + rStrm.WriteZeroBytes( 7 ); +} + +void XclExpCrn::WriteDouble( XclExpStream& rStrm, double fValue ) +{ + if( !std::isfinite( fValue ) ) + { + FormulaError nScError = GetDoubleErrorValue(fValue); + WriteError( rStrm, XclTools::GetXclErrorCode( nScError ) ); + } + else + { + rStrm << EXC_CACHEDVAL_DOUBLE << fValue; + } +} + +void XclExpCrn::WriteString( XclExpStream& rStrm, const OUString& rValue ) +{ + rStrm << EXC_CACHEDVAL_STRING << XclExpString( rValue ); +} + +void XclExpCrn::WriteError( XclExpStream& rStrm, sal_uInt8 nErrCode ) +{ + rStrm << EXC_CACHEDVAL_ERROR << nErrCode; + rStrm.WriteZeroBytes( 7 ); +} + +void XclExpCrn::WriteEmpty( XclExpStream& rStrm ) +{ + rStrm << EXC_CACHEDVAL_EMPTY; + rStrm.WriteZeroBytes( 8 ); +} + +void XclExpCrn::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pFS = rStrm.GetCurrentStream(); + + pFS->startElement(XML_row, XML_r, OString::number(mnScRow + 1)); + + ScAddress aAdr( mnScCol, mnScRow, 0); // Tab number doesn't matter + for( const auto& rValue : maValues ) + { + bool bCloseCell = true; + if( rValue.has< double >() ) + { + double fVal = rValue.get< double >(); + if (std::isfinite( fVal)) + { + // t='n' is omitted + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr)); + pFS->startElement(XML_v); + pFS->write( fVal ); + } + else + { + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr), XML_t, "e"); + pFS->startElement(XML_v); + pFS->write( "#VALUE!" ); // OOXTODO: support other error values + } + } + else if( rValue.has< OUString >() ) + { + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr), XML_t, "str"); + pFS->startElement(XML_v); + pFS->write( rValue.get< OUString >() ); + } + else if( rValue.has< bool >() ) + { + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr), XML_t, "b"); + pFS->startElement(XML_v); + pFS->write( rValue.get< bool >() ? "1" : "0" ); + } + // OOXTODO: error type cell t='e' + else + { + // Empty/blank cell not stored, only aAdr is incremented. + bCloseCell = false; + } + if (bCloseCell) + { + pFS->endElement(XML_v); + pFS->endElement(XML_cell); + } + aAdr.IncCol(); + } + + pFS->endElement( XML_row); +} + +// Cached cells of a sheet ==================================================== + +XclExpXct::XclExpXct( const XclExpRoot& rRoot, const OUString& rTabName, + sal_uInt16 nSBTab, ScExternalRefCache::TableTypeRef const & xCacheTable ) : + XclExpRoot( rRoot ), + mxCacheTable( xCacheTable ), + maUsedCells( rRoot.GetDoc().GetSheetLimits() ), + maBoundRange( ScAddress::INITIALIZE_INVALID ), + maTabName( rTabName ), + mnSBTab( nSBTab ) +{ +} + +void XclExpXct::StoreCellRange( const ScRange& rRange ) +{ + // #i70418# restrict size of external range to prevent memory overflow + if( (rRange.aEnd.Col() - rRange.aStart.Col()) * (rRange.aEnd.Row() - rRange.aStart.Row()) > 1024 ) + return; + + maUsedCells.SetMultiMarkArea( rRange ); + maBoundRange.ExtendTo( rRange ); +} + +void XclExpXct::StoreCell_( const ScAddress& rCell ) +{ + maUsedCells.SetMultiMarkArea( ScRange( rCell ) ); + maBoundRange.ExtendTo( ScRange( rCell ) ); +} + +void XclExpXct::StoreCellRange_( const ScRange& rRange ) +{ + maUsedCells.SetMultiMarkArea( rRange ); + maBoundRange.ExtendTo( rRange ); +} + +namespace { + +class XclExpCrnList : public XclExpRecordList< XclExpCrn > +{ +public: + /** Inserts the passed value into an existing or new CRN record. + @return True = value inserted successfully, false = CRN list is full. */ + bool InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ); +}; + +bool XclExpCrnList::InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ) +{ + RecordRefType xLastRec = GetLastRecord(); + if( xLastRec && xLastRec->InsertValue( nScCol, nScRow, rValue ) ) + return true; + if( GetSize() == SAL_MAX_UINT16 ) + return false; + AppendNewRecord( new XclExpCrn( nScCol, nScRow, rValue ) ); + return true; +} + +} // namespace + +bool XclExpXct::BuildCrnList( XclExpCrnList& rCrnRecs ) +{ + if( !mxCacheTable ) + return false; + + /* Get the range of used rows in the cache table. This may help to + optimize building the CRN record list if the cache table does not + contain all referred cells, e.g. if big empty ranges are used in the + formulas. */ + ::std::pair< SCROW, SCROW > aRowRange = mxCacheTable->getRowRange(); + if( aRowRange.first >= aRowRange.second ) + return false; + + /* Crop the bounding range of used cells in this table to Excel limits. + Return if there is no external cell inside these limits. */ + if( !GetAddressConverter().ValidateRange( maBoundRange, false ) ) + return false; + + /* Find the resulting row range that needs to be processed. */ + SCROW nScRow1 = ::std::max( aRowRange.first, maBoundRange.aStart.Row() ); + SCROW nScRow2 = ::std::min( aRowRange.second - 1, maBoundRange.aEnd.Row() ); + if( nScRow1 > nScRow2 ) + return false; + + /* Build and collect all CRN records before writing the XCT record. This + is needed to determine the total number of CRN records which must be + known when writing the XCT record (possibly encrypted, so seeking the + output stream back after writing the CRN records is not an option). */ + SvNumberFormatter& rFormatter = GetFormatter(); + bool bValid = true; + for( SCROW nScRow = nScRow1; bValid && (nScRow <= nScRow2); ++nScRow ) + { + ::std::pair< SCCOL, SCCOL > aColRange = mxCacheTable->getColRange( nScRow ); + const SCCOL nScEnd = ::std::min( aColRange.second, GetDoc().GetSheetLimits().GetMaxColCount() ); + for( SCCOL nScCol = aColRange.first; bValid && (nScCol < nScEnd); ++nScCol ) + { + if( maUsedCells.IsCellMarked( nScCol, nScRow, true ) ) + { + sal_uInt32 nScNumFmt = 0; + ScExternalRefCache::TokenRef xToken = mxCacheTable->getCell( nScCol, nScRow, &nScNumFmt ); + using namespace ::formula; + if( xToken ) + switch( xToken->GetType() ) + { + case svDouble: + bValid = (rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL) ? + rCrnRecs.InsertValue( nScCol, nScRow, Any( xToken->GetDouble() != 0 ) ) : + rCrnRecs.InsertValue( nScCol, nScRow, Any( xToken->GetDouble() ) ); + break; + case svString: + // do not save empty strings (empty cells) to cache + if( !xToken->GetString().isEmpty() ) + bValid = rCrnRecs.InsertValue( nScCol, nScRow, Any( xToken->GetString().getString() ) ); + break; + default: + break; + } + } + } + } + return true; +} + +void XclExpXct::Save( XclExpStream& rStrm ) +{ + XclExpCrnList aCrnRecs; + if (!BuildCrnList( aCrnRecs)) + return; + + // write the XCT record and the list of CRN records + rStrm.StartRecord( EXC_ID_XCT, 4 ); + rStrm << static_cast< sal_uInt16 >( aCrnRecs.GetSize() ) << mnSBTab; + rStrm.EndRecord(); + aCrnRecs.Save( rStrm ); +} + +void XclExpXct::SaveXml( XclExpXmlStream& rStrm ) +{ + XclExpCrnList aCrnRecs; + + sax_fastparser::FSHelperPtr pFS = rStrm.GetCurrentStream(); + + bool bValid = BuildCrnList( aCrnRecs); + pFS->startElement(XML_sheetData, XML_sheetId, OString::number(mnSBTab)); + if (bValid) + { + // row elements + aCrnRecs.SaveXml( rStrm ); + } + pFS->endElement( XML_sheetData); +} + +// External documents (EXTERNSHEET/SUPBOOK), base class ======================= + +XclExpExternSheetBase::XclExpExternSheetBase( const XclExpRoot& rRoot, sal_uInt16 nRecId, sal_uInt32 nRecSize ) : + XclExpRecord( nRecId, nRecSize ), + XclExpRoot( rRoot ) +{ +} + +XclExpExtNameBuffer& XclExpExternSheetBase::GetExtNameBuffer() +{ + if( !mxExtNameBfr ) + mxExtNameBfr = std::make_shared<XclExpExtNameBuffer>( GetRoot() ); + return *mxExtNameBfr; +} + +void XclExpExternSheetBase::WriteExtNameBuffer( XclExpStream& rStrm ) +{ + if( mxExtNameBfr ) + mxExtNameBfr->Save( rStrm ); +} + +void XclExpExternSheetBase::WriteExtNameBufferXml( XclExpXmlStream& rStrm ) +{ + if( mxExtNameBfr ) + mxExtNameBfr->SaveXml( rStrm ); +} + +// External documents (EXTERNSHEET, BIFF5/BIFF7) ============================== + +XclExpExternSheet::XclExpExternSheet( const XclExpRoot& rRoot, sal_Unicode cCode ) : + XclExpExternSheetBase( rRoot, EXC_ID_EXTERNSHEET ) +{ + Init( OUStringChar(cCode) ); +} + +XclExpExternSheet::XclExpExternSheet( const XclExpRoot& rRoot, std::u16string_view rTabName ) : + XclExpExternSheetBase( rRoot, EXC_ID_EXTERNSHEET ) +{ + // reference to own sheet: \03<sheetname> + Init(OUStringConcatenation(OUStringChar(EXC_EXTSH_TABNAME) + rTabName)); +} + +void XclExpExternSheet::Save( XclExpStream& rStrm ) +{ + // EXTERNSHEET record + XclExpRecord::Save( rStrm ); + // EXTERNNAME records + WriteExtNameBuffer( rStrm ); +} + +void XclExpExternSheet::Init( std::u16string_view rEncUrl ) +{ + OSL_ENSURE_BIFF( GetBiff() <= EXC_BIFF5 ); + maTabName.AssignByte( rEncUrl, GetTextEncoding(), XclStrFlags::EightBitLength ); + SetRecSize( maTabName.GetSize() ); +} + +sal_uInt16 XclExpExternSheet::InsertAddIn( const OUString& rName ) +{ + return GetExtNameBuffer().InsertAddIn( rName ); +} + +void XclExpExternSheet::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt8 nNameSize = static_cast< sal_uInt8 >( maTabName.Len() ); + // special case: reference to own sheet (starting with '\03') needs wrong string length + if( maTabName.GetChar( 0 ) == EXC_EXTSH_TABNAME ) + --nNameSize; + rStrm << nNameSize; + maTabName.WriteBuffer( rStrm ); +} + +// External document (SUPBOOK, BIFF8) ========================================= + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, sal_uInt16 nXclTabCount ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK, 4 ), + meType( XclSupbookType::Self ), + mnXclTabCount( nXclTabCount ), + mnFileId( 0 ) +{ +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK, 4 ), + meType( XclSupbookType::Addin ), + mnXclTabCount( 1 ), + mnFileId( 0 ) +{ +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl, XclSupbookType ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK ), + maUrl( rUrl ), + maUrlEncoded( rUrl ), + meType( XclSupbookType::Eurotool ), + mnXclTabCount( 0 ), + mnFileId( 0 ) +{ + SetRecSize( 2 + maUrlEncoded.GetSize() ); +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK ), + maUrl( rUrl ), + maUrlEncoded( XclExpUrlHelper::EncodeUrl( rRoot, rUrl ) ), + meType( XclSupbookType::Extern ), + mnXclTabCount( 0 ), + mnFileId( 0 ) +{ + SetRecSize( 2 + maUrlEncoded.GetSize() ); + + // We need to create all tables up front to ensure the correct table order. + ScExternalRefManager* pRefMgr = rRoot.GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId( rUrl ); + mnFileId = nFileId + 1; + ScfStringVec aTabNames; + pRefMgr->getAllCachedTableNames( nFileId, aTabNames ); + size_t nTabIndex = 0; + for( const auto& rTabName : aTabNames ) + { + InsertTabName( rTabName, pRefMgr->getCacheTable( nFileId, nTabIndex ) ); + ++nTabIndex; + } +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, const OUString& rApplic, const OUString& rTopic ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK, 4 ), + maUrl( rApplic ), + maDdeTopic( rTopic ), + maUrlEncoded( XclExpUrlHelper::EncodeDde( rApplic, rTopic ) ), + meType( XclSupbookType::Special ), + mnXclTabCount( 0 ), + mnFileId( 0 ) +{ + SetRecSize( 2 + maUrlEncoded.GetSize() ); +} + +bool XclExpSupbook::IsUrlLink( std::u16string_view rUrl ) const +{ + return (meType == XclSupbookType::Extern || meType == XclSupbookType::Eurotool) && (maUrl == rUrl); +} + +bool XclExpSupbook::IsDdeLink( std::u16string_view rApplic, std::u16string_view rTopic ) const +{ + return (meType == XclSupbookType::Special) && (maUrl == rApplic) && (maDdeTopic == rTopic); +} + +void XclExpSupbook::FillRefLogEntry( XclExpRefLogEntry& rRefLogEntry, + sal_uInt16 nFirstSBTab, sal_uInt16 nLastSBTab ) const +{ + rRefLogEntry.mpUrl = maUrlEncoded.IsEmpty() ? nullptr : &maUrlEncoded; + rRefLogEntry.mpFirstTab = GetTabName( nFirstSBTab ); + rRefLogEntry.mpLastTab = GetTabName( nLastSBTab ); +} + +void XclExpSupbook::StoreCellRange( const ScRange& rRange, sal_uInt16 nSBTab ) +{ + if( XclExpXct* pXct = maXctList.GetRecord( nSBTab ) ) + pXct->StoreCellRange( rRange ); +} + +void XclExpSupbook::StoreCell_( sal_uInt16 nSBTab, const ScAddress& rCell ) +{ + if( XclExpXct* pXct = maXctList.GetRecord( nSBTab ) ) + pXct->StoreCell_( rCell ); +} + +void XclExpSupbook::StoreCellRange_( sal_uInt16 nSBTab, const ScRange& rRange ) +{ + // multi-table range is not allowed! + if( rRange.aStart.Tab() == rRange.aEnd.Tab() ) + if( XclExpXct* pXct = maXctList.GetRecord( nSBTab ) ) + pXct->StoreCellRange_( rRange ); +} + +sal_uInt16 XclExpSupbook::GetTabIndex( const OUString& rTabName ) const +{ + XclExpString aXclName(rTabName); + size_t nSize = maXctList.GetSize(); + for (size_t i = 0; i < nSize; ++i) + { + XclExpXctRef aRec = maXctList.GetRecord(i); + if (aXclName == aRec->GetTabName()) + return ulimit_cast<sal_uInt16>(i); + } + return EXC_NOTAB; +} + +sal_uInt16 XclExpSupbook::GetTabCount() const +{ + return ulimit_cast<sal_uInt16>(maXctList.GetSize()); +} + +sal_uInt16 XclExpSupbook::InsertTabName( const OUString& rTabName, ScExternalRefCache::TableTypeRef const & xCacheTable ) +{ + SAL_WARN_IF( meType != XclSupbookType::Extern, "sc.filter", "Don't insert sheet names here" ); + sal_uInt16 nSBTab = ulimit_cast< sal_uInt16 >( maXctList.GetSize() ); + XclExpXctRef xXct = new XclExpXct( GetRoot(), rTabName, nSBTab, xCacheTable ); + AddRecSize( xXct->GetTabName().GetSize() ); + maXctList.AppendRecord( xXct ); + return nSBTab; +} + +sal_uInt16 XclExpSupbook::InsertAddIn( const OUString& rName ) +{ + return GetExtNameBuffer().InsertAddIn( rName ); +} + +sal_uInt16 XclExpSupbook::InsertEuroTool( const OUString& rName ) +{ + return GetExtNameBuffer().InsertEuroTool( rName ); +} + +sal_uInt16 XclExpSupbook::InsertDde( const OUString& rItem ) +{ + return GetExtNameBuffer().InsertDde( maUrl, maDdeTopic, rItem ); +} + +sal_uInt16 XclExpSupbook::InsertExtName( const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + return GetExtNameBuffer().InsertExtName(*this, rName, rArray); +} + +XclSupbookType XclExpSupbook::GetType() const +{ + return meType; +} + +sal_uInt16 XclExpSupbook::GetFileId() const +{ + return mnFileId; +} + +const OUString& XclExpSupbook::GetUrl() const +{ + return maUrl; +} + +void XclExpSupbook::Save( XclExpStream& rStrm ) +{ + // SUPBOOK record + XclExpRecord::Save( rStrm ); + // XCT record, CRN records + maXctList.Save( rStrm ); + // EXTERNNAME records + WriteExtNameBuffer( rStrm ); +} + +void XclExpSupbook::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pExternalLink = rStrm.GetCurrentStream(); + + // Add relation for this stream, e.g. xl/externalLinks/_rels/externalLink1.xml.rels + sal_uInt16 nLevel = 0; + bool bRel = true; + + // BuildFileName delete ../ and convert them to nLevel + // but addrelation needs ../ instead of nLevel, so we have to convert it back + OUString sFile = XclExpHyperlink::BuildFileName(nLevel, bRel, maUrl, GetRoot(), true); + while (nLevel-- > 0) + sFile = "../" + sFile; + + OUString sId = rStrm.addRelation( pExternalLink->getOutputStream(), + oox::getRelationship(Relationship::EXTERNALLINKPATH), sFile, true ); + + pExternalLink->startElement( XML_externalLink, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8()); + + pExternalLink->startElement( XML_externalBook, + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_r, XML_id), sId.toUtf8()); + + if (!maXctList.IsEmpty()) + { + pExternalLink->startElement(XML_sheetNames); + for (size_t nPos = 0, nSize = maXctList.GetSize(); nPos < nSize; ++nPos) + { + pExternalLink->singleElement(XML_sheetName, + XML_val, XclXmlUtils::ToOString(maXctList.GetRecord(nPos)->GetTabName())); + } + pExternalLink->endElement( XML_sheetNames); + + } + + if (mxExtNameBfr) + { + pExternalLink->startElement(XML_definedNames); + // externalName elements + WriteExtNameBufferXml( rStrm ); + pExternalLink->endElement(XML_definedNames); + } + + if (!maXctList.IsEmpty()) + { + pExternalLink->startElement(XML_sheetDataSet); + + // sheetData elements + maXctList.SaveXml( rStrm ); + + pExternalLink->endElement( XML_sheetDataSet); + + } + pExternalLink->endElement( XML_externalBook); + pExternalLink->endElement( XML_externalLink); +} + +const XclExpString* XclExpSupbook::GetTabName( sal_uInt16 nSBTab ) const +{ + XclExpXctRef xXct = maXctList.GetRecord( nSBTab ); + return xXct ? &xXct->GetTabName() : nullptr; +} + +void XclExpSupbook::WriteBody( XclExpStream& rStrm ) +{ + switch( meType ) + { + case XclSupbookType::Self: + rStrm << mnXclTabCount << EXC_SUPB_SELF; + break; + case XclSupbookType::Extern: + case XclSupbookType::Special: + case XclSupbookType::Eurotool: + { + sal_uInt16 nCount = ulimit_cast< sal_uInt16 >( maXctList.GetSize() ); + rStrm << nCount << maUrlEncoded; + + for( size_t nPos = 0, nSize = maXctList.GetSize(); nPos < nSize; ++nPos ) + rStrm << maXctList.GetRecord( nPos )->GetTabName(); + } + break; + case XclSupbookType::Addin: + rStrm << mnXclTabCount << EXC_SUPB_ADDIN; + break; + default: + SAL_WARN( "sc.filter", "Unhandled SUPBOOK type " << meType); + } +} + +// All SUPBOOKS in a document ================================================= + +XclExpSupbookBuffer::XclExpSupbookBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnOwnDocSB( SAL_MAX_UINT16 ), + mnAddInSB( SAL_MAX_UINT16 ) +{ + XclExpTabInfo& rTabInfo = GetTabInfo(); + sal_uInt16 nXclCnt = rTabInfo.GetXclTabCount(); + sal_uInt16 nCodeCnt = static_cast< sal_uInt16 >( GetExtDocOptions().GetCodeNameCount() ); + size_t nCount = nXclCnt + rTabInfo.GetXclExtTabCount(); + + OSL_ENSURE( nCount > 0, "XclExpSupbookBuffer::XclExpSupbookBuffer - no sheets to export" ); + if( nCount ) + { + maSBIndexVec.resize( nCount ); + + // self-ref SUPBOOK first of list + XclExpSupbookRef xSupbook = new XclExpSupbook( GetRoot(), ::std::max( nXclCnt, nCodeCnt ) ); + mnOwnDocSB = Append( xSupbook ); + for( sal_uInt16 nXclTab = 0; nXclTab < nXclCnt; ++nXclTab ) + maSBIndexVec[ nXclTab ].Set( mnOwnDocSB, nXclTab ); + } +} + +XclExpXti XclExpSupbookBuffer::GetXti( sal_uInt16 nFirstXclTab, sal_uInt16 nLastXclTab, + XclExpRefLogEntry* pRefLogEntry ) const +{ + XclExpXti aXti; + size_t nSize = maSBIndexVec.size(); + if( (nFirstXclTab < nSize) && (nLastXclTab < nSize) ) + { + // index of the SUPBOOK record + aXti.mnSupbook = maSBIndexVec[ nFirstXclTab ].mnSupbook; + + // all sheets in the same supbook? + bool bSameSB = true; + for( sal_uInt16 nXclTab = nFirstXclTab + 1; bSameSB && (nXclTab <= nLastXclTab); ++nXclTab ) + { + bSameSB = maSBIndexVec[ nXclTab ].mnSupbook == aXti.mnSupbook; + if( !bSameSB ) + nLastXclTab = nXclTab - 1; + } + aXti.mnFirstSBTab = maSBIndexVec[ nFirstXclTab ].mnSBTab; + aXti.mnLastSBTab = maSBIndexVec[ nLastXclTab ].mnSBTab; + + // fill external reference log entry (for change tracking) + if( pRefLogEntry ) + { + pRefLogEntry->mnFirstXclTab = nFirstXclTab; + pRefLogEntry->mnLastXclTab = nLastXclTab; + XclExpSupbookRef xSupbook = maSupbookList.GetRecord( aXti.mnSupbook ); + if( xSupbook ) + xSupbook->FillRefLogEntry( *pRefLogEntry, aXti.mnFirstSBTab, aXti.mnLastSBTab ); + } + } + else + { + // special range, i.e. for deleted sheets or add-ins + aXti.mnSupbook = mnOwnDocSB; + aXti.mnFirstSBTab = nFirstXclTab; + aXti.mnLastSBTab = nLastXclTab; + } + + return aXti; +} + +void XclExpSupbookBuffer::StoreCellRange( const ScRange& rRange ) +{ + sal_uInt16 nXclTab = GetTabInfo().GetXclTab( rRange.aStart.Tab() ); + if( nXclTab < maSBIndexVec.size() ) + { + const XclExpSBIndex& rSBIndex = maSBIndexVec[ nXclTab ]; + XclExpSupbookRef xSupbook = maSupbookList.GetRecord( rSBIndex.mnSupbook ); + OSL_ENSURE( xSupbook , "XclExpSupbookBuffer::StoreCellRange - missing SUPBOOK record" ); + if( xSupbook ) + xSupbook->StoreCellRange( rRange, rSBIndex.mnSBTab ); + } +} + +namespace { + +class FindSBIndexEntry +{ +public: + explicit FindSBIndexEntry(sal_uInt16 nSupbookId, sal_uInt16 nTabId) : + mnSupbookId(nSupbookId), mnTabId(nTabId) {} + + bool operator()(const XclExpSupbookBuffer::XclExpSBIndex& r) const + { + return mnSupbookId == r.mnSupbook && mnTabId == r.mnSBTab; + } + +private: + sal_uInt16 mnSupbookId; + sal_uInt16 mnTabId; +}; + +} + +void XclExpSupbookBuffer::StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell ) +{ + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + const OUString* pUrl = pRefMgr->getExternalFileName(nFileId); + if (!pUrl) + return; + + XclExpSupbookRef xSupbook; + sal_uInt16 nSupbookId; + if (!GetSupbookUrl(xSupbook, nSupbookId, *pUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), *pUrl); + nSupbookId = Append(xSupbook); + } + + sal_uInt16 nSheetId = xSupbook->GetTabIndex(rTabName); + if (nSheetId == EXC_NOTAB) + // specified table name not found in this SUPBOOK. + return; + + FindSBIndexEntry f(nSupbookId, nSheetId); + if (::std::none_of(maSBIndexVec.begin(), maSBIndexVec.end(), f)) + { + maSBIndexVec.emplace_back(); + XclExpSBIndex& r = maSBIndexVec.back(); + r.mnSupbook = nSupbookId; + r.mnSBTab = nSheetId; + } + + xSupbook->StoreCell_(nSheetId, rCell); +} + +void XclExpSupbookBuffer::StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) +{ + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + const OUString* pUrl = pRefMgr->getExternalFileName(nFileId); + if (!pUrl) + return; + + XclExpSupbookRef xSupbook; + sal_uInt16 nSupbookId; + if (!GetSupbookUrl(xSupbook, nSupbookId, *pUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), *pUrl); + nSupbookId = Append(xSupbook); + } + + SCTAB nTabCount = rRange.aEnd.Tab() - rRange.aStart.Tab() + 1; + + // If this is a multi-table range, get token for each table. + using namespace ::formula; + SCTAB aMatrixListSize = 0; + + // This is a new'ed instance, so we must manage its life cycle here. + ScExternalRefCache::TokenArrayRef pArray = pRefMgr->getDoubleRefTokens(nFileId, rTabName, rRange, nullptr); + if (!pArray) + return; + + FormulaTokenArrayPlainIterator aIter(*pArray); + for (FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + if (p->GetType() == svMatrix) + ++aMatrixListSize; + else if (p->GetOpCode() != ocSep) + { + // This is supposed to be ocSep!!! + return; + } + } + + if (aMatrixListSize != nTabCount) + { + // matrix size mismatch! + return; + } + + sal_uInt16 nFirstSheetId = xSupbook->GetTabIndex(rTabName); + + ScRange aRange(rRange); + aRange.aStart.SetTab(0); + aRange.aEnd.SetTab(0); + for (SCTAB nTab = 0; nTab < nTabCount; ++nTab) + { + sal_uInt16 nSheetId = nFirstSheetId + static_cast<sal_uInt16>(nTab); + FindSBIndexEntry f(nSupbookId, nSheetId); + if (::std::none_of(maSBIndexVec.begin(), maSBIndexVec.end(), f)) + { + maSBIndexVec.emplace_back(); + XclExpSBIndex& r = maSBIndexVec.back(); + r.mnSupbook = nSupbookId; + r.mnSBTab = nSheetId; + } + + xSupbook->StoreCellRange_(nSheetId, aRange); + } +} + +bool XclExpSupbookBuffer::InsertAddIn( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rName ) +{ + XclExpSupbookRef xSupbook; + if( mnAddInSB == SAL_MAX_UINT16 ) + { + xSupbook = new XclExpSupbook( GetRoot() ); + mnAddInSB = Append( xSupbook ); + } + else + xSupbook = maSupbookList.GetRecord( mnAddInSB ); + OSL_ENSURE( xSupbook, "XclExpSupbookBuffer::InsertAddin - missing add-in supbook" ); + rnSupbook = mnAddInSB; + rnExtName = xSupbook->InsertAddIn( rName ); + return rnExtName > 0; +} + +bool XclExpSupbookBuffer::InsertEuroTool( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rName ) +{ + XclExpSupbookRef xSupbook; + OUString aUrl( "\001\010EUROTOOL.XLA" ); + if( !GetSupbookUrl( xSupbook, rnSupbook, aUrl ) ) + { + xSupbook = new XclExpSupbook( GetRoot(), aUrl, XclSupbookType::Eurotool ); + rnSupbook = Append( xSupbook ); + } + rnExtName = xSupbook->InsertEuroTool( rName ); + return rnExtName > 0; +} + +bool XclExpSupbookBuffer::InsertDde( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) +{ + XclExpSupbookRef xSupbook; + if( !GetSupbookDde( xSupbook, rnSupbook, rApplic, rTopic ) ) + { + xSupbook = new XclExpSupbook( GetRoot(), rApplic, rTopic ); + rnSupbook = Append( xSupbook ); + } + rnExtName = xSupbook->InsertDde( rItem ); + return rnExtName > 0; +} + +bool XclExpSupbookBuffer::InsertExtName( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + XclExpSupbookRef xSupbook; + if (!GetSupbookUrl(xSupbook, rnSupbook, rUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), rUrl); + rnSupbook = Append(xSupbook); + } + rnExtName = xSupbook->InsertExtName(rName, rArray); + return rnExtName > 0; +} + +XclExpXti XclExpSupbookBuffer::GetXti( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + XclExpRefLogEntry* pRefLogEntry ) +{ + XclExpXti aXti(0, EXC_NOTAB, EXC_NOTAB); + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + const OUString* pUrl = pRefMgr->getExternalFileName(nFileId); + if (!pUrl) + return aXti; + + XclExpSupbookRef xSupbook; + sal_uInt16 nSupbookId; + if (!GetSupbookUrl(xSupbook, nSupbookId, *pUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), *pUrl); + nSupbookId = Append(xSupbook); + } + aXti.mnSupbook = nSupbookId; + + sal_uInt16 nFirstSheetId = xSupbook->GetTabIndex(rTabName); + if (nFirstSheetId == EXC_NOTAB) + { + // first sheet not found in SUPBOOK. + return aXti; + } + sal_uInt16 nSheetCount = xSupbook->GetTabCount(); + for (sal_uInt16 i = 0; i < nXclTabSpan; ++i) + { + sal_uInt16 nSheetId = nFirstSheetId + i; + if (nSheetId >= nSheetCount) + return aXti; + + FindSBIndexEntry f(nSupbookId, nSheetId); + if (::std::none_of(maSBIndexVec.begin(), maSBIndexVec.end(), f)) + { + maSBIndexVec.emplace_back(); + XclExpSBIndex& r = maSBIndexVec.back(); + r.mnSupbook = nSupbookId; + r.mnSBTab = nSheetId; + } + if (i == 0) + aXti.mnFirstSBTab = nSheetId; + if (i == nXclTabSpan - 1) + aXti.mnLastSBTab = nSheetId; + } + + if (pRefLogEntry) + { + pRefLogEntry->mnFirstXclTab = 0; + pRefLogEntry->mnLastXclTab = 0; + if (xSupbook) + xSupbook->FillRefLogEntry(*pRefLogEntry, aXti.mnFirstSBTab, aXti.mnLastSBTab); + } + + return aXti; +} + +void XclExpSupbookBuffer::Save( XclExpStream& rStrm ) +{ + maSupbookList.Save( rStrm ); +} + +void XclExpSupbookBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + // Unused external references are not saved, only kept in memory. + // Those that are saved must be indexed from 1, so indexes must be reordered + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + vector<sal_uInt16> aExternFileIds; + for (size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos) + { + XclExpSupbookRef xRef(maSupbookList.GetRecord(nPos)); + // fileIDs are indexed from 1 in xlsx, and from 0 in ScExternalRefManager + // converting between them require a -1 or +1 + if (xRef->GetType() == XclSupbookType::Extern) + aExternFileIds.push_back(xRef->GetFileId() - 1); + } + if (aExternFileIds.size() > 0) + pRefMgr->setSkipUnusedFileIds(aExternFileIds); + + ::std::map< sal_uInt16, OUString > aMap; + for (size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos) + { + XclExpSupbookRef xRef( maSupbookList.GetRecord( nPos)); + if (xRef->GetType() != XclSupbookType::Extern) + continue; // handle only external reference (for now?) + + sal_uInt16 nId = xRef->GetFileId(); + sal_uInt16 nUsedId = pRefMgr->convertFileIdToUsedFileId(nId - 1) + 1; + const OUString& rUrl = xRef->GetUrl(); + ::std::pair< ::std::map< sal_uInt16, OUString >::iterator, bool > aInsert( + aMap.insert( ::std::make_pair( nId, rUrl))); + if (!aInsert.second) + { + SAL_WARN( "sc.filter", "XclExpSupbookBuffer::SaveXml: file ID already used: " << nId << + " wanted for " << rUrl << " and is " << (*aInsert.first).second << + (rUrl == (*aInsert.first).second ? " multiple Supbook not supported" : "")); + continue; + } + OUString sId; + sax_fastparser::FSHelperPtr pExternalLink = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName( "xl/", "externalLinks/externalLink", nUsedId), + XclXmlUtils::GetStreamName( nullptr, "externalLinks/externalLink", nUsedId), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml", + CREATE_OFFICEDOC_RELATION_TYPE("externalLink"), + &sId ); + + // externalReference entry in workbook externalReferences + rStrm.GetCurrentStream()->singleElement( XML_externalReference, + FSNS(XML_r, XML_id), sId.toUtf8() ); + + // Each externalBook in a separate stream. + rStrm.PushStream( pExternalLink ); + xRef->SaveXml( rStrm ); + rStrm.PopStream(); + } +} + +bool XclExpSupbookBuffer::HasExternalReferences() const +{ + for (size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos) + { + if (maSupbookList.GetRecord( nPos)->GetType() == XclSupbookType::Extern) + return true; + } + return false; +} + +bool XclExpSupbookBuffer::GetSupbookUrl( + XclExpSupbookRef& rxSupbook, sal_uInt16& rnIndex, std::u16string_view rUrl ) const +{ + for( size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos ) + { + rxSupbook = maSupbookList.GetRecord( nPos ); + if( rxSupbook->IsUrlLink( rUrl ) ) + { + rnIndex = ulimit_cast< sal_uInt16 >( nPos ); + return true; + } + } + return false; +} + +bool XclExpSupbookBuffer::GetSupbookDde( XclExpSupbookRef& rxSupbook, + sal_uInt16& rnIndex, std::u16string_view rApplic, std::u16string_view rTopic ) const +{ + for( size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos ) + { + rxSupbook = maSupbookList.GetRecord( nPos ); + if( rxSupbook->IsDdeLink( rApplic, rTopic ) ) + { + rnIndex = ulimit_cast< sal_uInt16 >( nPos ); + return true; + } + } + return false; +} + +sal_uInt16 XclExpSupbookBuffer::Append( XclExpSupbookRef const & xSupbook ) +{ + maSupbookList.AppendRecord( xSupbook ); + return ulimit_cast< sal_uInt16 >( maSupbookList.GetSize() - 1 ); +} + +// Export link manager ======================================================== + +XclExpLinkManagerImpl::XclExpLinkManagerImpl( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpLinkManagerImpl5::XclExpLinkManagerImpl5( const XclExpRoot& rRoot ) : + XclExpLinkManagerImpl( rRoot ) +{ +} + +void XclExpLinkManagerImpl5::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + FindInternal( rnExtSheet, rnFirstXclTab, nFirstScTab ); + if( (rnFirstXclTab == EXC_TAB_DELETED) || (nFirstScTab == nLastScTab) ) + { + rnLastXclTab = rnFirstXclTab; + } + else + { + sal_uInt16 nDummyExtSheet; + FindInternal( nDummyExtSheet, rnLastXclTab, nLastScTab ); + } + + OSL_ENSURE( !pRefLogEntry, "XclExpLinkManagerImpl5::FindExtSheet - fill reflog entry not implemented" ); +} + +sal_uInt16 XclExpLinkManagerImpl5::FindExtSheet( sal_Unicode cCode ) +{ + sal_uInt16 nExtSheet; + FindInternal( nExtSheet, cCode ); + return nExtSheet; +} + +void XclExpLinkManagerImpl5::FindExtSheet( + sal_uInt16 /*nFileId*/, const OUString& /*rTabName*/, sal_uInt16 /*nXclTabSpan*/, + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnFirstSBTab*/, sal_uInt16& /*rnLastSBTab*/, + XclExpRefLogEntry* /*pRefLogEntry*/ ) +{ + // not implemented +} + +void XclExpLinkManagerImpl5::StoreCellRange( const ScSingleRefData& /*rRef1*/, const ScSingleRefData& /*rRef2*/, const ScAddress& /*rPos*/ ) +{ + // not implemented +} + +void XclExpLinkManagerImpl5::StoreCell( sal_uInt16 /*nFileId*/, const OUString& /*rTabName*/, const ScAddress& /*rPos*/ ) +{ + // not implemented +} + +void XclExpLinkManagerImpl5::StoreCellRange( sal_uInt16 /*nFileId*/, const OUString& /*rTabName*/, const ScRange& /*rRange*/ ) +{ + // not implemented +} + +bool XclExpLinkManagerImpl5::InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + XclExpExtSheetRef xExtSheet = FindInternal( rnExtSheet, EXC_EXTSH_ADDIN ); + if( xExtSheet ) + { + rnExtName = xExtSheet->InsertAddIn( rName ); + return rnExtName > 0; + } + return false; +} + +bool XclExpLinkManagerImpl5::InsertEuroTool( + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnExtName*/, const OUString& /*rName*/ ) +{ + return false; +} + +bool XclExpLinkManagerImpl5::InsertDde( + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnExtName*/, + const OUString& /*rApplic*/, const OUString& /*rTopic*/, const OUString& /*rItem*/ ) +{ + // not implemented + return false; +} + +bool XclExpLinkManagerImpl5::InsertExtName( + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnExtName*/, const OUString& /*rUrl*/, + const OUString& /*rName*/, const ScExternalRefCache::TokenArrayRef& /*rArray*/ ) +{ + // not implemented + return false; +} + +void XclExpLinkManagerImpl5::Save( XclExpStream& rStrm ) +{ + if( sal_uInt16 nExtSheetCount = GetExtSheetCount() ) + { + // EXTERNCOUNT record + XclExpUInt16Record( EXC_ID_EXTERNCOUNT, nExtSheetCount ).Save( rStrm ); + // list of EXTERNSHEET records with EXTERNNAME, XCT, CRN records + maExtSheetList.Save( rStrm ); + } +} + +void XclExpLinkManagerImpl5::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ + // not applicable +} + +sal_uInt16 XclExpLinkManagerImpl5::GetExtSheetCount() const +{ + return static_cast< sal_uInt16 >( maExtSheetList.GetSize() ); +} + +sal_uInt16 XclExpLinkManagerImpl5::AppendInternal( XclExpExtSheetRef const & xExtSheet ) +{ + if( GetExtSheetCount() < 0x7FFF ) + { + maExtSheetList.AppendRecord( xExtSheet ); + // return negated one-based EXTERNSHEET index (i.e. 0xFFFD for 3rd record) + return static_cast< sal_uInt16 >( -GetExtSheetCount() ); + } + return 0; +} + +void XclExpLinkManagerImpl5::CreateInternal() +{ + if( !maIntTabMap.empty() ) + return; + + // create EXTERNSHEET records for all internal exported sheets + XclExpTabInfo& rTabInfo = GetTabInfo(); + for( SCTAB nScTab = 0, nScCnt = rTabInfo.GetScTabCount(); nScTab < nScCnt; ++nScTab ) + { + if( rTabInfo.IsExportTab( nScTab ) ) + { + XclExpExtSheetRef xRec; + if( nScTab == GetCurrScTab() ) + xRec = new XclExpExternSheet( GetRoot(), EXC_EXTSH_OWNTAB ); + else + xRec = new XclExpExternSheet( GetRoot(), rTabInfo.GetScTabName( nScTab ) ); + maIntTabMap[ nScTab ] = AppendInternal( xRec ); + } + } +} + +XclExpLinkManagerImpl5::XclExpExtSheetRef XclExpLinkManagerImpl5::GetInternal( sal_uInt16 nExtSheet ) +{ + return maExtSheetList.GetRecord( static_cast< sal_uInt16 >( -nExtSheet - 1 ) ); +} + +XclExpLinkManagerImpl5::XclExpExtSheetRef XclExpLinkManagerImpl5::FindInternal( + sal_uInt16& rnExtSheet, sal_uInt16& rnXclTab, SCTAB nScTab ) +{ + // create internal EXTERNSHEET records on demand + CreateInternal(); + + // try to find an EXTERNSHEET record - if not, return a "deleted sheet" reference + XclExpExtSheetRef xExtSheet; + XclExpIntTabMap::const_iterator aIt = maIntTabMap.find( nScTab ); + if( aIt == maIntTabMap.end() ) + { + xExtSheet = FindInternal( rnExtSheet, EXC_EXTSH_OWNDOC ); + rnXclTab = EXC_TAB_DELETED; + } + else + { + rnExtSheet = aIt->second; + xExtSheet = GetInternal( rnExtSheet ); + rnXclTab = GetTabInfo().GetXclTab( nScTab ); + } + return xExtSheet; +} + +XclExpLinkManagerImpl5::XclExpExtSheetRef XclExpLinkManagerImpl5::FindInternal( + sal_uInt16& rnExtSheet, sal_Unicode cCode ) +{ + XclExpExtSheetRef xExtSheet; + XclExpCodeMap::const_iterator aIt = maCodeMap.find( cCode ); + if( aIt == maCodeMap.end() ) + { + xExtSheet = new XclExpExternSheet( GetRoot(), cCode ); + rnExtSheet = maCodeMap[ cCode ] = AppendInternal( xExtSheet ); + } + else + { + rnExtSheet = aIt->second; + xExtSheet = GetInternal( rnExtSheet ); + } + return xExtSheet; +} + +XclExpLinkManagerImpl8::XclExpLinkManagerImpl8( const XclExpRoot& rRoot ) : + XclExpLinkManagerImpl( rRoot ), + maSBBuffer( rRoot ) +{ +} + +void XclExpLinkManagerImpl8::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + XclExpTabInfo& rTabInfo = GetTabInfo(); + rnFirstXclTab = rTabInfo.GetXclTab( nFirstScTab ); + rnLastXclTab = rTabInfo.GetXclTab( nLastScTab ); + rnExtSheet = InsertXti( maSBBuffer.GetXti( rnFirstXclTab, rnLastXclTab, pRefLogEntry ) ); +} + +sal_uInt16 XclExpLinkManagerImpl8::FindExtSheet( sal_Unicode cCode ) +{ + OSL_ENSURE( (cCode == EXC_EXTSH_OWNDOC) || (cCode == EXC_EXTSH_ADDIN), + "XclExpLinkManagerImpl8::FindExtSheet - unknown externsheet code" ); + return InsertXti( maSBBuffer.GetXti( EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); +} + +void XclExpLinkManagerImpl8::FindExtSheet( + sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) +{ + XclExpXti aXti = maSBBuffer.GetXti(nFileId, rTabName, nXclTabSpan, pRefLogEntry); + rnExtSheet = InsertXti(aXti); + rnFirstSBTab = aXti.mnFirstSBTab; + rnLastSBTab = aXti.mnLastSBTab; +} + +void XclExpLinkManagerImpl8::StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) +{ + ScAddress aAbs1 = rRef1.toAbs(GetRoot().GetDoc(), rPos); + ScAddress aAbs2 = rRef2.toAbs(GetRoot().GetDoc(), rPos); + if (!(!rRef1.IsDeleted() && !rRef2.IsDeleted() && (aAbs1.Tab() >= 0) && (aAbs2.Tab() >= 0))) + return; + + const XclExpTabInfo& rTabInfo = GetTabInfo(); + SCTAB nFirstScTab = aAbs1.Tab(); + SCTAB nLastScTab = aAbs2.Tab(); + ScRange aRange(aAbs1.Col(), aAbs1.Row(), 0, aAbs2.Col(), aAbs2.Row(), 0); + for (SCTAB nScTab = nFirstScTab; nScTab <= nLastScTab; ++nScTab) + { + if( rTabInfo.IsExternalTab( nScTab ) ) + { + aRange.aStart.SetTab( nScTab ); + aRange.aEnd.SetTab( nScTab ); + maSBBuffer.StoreCellRange( aRange ); + } + } +} + +void XclExpLinkManagerImpl8::StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) +{ + maSBBuffer.StoreCell(nFileId, rTabName, rPos); +} + +void XclExpLinkManagerImpl8::StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) +{ + maSBBuffer.StoreCellRange(nFileId, rTabName, rRange); +} + +bool XclExpLinkManagerImpl8::InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertAddIn( nSupbook, rnExtName, rName ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +bool XclExpLinkManagerImpl8::InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertEuroTool( nSupbook, rnExtName, rName ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +bool XclExpLinkManagerImpl8::InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertDde( nSupbook, rnExtName, rApplic, rTopic, rItem ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +bool XclExpLinkManagerImpl8::InsertExtName( sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rUrl, const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertExtName( nSupbook, rnExtName, rUrl, rName, rArray ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +void XclExpLinkManagerImpl8::Save( XclExpStream& rStrm ) +{ + if( maXtiVec.empty() ) + return; + + // SUPBOOKs, XCTs, CRNs, EXTERNNAMEs + maSBBuffer.Save( rStrm ); + + // EXTERNSHEET + sal_uInt16 nCount = ulimit_cast< sal_uInt16 >( maXtiVec.size() ); + rStrm.StartRecord( EXC_ID_EXTERNSHEET, 2 + 6 * nCount ); + rStrm << nCount; + rStrm.SetSliceSize( 6 ); + for( const auto& rXti : maXtiVec ) + rXti.Save( rStrm ); + rStrm.EndRecord(); +} + +void XclExpLinkManagerImpl8::SaveXml( XclExpXmlStream& rStrm ) +{ + if (maSBBuffer.HasExternalReferences()) + { + sax_fastparser::FSHelperPtr pWorkbook = rStrm.GetCurrentStream(); + pWorkbook->startElement(XML_externalReferences); + + // externalLink, externalBook, sheetNames, sheetDataSet, externalName + maSBBuffer.SaveXml( rStrm ); + + pWorkbook->endElement( XML_externalReferences); + } + + // TODO: equivalent for EXTERNSHEET in OOXML? +#if 0 + if( !maXtiVec.empty() ) + { + for( const auto& rXti : maXtiVec ) + rXti.SaveXml( rStrm ); + } +#endif +} + +sal_uInt16 XclExpLinkManagerImpl8::InsertXti( const XclExpXti& rXti ) +{ + auto aIt = std::find(maXtiVec.begin(), maXtiVec.end(), rXti); + if (aIt != maXtiVec.end()) + return ulimit_cast< sal_uInt16 >( std::distance(maXtiVec.begin(), aIt) ); + maXtiVec.push_back( rXti ); + return ulimit_cast< sal_uInt16 >( maXtiVec.size() - 1 ); +} + +XclExpLinkManager::XclExpLinkManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + switch( GetBiff() ) + { + case EXC_BIFF5: + mxImpl = std::make_shared<XclExpLinkManagerImpl5>( rRoot ); + break; + case EXC_BIFF8: + mxImpl = std::make_shared<XclExpLinkManagerImpl8>( rRoot ); + break; + default: + DBG_ERROR_BIFF(); + } +} + +XclExpLinkManager::~XclExpLinkManager() +{ +} + +void XclExpLinkManager::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnXclTab, + SCTAB nScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + mxImpl->FindExtSheet( rnExtSheet, rnXclTab, rnXclTab, nScTab, nScTab, pRefLogEntry ); +} + +void XclExpLinkManager::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + mxImpl->FindExtSheet( rnExtSheet, rnFirstXclTab, rnLastXclTab, nFirstScTab, nLastScTab, pRefLogEntry ); +} + +sal_uInt16 XclExpLinkManager::FindExtSheet( sal_Unicode cCode ) +{ + return mxImpl->FindExtSheet( cCode ); +} + +void XclExpLinkManager::FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) +{ + mxImpl->FindExtSheet( nFileId, rTabName, nXclTabSpan, rnExtSheet, rnFirstSBTab, rnLastSBTab, pRefLogEntry ); +} + +void XclExpLinkManager::StoreCell( const ScSingleRefData& rRef, const ScAddress& rPos ) +{ + mxImpl->StoreCellRange(rRef, rRef, rPos); +} + +void XclExpLinkManager::StoreCellRange( const ScComplexRefData& rRef, const ScAddress& rPos ) +{ + mxImpl->StoreCellRange(rRef.Ref1, rRef.Ref2, rPos); +} + +void XclExpLinkManager::StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) +{ + mxImpl->StoreCell(nFileId, rTabName, rPos); +} + +void XclExpLinkManager::StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) +{ + mxImpl->StoreCellRange(nFileId, rTabName, rRange); +} + +bool XclExpLinkManager::InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + return mxImpl->InsertAddIn( rnExtSheet, rnExtName, rName ); +} + +bool XclExpLinkManager::InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + return mxImpl->InsertEuroTool( rnExtSheet, rnExtName, rName ); +} + +bool XclExpLinkManager::InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) +{ + return mxImpl->InsertDde( rnExtSheet, rnExtName, rApplic, rTopic, rItem ); +} + +bool XclExpLinkManager::InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, const OUString& rName, + const ScExternalRefCache::TokenArrayRef& rArray ) +{ + return mxImpl->InsertExtName(rnExtSheet, rnExtName, rUrl, rName, rArray); +} + +void XclExpLinkManager::Save( XclExpStream& rStrm ) +{ + mxImpl->Save( rStrm ); +} + +void XclExpLinkManager::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xename.cxx b/sc/source/filter/excel/xename.cxx new file mode 100644 index 000000000..c8fd0ed37 --- /dev/null +++ b/sc/source/filter/excel/xename.cxx @@ -0,0 +1,870 @@ +/* -*- 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 <xename.hxx> + +#include <map> + +#include <document.hxx> +#include <rangenam.hxx> +#include <tokenarray.hxx> +#include <xehelper.hxx> +#include <xelink.hxx> +#include <excrecds.hxx> +#include <xlname.hxx> +#include <xeformula.hxx> +#include <xestring.hxx> +#include <xltools.hxx> + +#include <formula/grammar.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/tokens.hxx> + +using namespace ::oox; + +// *** Helper classes *** + +namespace { + +/** Represents an internal defined name, supports writing it to a NAME record. */ +class XclExpName : public XclExpRecord, protected XclExpRoot +{ +public: + /** Creates a standard defined name. */ + explicit XclExpName( const XclExpRoot& rRoot, const OUString& rName ); + /** Creates a built-in defined name. */ + explicit XclExpName( const XclExpRoot& rRoot, sal_Unicode cBuiltIn ); + + /** Sets a token array containing the definition of this name. */ + void SetTokenArray( const XclTokenArrayRef& xTokArr ); + /** Changes this defined name to be local on the specified Calc sheet. */ + void SetLocalTab( SCTAB nScTab ); + /** Hides or unhides the defined name. */ + void SetHidden( bool bHidden = true ); + /** Changes this name to be the call to a VB macro function or procedure. + @param bVBasic true = Visual Basic macro, false = Sheet macro. + @param bFunc true = Macro function; false = Macro procedure. */ + void SetMacroCall( bool bVBasic, bool bFunc ); + + /** Sets the name's symbol value + @param sValue the name's symbolic value */ + void SetSymbol( const OUString& rValue ); + + /** Returns the original name (title) of this defined name. */ + const OUString& GetOrigName() const { return maOrigName; } + /** Returns the Excel built-in name index of this defined name. + @return The built-in name index or EXC_BUILTIN_UNKNOWN for user-defined names. */ + sal_Unicode GetBuiltInName() const { return mcBuiltIn; } + + /** Returns the symbol value for this defined name. */ + const OUString& GetSymbol() const { return msSymbol; } + + /** Returns true, if this is a document-global defined name. */ + bool IsGlobal() const { return mnXclTab == EXC_NAME_GLOBAL; } + /** Returns the Calc sheet of a local defined name. */ + SCTAB GetScTab() const { return mnScTab; } + + /** Returns true, if this defined name is volatile. */ + bool IsVolatile() const; + /** Returns true, if this defined name describes a macro call. + @param bFunc true = Macro function; false = Macro procedure. */ + bool IsMacroCall( bool bVBasic, bool bFunc ) const; + + /** Writes the entire NAME record to the passed stream. */ + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + /** Writes the body of the NAME record to the passed stream. */ + virtual void WriteBody( XclExpStream& rStrm ) override; + /** Convert localized range separators */ + OUString GetWithDefaultRangeSeparator( const OUString& rSymbol ) const; + +private: + OUString maOrigName; /// The original user-defined name. + OUString msSymbol; /// The value of the symbol + XclExpStringRef mxName; /// The name as Excel string object. + XclTokenArrayRef mxTokArr; /// The definition of the defined name. + sal_Unicode mcBuiltIn; /// The built-in index for built-in names. + SCTAB mnScTab; /// The Calc sheet index for local names. + sal_uInt16 mnFlags; /// Additional flags for this defined name. + sal_uInt16 mnExtSheet; /// The 1-based index to a global EXTERNSHEET record. + sal_uInt16 mnXclTab; /// The 1-based Excel sheet index for local names. +}; + +} + +/** Implementation class of the name manager. */ +class XclExpNameManagerImpl : protected XclExpRoot +{ +public: + explicit XclExpNameManagerImpl( const XclExpRoot& rRoot ); + + /** Creates NAME records for built-in and user defined names. */ + void Initialize(); + + /** Inserts the Calc name with the passed index and returns the Excel NAME index. */ + sal_uInt16 InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab ); + + /** Inserts a new built-in defined name. */ + sal_uInt16 InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, SCTAB nScTab, const ScRangeList& aRangeList ); + sal_uInt16 InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, const ScRange& aRange ); + /** Inserts a new defined name. Sets another unused name, if rName already exists. */ + sal_uInt16 InsertUniqueName( const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab ); + /** Returns index of an existing name, or creates a name without definition. */ + sal_uInt16 InsertRawName( const OUString& rName ); + /** Searches or inserts a defined name describing a macro name. + @param bVBasic true = Visual Basic macro; false = Sheet macro. + @param bFunc true = Macro function; false = Macro procedure. */ + sal_uInt16 InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden ); + + /** Returns the NAME record at the specified position or 0 on error. */ + const XclExpName* GetName( sal_uInt16 nNameIdx ) const; + + /** Writes the entire list of NAME records. + @descr In BIFF7 and lower, writes the entire global link table, which + consists of an EXTERNCOUNT record, several EXTERNSHEET records, and + the list of NAME records. */ + void Save( XclExpStream& rStrm ); + + void SaveXml( XclExpXmlStream& rStrm ); + +private: + typedef XclExpRecordList< XclExpName > XclExpNameList; + typedef XclExpNameList::RecordRefType XclExpNameRef; + + typedef ::std::map< ::std::pair<SCTAB, OUString>, sal_uInt16> NamedExpMap; + +private: + /** + * @param nTab 0-based table index, or SCTAB_GLOBAL for global names. + * @param nScIdx calc's name index. + * + * @return excel's name index. + */ + sal_uInt16 FindNamedExp( SCTAB nTab, OUString sName ); + + /** Returns the index of an existing built-in NAME record with the passed definition, otherwise 0. */ + sal_uInt16 FindBuiltInNameIdx( const OUString& rName, + const OUString& sSymbol ) const; + /** Returns an unused name for the passed name. */ + OUString GetUnusedName( const OUString& rName ) const; + + /** Appends a new NAME record to the record list. + @return The 1-based NAME record index used elsewhere in the Excel file. */ + sal_uInt16 Append( XclExpName* pName ); + sal_uInt16 Append( XclExpNameRef const & rxName ) { return Append(rxName.get()); } + /** Creates a new NAME record for the passed user-defined name. + @return The 1-based NAME record index used elsewhere in the Excel file. */ + sal_uInt16 CreateName( SCTAB nTab, const ScRangeData& rRangeData ); + + /** Creates NAME records for all built-in names in the document. */ + void CreateBuiltInNames(); + /** Creates NAME records for all user-defined names in the document. */ + void CreateUserNames(); + +private: + /** + * Maps Calc's named range to Excel's NAME records. Global names use + * -1 as their table index, whereas sheet-local names have 0-based table + * index. + */ + NamedExpMap maNamedExpMap; + XclExpNameList maNameList; /// List of NAME records. + size_t mnFirstUserIdx; /// List index of first user-defined NAME record. +}; + +// *** Implementation *** + +XclExpName::XclExpName( const XclExpRoot& rRoot, const OUString& rName ) : + XclExpRecord( EXC_ID_NAME ), + XclExpRoot( rRoot ), + maOrigName( rName ), + mxName( XclExpStringHelper::CreateString( rRoot, rName, XclStrFlags::EightBitLength ) ), + mcBuiltIn( EXC_BUILTIN_UNKNOWN ), + mnScTab( SCTAB_GLOBAL ), + mnFlags( EXC_NAME_DEFAULT ), + mnExtSheet( EXC_NAME_GLOBAL ), + mnXclTab( EXC_NAME_GLOBAL ) +{ +} + +XclExpName::XclExpName( const XclExpRoot& rRoot, sal_Unicode cBuiltIn ) : + XclExpRecord( EXC_ID_NAME ), + XclExpRoot( rRoot ), + mcBuiltIn( cBuiltIn ), + mnScTab( SCTAB_GLOBAL ), + mnFlags( EXC_NAME_DEFAULT ), + mnExtSheet( EXC_NAME_GLOBAL ), + mnXclTab( EXC_NAME_GLOBAL ) +{ + // filter source range is hidden in Excel + if( cBuiltIn == EXC_BUILTIN_FILTERDATABASE ) + SetHidden(); + + // special case for BIFF5/7 filter source range - name appears as plain text without built-in flag + if( (GetBiff() <= EXC_BIFF5) && (cBuiltIn == EXC_BUILTIN_FILTERDATABASE) ) + { + OUString aName( XclTools::GetXclBuiltInDefName( EXC_BUILTIN_FILTERDATABASE ) ); + mxName = XclExpStringHelper::CreateString( rRoot, aName, XclStrFlags::EightBitLength ); + maOrigName = XclTools::GetXclBuiltInDefName( cBuiltIn ); + } + else + { + maOrigName = XclTools::GetBuiltInDefNameXml( cBuiltIn ) ; + mxName = XclExpStringHelper::CreateString( rRoot, cBuiltIn, XclStrFlags::EightBitLength ); + ::set_flag( mnFlags, EXC_NAME_BUILTIN ); + } +} + +void XclExpName::SetTokenArray( const XclTokenArrayRef& xTokArr ) +{ + mxTokArr = xTokArr; +} + +void XclExpName::SetLocalTab( SCTAB nScTab ) +{ + OSL_ENSURE( GetTabInfo().IsExportTab( nScTab ), "XclExpName::SetLocalTab - invalid sheet index" ); + if( !GetTabInfo().IsExportTab( nScTab ) ) + return; + + mnScTab = nScTab; + GetGlobalLinkManager().FindExtSheet( mnExtSheet, mnXclTab, nScTab ); + + // special handling for NAME record + switch( GetBiff() ) + { + case EXC_BIFF5: // EXTERNSHEET index is positive in NAME record + mnExtSheet = ~mnExtSheet + 1; + break; + case EXC_BIFF8: // EXTERNSHEET index not used, but must be created in link table + mnExtSheet = 0; + break; + default: DBG_ERROR_BIFF(); + } + + // Excel sheet index is 1-based + ++mnXclTab; +} + +void XclExpName::SetHidden( bool bHidden ) +{ + ::set_flag( mnFlags, EXC_NAME_HIDDEN, bHidden ); +} + +void XclExpName::SetMacroCall( bool bVBasic, bool bFunc ) +{ + ::set_flag( mnFlags, EXC_NAME_PROC ); + ::set_flag( mnFlags, EXC_NAME_VB, bVBasic ); + ::set_flag( mnFlags, EXC_NAME_FUNC, bFunc ); +} + +void XclExpName::SetSymbol( const OUString& rSymbol ) +{ + msSymbol = rSymbol; +} + +bool XclExpName::IsVolatile() const +{ + return mxTokArr && mxTokArr->IsVolatile(); +} + +bool XclExpName::IsMacroCall( bool bVBasic, bool bFunc ) const +{ + return + (::get_flag( mnFlags, EXC_NAME_VB ) == bVBasic) && + (::get_flag( mnFlags, EXC_NAME_FUNC ) == bFunc); +} + +void XclExpName::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( mxName && (mxName->Len() > 0), "XclExpName::Save - missing name" ); + OSL_ENSURE( !(IsGlobal() && ::get_flag( mnFlags, EXC_NAME_BUILTIN )), "XclExpName::Save - global built-in name" ); + SetRecSize( 11 + mxName->GetSize() + (mxTokArr ? mxTokArr->GetSize() : 2) ); + XclExpRecord::Save( rStrm ); +} + +OUString XclExpName::GetWithDefaultRangeSeparator( const OUString& rSymbol ) const +{ + sal_Int32 nPos = rSymbol.indexOf(';'); + if ( nPos > -1 ) + { + // convert with validation + ScRange aRange; + ScAddress::Details detailsXL( ::formula::FormulaGrammar::CONV_XL_A1 ); + ScRefFlags nRes = aRange.Parse( rSymbol.copy(0, nPos), GetDoc(), detailsXL ); + if ( nRes & ScRefFlags::VALID ) + { + nRes = aRange.Parse( rSymbol.copy(nPos+1), GetDoc(), detailsXL ); + if ( nRes & ScRefFlags::VALID ) + { + return rSymbol.replaceFirst(";", ","); + } + } + } + return rSymbol; +} + +void XclExpName::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream(); + rWorkbook->startElement( XML_definedName, + // OOXTODO: XML_comment, "", + // OOXTODO: XML_customMenu, "", + // OOXTODO: XML_description, "", + XML_function, ToPsz( ::get_flag( mnFlags, EXC_NAME_VB ) ), + // OOXTODO: XML_functionGroupId, "", + // OOXTODO: XML_help, "", + XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_NAME_HIDDEN ) ), + XML_localSheetId, mnScTab == SCTAB_GLOBAL ? nullptr : OString::number( mnScTab ).getStr(), + XML_name, maOrigName.toUtf8(), + // OOXTODO: XML_publishToServer, "", + // OOXTODO: XML_shortcutKey, "", + // OOXTODO: XML_statusBar, "", + XML_vbProcedure, ToPsz( ::get_flag( mnFlags, EXC_NAME_VB ) ) + // OOXTODO: XML_workbookParameter, "", + // OOXTODO: XML_xlm, "" + ); + rWorkbook->writeEscaped( GetWithDefaultRangeSeparator( msSymbol ) ); + rWorkbook->endElement( XML_definedName ); +} + +void XclExpName::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nFmlaSize = mxTokArr ? mxTokArr->GetSize() : 0; + + rStrm << mnFlags // flags + << sal_uInt8( 0 ); // keyboard shortcut + mxName->WriteLenField( rStrm ); // length of name + rStrm << nFmlaSize // size of token array + << mnExtSheet // BIFF5/7: EXTSHEET index, BIFF8: not used + << mnXclTab // 1-based sheet index for local names + << sal_uInt32( 0 ); // length of menu/descr/help/status text + mxName->WriteFlagField( rStrm ); // BIFF8 flag field (no-op in <=BIFF7) + mxName->WriteBuffer( rStrm ); // character array of the name + if( mxTokArr ) + mxTokArr->WriteArray( rStrm ); // token array without size +} + +/** Returns true (needed fixing) if FormulaToken was not absolute and 3D. + So, regardless of whether the fix was successful or not, true is still returned since a fix was required.*/ +static bool lcl_EnsureAbs3DToken( const SCTAB nTab, formula::FormulaToken* pTok, const bool bFix = true ) +{ + bool bFixRequired = false; + if ( !pTok || ( pTok->GetType() != formula::svSingleRef && pTok->GetType() != formula::svDoubleRef ) ) + return bFixRequired; + + ScSingleRefData* pRef1 = pTok->GetSingleRef(); + if ( !pRef1 ) + return bFixRequired; + + ScSingleRefData* pRef2 = nullptr; + if ( pTok->GetType() == formula::svDoubleRef ) + pRef2 = pTok->GetSingleRef2(); + + if ( pRef1->IsTabRel() || !pRef1->IsFlag3D() ) + { + bFixRequired = true; + if ( bFix ) + { + if ( pRef1->IsTabRel() && nTab != SCTAB_GLOBAL ) + pRef1->SetAbsTab( nTab + pRef1->Tab() ); //XLS requirement + if ( !pRef1->IsTabRel() ) + { + pRef1->SetFlag3D( true ); //XLSX requirement + if ( pRef2 && !pRef2->IsTabRel() ) + pRef2->SetFlag3D( pRef2->Tab() != pRef1->Tab() ); + } + } + } + + if ( pRef2 && pRef2->IsTabRel() && !pRef1->IsTabRel() ) + { + bFixRequired = true; + if ( bFix && nTab != SCTAB_GLOBAL ) + { + pRef2->SetAbsTab( nTab + pRef2->Tab() ); + pRef2->SetFlag3D( pRef2->Tab() != pRef1->Tab() ); + } + } + return bFixRequired; +} + +XclExpNameManagerImpl::XclExpNameManagerImpl( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnFirstUserIdx( 0 ) +{ +} + +void XclExpNameManagerImpl::Initialize() +{ + CreateBuiltInNames(); + mnFirstUserIdx = maNameList.GetSize(); + CreateUserNames(); +} + +sal_uInt16 XclExpNameManagerImpl::InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab ) +{ + sal_uInt16 nNameIdx = 0; + const ScRangeData* pData = nullptr; + ScRangeName* pRN = (nTab == SCTAB_GLOBAL) ? GetDoc().GetRangeName() : GetDoc().GetRangeName(nTab); + if (pRN) + pData = pRN->findByIndex(nScNameIdx); + + if (pData) + { + bool bEmulateGlobalRelativeTable = false; + const ScTokenArray* pCode = pData->GetCode(); + if ( pCode + && nTab == SCTAB_GLOBAL + && (pData->HasType( ScRangeData::Type::AbsPos ) || pData->HasType( ScRangeData::Type::AbsArea )) ) + { + bEmulateGlobalRelativeTable = lcl_EnsureAbs3DToken( nTab, pCode->FirstToken(), /*bFix=*/false ); + } + nNameIdx = FindNamedExp( bEmulateGlobalRelativeTable ? nCurrTab : nTab, pData->GetName() ); + if (!nNameIdx) + nNameIdx = CreateName(nTab, *pData); + } + + return nNameIdx; +} + +sal_uInt16 XclExpNameManagerImpl::InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, const ScRange& aRange ) +{ + XclExpNameRef xName = new XclExpName( GetRoot(), cBuiltIn ); + xName->SetTokenArray( xTokArr ); + xName->SetLocalTab( aRange.aStart.Tab() ); + OUString sSymbol(aRange.Format(GetDoc(), ScRefFlags::RANGE_ABS_3D, ScAddress::Details( ::formula::FormulaGrammar::CONV_XL_A1))); + xName->SetSymbol( sSymbol ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, SCTAB nScTab, const ScRangeList& rRangeList ) +{ + XclExpNameRef xName = new XclExpName( GetRoot(), cBuiltIn ); + xName->SetTokenArray( xTokArr ); + xName->SetLocalTab( nScTab ); + OUString sSymbol; + rRangeList.Format( sSymbol, ScRefFlags::RANGE_ABS_3D, GetDoc(), ::formula::FormulaGrammar::CONV_XL_A1 ); + xName->SetSymbol( sSymbol ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertUniqueName( + const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab ) +{ + OSL_ENSURE( !rName.isEmpty(), "XclExpNameManagerImpl::InsertUniqueName - empty name" ); + XclExpNameRef xName = new XclExpName( GetRoot(), GetUnusedName( rName ) ); + xName->SetTokenArray( xTokArr ); + xName->SetLocalTab( nScTab ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertRawName( const OUString& rName ) +{ + // empty name? may occur in broken external Calc tokens + if( rName.isEmpty() ) + return 0; + + // try to find an existing NAME record, regardless of its type + for( size_t nListIdx = mnFirstUserIdx, nListSize = maNameList.GetSize(); nListIdx < nListSize; ++nListIdx ) + { + XclExpNameRef xName = maNameList.GetRecord( nListIdx ); + if( xName->IsGlobal() && (xName->GetOrigName() == rName) ) + return static_cast< sal_uInt16 >( nListIdx + 1 ); + } + + // create a new NAME record + XclExpNameRef xName = new XclExpName( GetRoot(), rName ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden ) +{ + // empty name? may occur in broken external Calc tokens + if( rMacroName.isEmpty() ) + return 0; + + // try to find an existing NAME record + for( size_t nListIdx = mnFirstUserIdx, nListSize = maNameList.GetSize(); nListIdx < nListSize; ++nListIdx ) + { + XclExpNameRef xName = maNameList.GetRecord( nListIdx ); + if( xName->IsMacroCall( bVBasic, bFunc ) && (xName->GetOrigName() == rMacroName) ) + return static_cast< sal_uInt16 >( nListIdx + 1 ); + } + + // create a new NAME record + XclExpNameRef xName = new XclExpName( GetRoot(), rMacroName ); + xName->SetMacroCall( bVBasic, bFunc ); + xName->SetHidden( bHidden ); + + // for sheet macros, add a #NAME! error + if( !bVBasic ) + xName->SetTokenArray( GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NAME ) ); + + return Append( xName ); +} + +const XclExpName* XclExpNameManagerImpl::GetName( sal_uInt16 nNameIdx ) const +{ + OSL_ENSURE( maNameList.HasRecord( nNameIdx - 1 ), "XclExpNameManagerImpl::GetName - wrong record index" ); + return maNameList.GetRecord( nNameIdx - 1 ); +} + +void XclExpNameManagerImpl::Save( XclExpStream& rStrm ) +{ + maNameList.Save( rStrm ); +} + +void XclExpNameManagerImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maNameList.IsEmpty() ) + return; + sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream(); + rWorkbook->startElement(XML_definedNames); + maNameList.SaveXml( rStrm ); + rWorkbook->endElement( XML_definedNames ); +} + +// private -------------------------------------------------------------------- + +sal_uInt16 XclExpNameManagerImpl::FindNamedExp( SCTAB nTab, OUString sName ) +{ + NamedExpMap::key_type key(nTab, sName); + NamedExpMap::const_iterator itr = maNamedExpMap.find(key); + return (itr == maNamedExpMap.end()) ? 0 : itr->second; +} + +sal_uInt16 XclExpNameManagerImpl::FindBuiltInNameIdx( + const OUString& rName, const OUString& sSymbol ) const +{ + /* Get built-in index from the name. Special case: the database range + 'unnamed' will be mapped to Excel's built-in '_FilterDatabase' name. */ + sal_Unicode cBuiltIn = XclTools::GetBuiltInDefNameIndex( rName ); + + if( cBuiltIn < EXC_BUILTIN_UNKNOWN ) + { + // try to find the record in existing built-in NAME record list + for( size_t nPos = 0; nPos < mnFirstUserIdx; ++nPos ) + { + XclExpNameRef xName = maNameList.GetRecord( nPos ); + if( xName->GetBuiltInName() == cBuiltIn && xName->GetSymbol().replace(';', ',') == sSymbol.replace(';', ',') ) + { + // tdf#112567 restore the original built-in names with non-localized separators + // TODO: support more localizations, if needed + if ( xName->GetSymbol() != sSymbol ) + { + xName->SetSymbol(xName->GetSymbol().replace(';', ',')); + } + return static_cast< sal_uInt16 >( nPos + 1 ); + } + } + } + return 0; +} + +OUString XclExpNameManagerImpl::GetUnusedName( const OUString& rName ) const +{ + OUString aNewName( rName ); + sal_Int32 nAppIdx = 0; + bool bExist = true; + while( bExist ) + { + // search the list of user-defined names + bExist = false; + for( size_t nPos = mnFirstUserIdx, nSize = maNameList.GetSize(); !bExist && (nPos < nSize); ++nPos ) + { + XclExpNameRef xName = maNameList.GetRecord( nPos ); + bExist = xName->GetOrigName() == aNewName; + // name exists -> create a new name "<originalname>_<counter>" + if( bExist ) + aNewName = rName + "_" + OUString::number( ++nAppIdx ); + } + } + return aNewName; +} + +sal_uInt16 XclExpNameManagerImpl::Append( XclExpName* pName ) +{ + if( maNameList.GetSize() == 0xFFFF ) + return 0; + maNameList.AppendRecord( pName ); + return static_cast< sal_uInt16 >( maNameList.GetSize() ); // 1-based +} + +sal_uInt16 XclExpNameManagerImpl::CreateName( SCTAB nTab, const ScRangeData& rRangeData ) +{ + const OUString& rName = rRangeData.GetName(); + + /* #i38821# recursive names: first insert the (empty) name object, + otherwise a recursive call of this function from the formula compiler + with the same defined name will not find it and will create it again. */ + size_t nOldListSize = maNameList.GetSize(); + XclExpNameRef xName = new XclExpName( GetRoot(), rName ); + if (nTab != SCTAB_GLOBAL) + xName->SetLocalTab(nTab); + sal_uInt16 nNameIdx = Append( xName ); + // store the index of the NAME record in the lookup map + NamedExpMap::key_type key(nTab, rRangeData.GetName()); + maNamedExpMap[key] = nNameIdx; + + /* Create the definition formula. + This may cause recursive creation of other defined names. */ + if( const ScTokenArray* pScTokArr = const_cast< ScRangeData& >( rRangeData ).GetCode() ) + { + XclTokenArrayRef xTokArr; + OUString sSymbol; + // MSO requires named ranges to have absolute sheet references + if ( rRangeData.HasType( ScRangeData::Type::AbsPos ) || rRangeData.HasType( ScRangeData::Type::AbsArea ) ) + { + // Don't modify the actual document; use a temporary copy to create the export formulas. + ScTokenArray aTokenCopy( pScTokArr->CloneValue() ); + lcl_EnsureAbs3DToken(nTab, aTokenCopy.FirstToken()); + + xTokArr = GetFormulaCompiler().CreateFormula(EXC_FMLATYPE_NAME, aTokenCopy); + if ( GetOutput() != EXC_OUTPUT_BINARY ) + { + ScCompiler aComp(GetDoc(), rRangeData.GetPos(), aTokenCopy, + formula::FormulaGrammar::GRAM_OOXML); + aComp.CreateStringFromTokenArray( sSymbol ); + } + } + else + { + bool bOOXML = GetOutput() == EXC_OUTPUT_XML_2007; + xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, *pScTokArr, bOOXML ? + &rRangeData.GetPos() : nullptr ); + sSymbol = rRangeData.GetSymbol( (GetOutput() == EXC_OUTPUT_BINARY) ? + formula::FormulaGrammar::GRAM_ENGLISH_XL_A1 : formula::FormulaGrammar::GRAM_OOXML); + } + xName->SetTokenArray( xTokArr ); + xName->SetSymbol( sSymbol ); + + /* Try to replace by existing built-in name - complete token array is + needed for comparison, and due to the recursion problem above this + cannot be done earlier. If a built-in name is found, the created NAME + record for this name and all following records in the list must be + deleted, otherwise they may contain wrong name list indexes. */ + sal_uInt16 nBuiltInIdx = FindBuiltInNameIdx( rName, sSymbol ); + if( nBuiltInIdx != 0 ) + { + // delete the new NAME records + while( maNameList.GetSize() > nOldListSize ) + maNameList.RemoveRecord( maNameList.GetSize() - 1 ); + // use index of the found built-in NAME record + key = NamedExpMap::key_type(nTab, rRangeData.GetName()); + maNamedExpMap[key] = nNameIdx = nBuiltInIdx; + } + } + + return nNameIdx; +} + +void XclExpNameManagerImpl::CreateBuiltInNames() +{ + ScDocument& rDoc = GetDoc(); + XclExpTabInfo& rTabInfo = GetTabInfo(); + + /* #i2394# built-in defined names must be sorted by the name of the + containing sheet. Example: SheetA!Print_Range must be stored *before* + SheetB!Print_Range, regardless of the position of SheetA in the document! */ + for( SCTAB nScTabIdx = 0, nScTabCount = rTabInfo.GetScTabCount(); nScTabIdx < nScTabCount; ++nScTabIdx ) + { + // find real sheet index from the nScTabIdx counter + SCTAB nScTab = rTabInfo.GetRealScTab( nScTabIdx ); + // create NAME records for all built-in names of this sheet + if( rTabInfo.IsExportTab( nScTab ) ) + { + // *** 1) print ranges *** ---------------------------------------- + + if( rDoc.HasPrintRange() ) + { + ScRangeList aRangeList; + for( sal_uInt16 nIdx = 0, nCount = rDoc.GetPrintRangeCount( nScTab ); nIdx < nCount; ++nIdx ) + { + const ScRange* pPrintRange = rDoc.GetPrintRange( nScTab, nIdx ); + if (!pPrintRange) + continue; + ScRange aRange( *pPrintRange ); + // Calc document does not care about sheet index in print ranges + aRange.aStart.SetTab( nScTab ); + aRange.aEnd.SetTab( nScTab ); + aRange.PutInOrder(); + aRangeList.push_back( aRange ); + } + // create the NAME record (do not warn if ranges are shrunken) + GetAddressConverter().ValidateRangeList( aRangeList, false ); + if( !aRangeList.empty() ) + GetNameManager().InsertBuiltInName( EXC_BUILTIN_PRINTAREA, aRangeList ); + } + + // *** 2) print titles *** ---------------------------------------- + + ScRangeList aTitleList; + // repeated columns + if( std::optional<ScRange> oColRange = rDoc.GetRepeatColRange( nScTab ) ) + aTitleList.push_back( ScRange( + oColRange->aStart.Col(), 0, nScTab, + oColRange->aEnd.Col(), GetXclMaxPos().Row(), nScTab ) ); + // repeated rows + if( std::optional<ScRange> oRowRange = rDoc.GetRepeatRowRange( nScTab ) ) + aTitleList.push_back( ScRange( + 0, oRowRange->aStart.Row(), nScTab, + GetXclMaxPos().Col(), oRowRange->aEnd.Row(), nScTab ) ); + // create the NAME record + GetAddressConverter().ValidateRangeList( aTitleList, false ); + if( !aTitleList.empty() ) + GetNameManager().InsertBuiltInName( EXC_BUILTIN_PRINTTITLES, aTitleList ); + + // *** 3) filter ranges *** --------------------------------------- + + if( GetBiff() == EXC_BIFF8 ) + GetFilterManager().InitTabFilter( nScTab ); + } + } +} + +void XclExpNameManagerImpl::CreateUserNames() +{ + std::vector<ScRangeData*> vEmulateAsLocalRange; + const ScRangeName& rNamedRanges = GetNamedRanges(); + for (const auto& rEntry : rNamedRanges) + { + // skip definitions of shared formulas + if (!FindNamedExp(SCTAB_GLOBAL, rEntry.second->GetName())) + { + const ScTokenArray* pCode = rEntry.second->GetCode(); + if ( pCode + && (rEntry.second->HasType( ScRangeData::Type::AbsPos ) || rEntry.second->HasType( ScRangeData::Type::AbsArea )) + && lcl_EnsureAbs3DToken( SCTAB_GLOBAL, pCode->FirstToken(), /*bFix=*/false ) ) + { + vEmulateAsLocalRange.emplace_back(rEntry.second.get()); + } + else + CreateName(SCTAB_GLOBAL, *rEntry.second); + } + } + //look at sheets containing local range names + ScRangeName::TabNameCopyMap rLocalNames; + GetDoc().GetAllTabRangeNames(rLocalNames); + for (const auto& [rTab, pRangeName] : rLocalNames) + { + for (const auto& rEntry : *pRangeName) + { + // skip definitions of shared formulas + if (!FindNamedExp(rTab, rEntry.second->GetName())) + CreateName(rTab, *rEntry.second); + } + } + + // Emulate relative global variables by creating a copy in each local range. + // Creating AFTER true local range names so that conflicting global names will be ignored. + for ( SCTAB nTab = 0; nTab < GetDoc().GetTableCount(); ++nTab ) + { + for ( auto rangeDataItr : vEmulateAsLocalRange ) + { + if ( !FindNamedExp(nTab, rangeDataItr->GetName()) ) + CreateName(nTab, *rangeDataItr ); + } + } +} + +XclExpNameManager::XclExpNameManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxImpl( std::make_shared<XclExpNameManagerImpl>( rRoot ) ) +{ +} + +XclExpNameManager::~XclExpNameManager() +{ +} + +void XclExpNameManager::Initialize() +{ + mxImpl->Initialize(); +} + +sal_uInt16 XclExpNameManager::InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab ) +{ + return mxImpl->InsertName( nTab, nScNameIdx, nCurrTab ); +} + +sal_uInt16 XclExpNameManager::InsertBuiltInName( sal_Unicode cBuiltIn, const ScRange& rRange ) +{ + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, rRange ); + return mxImpl->InsertBuiltInName( cBuiltIn, xTokArr, rRange ); +} + +sal_uInt16 XclExpNameManager::InsertBuiltInName( sal_Unicode cBuiltIn, const ScRangeList& rRangeList ) +{ + sal_uInt16 nNameIdx = 0; + if( !rRangeList.empty() ) + { + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, rRangeList ); + nNameIdx = mxImpl->InsertBuiltInName( cBuiltIn, xTokArr, rRangeList.front().aStart.Tab(), rRangeList ); + } + return nNameIdx; +} + +sal_uInt16 XclExpNameManager::InsertUniqueName( + const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab ) +{ + return mxImpl->InsertUniqueName( rName, xTokArr, nScTab ); +} + +sal_uInt16 XclExpNameManager::InsertRawName( const OUString& rName ) +{ + return mxImpl->InsertRawName( rName ); +} + +sal_uInt16 XclExpNameManager::InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden ) +{ + return mxImpl->InsertMacroCall( rMacroName, bVBasic, bFunc, bHidden ); +} + +OUString XclExpNameManager::GetOrigName( sal_uInt16 nNameIdx ) const +{ + const XclExpName* pName = mxImpl->GetName( nNameIdx ); + return pName ? pName->GetOrigName() : OUString(); +} + +SCTAB XclExpNameManager::GetScTab( sal_uInt16 nNameIdx ) const +{ + const XclExpName* pName = mxImpl->GetName( nNameIdx ); + return pName ? pName->GetScTab() : SCTAB_GLOBAL; +} + +bool XclExpNameManager::IsVolatile( sal_uInt16 nNameIdx ) const +{ + const XclExpName* pName = mxImpl->GetName( nNameIdx ); + return pName && pName->IsVolatile(); +} + +void XclExpNameManager::Save( XclExpStream& rStrm ) +{ + mxImpl->Save( rStrm ); +} + +void XclExpNameManager::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xepage.cxx b/sc/source/filter/excel/xepage.cxx new file mode 100644 index 000000000..56ecd2d6b --- /dev/null +++ b/sc/source/filter/excel/xepage.cxx @@ -0,0 +1,512 @@ +/* -*- 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 <xepage.hxx> +#include <svl/itemset.hxx> +#include <scitems.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svx/pageitem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/tokens.hxx> +#include <sax/fastattribs.hxx> +#include <document.hxx> +#include <stlpool.hxx> +#include <attrib.hxx> +#include <xehelper.hxx> +#include <xeescher.hxx> +#include <xltools.hxx> + +#include <set> +#include <limits> + +using namespace ::oox; + +using ::std::set; +using ::std::numeric_limits; + +// Page settings records ====================================================== + +// Header/footer -------------------------------------------------------------- + +XclExpHeaderFooter::XclExpHeaderFooter( sal_uInt16 nRecId, const OUString& rHdrString ) : + XclExpRecord( nRecId ), + maHdrString( rHdrString ) +{ +} + +void XclExpHeaderFooter::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + sal_Int32 nElement; + switch(GetRecId()) { + case EXC_ID_HEADER_FIRST: nElement = XML_firstHeader; break; + case EXC_ID_FOOTER_FIRST: nElement = XML_firstFooter; break; + case EXC_ID_HEADER_EVEN: nElement = XML_evenHeader; break; + case EXC_ID_FOOTER_EVEN: nElement = XML_evenFooter; break; + case EXC_ID_HEADER: nElement = XML_oddHeader; break; + case EXC_ID_FOOTER: + default: nElement = XML_oddFooter; + } + rWorksheet->startElement(nElement); + rWorksheet->writeEscaped( maHdrString ); + rWorksheet->endElement( nElement ); +} + +void XclExpHeaderFooter::WriteBody( XclExpStream& rStrm ) +{ + if( !maHdrString.isEmpty() ) + { + XclExpString aExString; + if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 ) + aExString.AssignByte( maHdrString, rStrm.GetRoot().GetTextEncoding(), XclStrFlags::EightBitLength ); + else + aExString.Assign( maHdrString, XclStrFlags::NONE, 255 ); // 16-bit length, but max 255 chars + rStrm << aExString; + } +} + +// General page settings ------------------------------------------------------ + +XclExpSetup::XclExpSetup( const XclPageData& rPageData ) : + XclExpRecord( EXC_ID_SETUP, 34 ), + mrData( rPageData ) +{ +} + +void XclExpSetup::SaveXml( XclExpXmlStream& rStrm ) +{ + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + if( rStrm.getVersion() != oox::core::ISOIEC_29500_2008 || + mrData.mnStrictPaperSize != EXC_PAPERSIZE_USER ) + { + pAttrList->add( XML_paperSize, OString::number( mrData.mnPaperSize ).getStr() ); + } + else + { + pAttrList->add( XML_paperWidth, OString::number( mrData.mnPaperWidth ) + "mm" ); + pAttrList->add( XML_paperHeight, OString::number( mrData.mnPaperHeight ) + "mm" ); + // pAttrList->add( XML_paperUnits, "mm" ); + } + pAttrList->add( XML_scale, OString::number( mrData.mnScaling ).getStr() ); + pAttrList->add( XML_fitToWidth, OString::number( mrData.mnFitToWidth ).getStr() ); + pAttrList->add( XML_fitToHeight, OString::number( mrData.mnFitToHeight ).getStr() ); + pAttrList->add( XML_pageOrder, mrData.mbPrintInRows ? "overThenDown" : "downThenOver" ); + pAttrList->add( XML_orientation, mrData.mbPortrait ? "portrait" : "landscape" ); // OOXTODO: "default"? + // tdf#48767 if XML_usePrinterDefaults field is exist, then XML_orientation is always "portrait" in MS Excel + // To resolve that import issue, if XML_usePrinterDefaults has default value (false) then XML_usePrinterDefaults is not added. + if ( !mrData.mbValid ) + pAttrList->add( XML_usePrinterDefaults, ToPsz( !mrData.mbValid ) ); + pAttrList->add( XML_blackAndWhite, ToPsz( mrData.mbBlackWhite ) ); + pAttrList->add( XML_draft, ToPsz( mrData.mbDraftQuality ) ); + pAttrList->add( XML_cellComments, mrData.mbPrintNotes ? "atEnd" : "none" ); // OOXTODO: "asDisplayed"? + + if ( mrData.mbManualStart ) + { + pAttrList->add( XML_firstPageNumber, OString::number( mrData.mnStartPage ).getStr() ); + pAttrList->add( XML_useFirstPageNumber, ToPsz( mrData.mbManualStart ) ); + } + // OOXTODO: XML_errors, // == displayed|blank|dash|NA + pAttrList->add( XML_horizontalDpi, OString::number( mrData.mnHorPrintRes ).getStr() ); + pAttrList->add( XML_verticalDpi, OString::number( mrData.mnVerPrintRes ).getStr() ); + pAttrList->add( XML_copies, OString::number( mrData.mnCopies ).getStr() ); + // OOXTODO: devMode settings part RelationshipId: FSNS( XML_r, XML_id ), + + rStrm.GetCurrentStream()->singleElement( XML_pageSetup, pAttrList ); +} + +void XclExpSetup::WriteBody( XclExpStream& rStrm ) +{ + XclBiff eBiff = rStrm.GetRoot().GetBiff(); + + sal_uInt16 nFlags = 0; + ::set_flag( nFlags, EXC_SETUP_INROWS, mrData.mbPrintInRows ); + ::set_flag( nFlags, EXC_SETUP_PORTRAIT, mrData.mbPortrait ); + ::set_flag( nFlags, EXC_SETUP_INVALID, !mrData.mbValid ); + ::set_flag( nFlags, EXC_SETUP_BLACKWHITE, mrData.mbBlackWhite ); + if( eBiff >= EXC_BIFF5 ) + { + ::set_flag( nFlags, EXC_SETUP_DRAFT, mrData.mbDraftQuality ); + /* Set the Comments/Notes to "At end of sheet" if Print Notes is true. + We don't currently support "as displayed on sheet". Thus this value + will be re-interpreted to "At end of sheet". */ + const sal_uInt16 nNotes = EXC_SETUP_PRINTNOTES | EXC_SETUP_NOTES_END; + ::set_flag( nFlags, nNotes, mrData.mbPrintNotes ); + ::set_flag( nFlags, EXC_SETUP_STARTPAGE, mrData.mbManualStart ); + } + + rStrm << mrData.mnPaperSize << mrData.mnScaling << mrData.mnStartPage + << mrData.mnFitToWidth << mrData.mnFitToHeight << nFlags; + if( eBiff >= EXC_BIFF5 ) + { + rStrm << mrData.mnHorPrintRes << mrData.mnVerPrintRes + << mrData.mfHeaderMargin << mrData.mfFooterMargin << mrData.mnCopies; + } +} + +// Manual page breaks --------------------------------------------------------- + +XclExpPageBreaks::XclExpPageBreaks( sal_uInt16 nRecId, const ScfUInt16Vec& rPageBreaks, sal_uInt16 nMaxPos ) : + XclExpRecord( nRecId ), + mrPageBreaks( rPageBreaks ), + mnMaxPos( nMaxPos ) +{ +} + +void XclExpPageBreaks::Save( XclExpStream& rStrm ) +{ + if( !mrPageBreaks.empty() ) + { + SetRecSize( 2 + ((rStrm.GetRoot().GetBiff() <= EXC_BIFF5) ? 2 : 6) * mrPageBreaks.size() ); + XclExpRecord::Save( rStrm ); + } +} + +void XclExpPageBreaks::WriteBody( XclExpStream& rStrm ) +{ + bool bWriteRange = (rStrm.GetRoot().GetBiff() == EXC_BIFF8); + + rStrm << static_cast< sal_uInt16 >( mrPageBreaks.size() ); + for( const auto& rPageBreak : mrPageBreaks ) + { + rStrm << rPageBreak; + if( bWriteRange ) + rStrm << sal_uInt16( 0 ) << mnMaxPos; + } +} + +void XclExpPageBreaks::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mrPageBreaks.empty() ) + return; + + sal_Int32 nElement = GetRecId() == EXC_ID_HORPAGEBREAKS ? XML_rowBreaks : XML_colBreaks; + sax_fastparser::FSHelperPtr& pWorksheet = rStrm.GetCurrentStream(); + OString sNumPageBreaks = OString::number( mrPageBreaks.size() ); + pWorksheet->startElement( nElement, + XML_count, sNumPageBreaks, + XML_manualBreakCount, sNumPageBreaks ); + for( const auto& rPageBreak : mrPageBreaks ) + { + pWorksheet->singleElement( XML_brk, + XML_id, OString::number(rPageBreak), + XML_man, "true", + XML_max, OString::number(mnMaxPos), + XML_min, "0" + // OOXTODO: XML_pt, "" + ); + } + pWorksheet->endElement( nElement ); +} + +// Page settings ============================================================== + +XclExpPageSettings::XclExpPageSettings( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + + if( SfxStyleSheetBase* pStyleSheet = GetStyleSheetPool().Find( rDoc.GetPageStyle( nScTab ), SfxStyleFamily::Page ) ) + { + const SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + maData.mbValid = true; + + // *** page settings *** + + maData.mbPrintInRows = ! rItemSet.Get( ATTR_PAGE_TOPDOWN ).GetValue(); + maData.mbHorCenter = rItemSet.Get( ATTR_PAGE_HORCENTER ).GetValue(); + maData.mbVerCenter = rItemSet.Get( ATTR_PAGE_VERCENTER ).GetValue(); + maData.mbPrintHeadings = rItemSet.Get( ATTR_PAGE_HEADERS ).GetValue(); + maData.mbPrintGrid = rItemSet.Get( ATTR_PAGE_GRID ).GetValue(); + maData.mbPrintNotes = rItemSet.Get( ATTR_PAGE_NOTES ).GetValue(); + + maData.mnStartPage = rItemSet.Get( ATTR_PAGE_FIRSTPAGENO ).GetValue(); + maData.mbManualStart = maData.mnStartPage && (!nScTab || rDoc.NeedPageResetAfterTab( nScTab - 1 )); + + const SvxLRSpaceItem& rLRItem = rItemSet.Get( ATTR_LRSPACE ); + maData.mfLeftMargin = XclTools::GetInchFromTwips( rLRItem.GetLeft() ); + maData.mfRightMargin = XclTools::GetInchFromTwips( rLRItem.GetRight() ); + const SvxULSpaceItem& rULItem = rItemSet.Get( ATTR_ULSPACE ); + maData.mfTopMargin = XclTools::GetInchFromTwips( rULItem.GetUpper() ); + maData.mfBottomMargin = XclTools::GetInchFromTwips( rULItem.GetLower() ); + + const SvxPageItem& rPageItem = rItemSet.Get( ATTR_PAGE ); + const SvxSizeItem& rSizeItem = rItemSet.Get( ATTR_PAGE_SIZE ); + maData.SetScPaperSize( rSizeItem.GetSize(), !rPageItem.IsLandscape() ); + + const ScPageScaleToItem& rScaleToItem = rItemSet.Get( ATTR_PAGE_SCALETO ); + sal_uInt16 nPages = rItemSet.Get( ATTR_PAGE_SCALETOPAGES ).GetValue(); + sal_uInt16 nScale = rItemSet.Get( ATTR_PAGE_SCALE ).GetValue(); + + if( ScfTools::CheckItem( rItemSet, ATTR_PAGE_SCALETO, false ) && rScaleToItem.IsValid() ) + { + maData.mnFitToWidth = rScaleToItem.GetWidth(); + maData.mnFitToHeight = rScaleToItem.GetHeight(); + maData.mbFitToPages = true; + } + else if( ScfTools::CheckItem( rItemSet, ATTR_PAGE_SCALETOPAGES, false ) && nPages ) + { + maData.mnFitToWidth = 1; + maData.mnFitToHeight = nPages; + maData.mbFitToPages = true; + } + else if( nScale ) + { + maData.mnScaling = nScale; + maData.mbFitToPages = false; + } + + maData.mxBrushItem.reset( new SvxBrushItem( rItemSet.Get( ATTR_BACKGROUND ) ) ); + maData.mbUseEvenHF = false; + maData.mbUseFirstHF = false; + + // *** header and footer *** + + XclExpHFConverter aHFConv( GetRoot() ); + + // header + const SfxItemSet& rHdrItemSet = rItemSet.Get( ATTR_PAGE_HEADERSET ).GetItemSet(); + if( rHdrItemSet.Get( ATTR_PAGE_ON ).GetValue() ) + { + const ScPageHFItem& rHFItem = rItemSet.Get( ATTR_PAGE_HEADERRIGHT ); + aHFConv.GenerateString( rHFItem.GetLeftArea(), rHFItem.GetCenterArea(), rHFItem.GetRightArea() ); + maData.maHeader = aHFConv.GetHFString(); + if ( rHdrItemSet.HasItem(ATTR_PAGE_SHARED) && !rHdrItemSet.Get(ATTR_PAGE_SHARED).GetValue()) + { + const ScPageHFItem& rHFItemLeft = rItemSet.Get( ATTR_PAGE_HEADERLEFT ); + aHFConv.GenerateString( rHFItemLeft.GetLeftArea(), rHFItemLeft.GetCenterArea(), rHFItemLeft.GetRightArea() ); + maData.maHeaderEven = aHFConv.GetHFString(); + maData.mbUseEvenHF = true; + } + else + { + // If maData.mbUseEvenHF become true, then we will need a copy of maHeader in maHeaderEven. + maData.maHeaderEven = maData.maHeader; + } + if (rHdrItemSet.HasItem(ATTR_PAGE_SHARED_FIRST) && !rHdrItemSet.Get(ATTR_PAGE_SHARED_FIRST).GetValue()) + { + const ScPageHFItem& rHFItemFirst = rItemSet.Get( ATTR_PAGE_HEADERFIRST ); + aHFConv.GenerateString( rHFItemFirst.GetLeftArea(), rHFItemFirst.GetCenterArea(), rHFItemFirst.GetRightArea() ); + maData.maHeaderFirst = aHFConv.GetHFString(); + maData.mbUseFirstHF = true; + } + else + { + maData.maHeaderFirst = maData.maHeader; + } + // header height (Excel excludes header from top margin) + sal_Int32 nHdrHeight = rHdrItemSet.Get( ATTR_PAGE_DYNAMIC ).GetValue() ? + // dynamic height: calculate header height, add header <-> sheet area distance + (aHFConv.GetTotalHeight() + rHdrItemSet.Get( ATTR_ULSPACE ).GetLower()) : + // static height: ATTR_PAGE_SIZE already includes header <-> sheet area distance + static_cast< sal_Int32 >( rHdrItemSet.Get( ATTR_PAGE_SIZE ).GetSize().Height() ); + maData.mfHeaderMargin = maData.mfTopMargin; + maData.mfTopMargin += XclTools::GetInchFromTwips( nHdrHeight ); + } + + // footer + const SfxItemSet& rFtrItemSet = rItemSet.Get( ATTR_PAGE_FOOTERSET ).GetItemSet(); + if( rFtrItemSet.Get( ATTR_PAGE_ON ).GetValue() ) + { + const ScPageHFItem& rHFItem = rItemSet.Get( ATTR_PAGE_FOOTERRIGHT ); + aHFConv.GenerateString( rHFItem.GetLeftArea(), rHFItem.GetCenterArea(), rHFItem.GetRightArea() ); + maData.maFooter = aHFConv.GetHFString(); + if (rFtrItemSet.HasItem(ATTR_PAGE_SHARED) && !rFtrItemSet.Get(ATTR_PAGE_SHARED).GetValue()) + { + const ScPageHFItem& rHFItemLeft = rItemSet.Get( ATTR_PAGE_FOOTERLEFT ); + aHFConv.GenerateString( rHFItemLeft.GetLeftArea(), rHFItemLeft.GetCenterArea(), rHFItemLeft.GetRightArea() ); + maData.maFooterEven = aHFConv.GetHFString(); + maData.mbUseEvenHF = true; + } + else + { + maData.maFooterEven = maData.maFooter; + } + if (rFtrItemSet.HasItem(ATTR_PAGE_SHARED_FIRST) && !rFtrItemSet.Get(ATTR_PAGE_SHARED_FIRST).GetValue()) + { + const ScPageHFItem& rHFItemFirst = rItemSet.Get( ATTR_PAGE_FOOTERFIRST ); + aHFConv.GenerateString( rHFItemFirst.GetLeftArea(), rHFItemFirst.GetCenterArea(), rHFItemFirst.GetRightArea() ); + maData.maFooterFirst = aHFConv.GetHFString(); + maData.mbUseFirstHF = true; + } + else + { + maData.maFooterFirst = maData.maFooter; + } + // footer height (Excel excludes footer from bottom margin) + sal_Int32 nFtrHeight = rFtrItemSet.Get( ATTR_PAGE_DYNAMIC ).GetValue() ? + // dynamic height: calculate footer height, add sheet area <-> footer distance + (aHFConv.GetTotalHeight() + rFtrItemSet.Get( ATTR_ULSPACE ).GetUpper()) : + // static height: ATTR_PAGE_SIZE already includes sheet area <-> footer distance + static_cast< sal_Int32 >( rFtrItemSet.Get( ATTR_PAGE_SIZE ).GetSize().Height() ); + maData.mfFooterMargin = maData.mfBottomMargin; + maData.mfBottomMargin += XclTools::GetInchFromTwips( nFtrHeight ); + } + } + + // *** page breaks *** + + set<SCROW> aRowBreaks; + rDoc.GetAllRowBreaks(aRowBreaks, nScTab, false, true); + + SCROW const nMaxRow = numeric_limits<sal_uInt16>::max(); + for (const SCROW nRow : aRowBreaks) + { + if (nRow > nMaxRow) + break; + + maData.maHorPageBreaks.push_back(nRow); + } + + if (maData.maHorPageBreaks.size() > 1026) + { + // Excel allows only up to 1026 page breaks. Trim any excess page breaks. + ScfUInt16Vec::iterator itr = maData.maHorPageBreaks.begin(); + ::std::advance(itr, 1026); + maData.maHorPageBreaks.erase(itr, maData.maHorPageBreaks.end()); + } + + set<SCCOL> aColBreaks; + rDoc.GetAllColBreaks(aColBreaks, nScTab, false, true); + for (const auto& rColBreak : aColBreaks) + maData.maVerPageBreaks.push_back(rColBreak); +} + +namespace { + +class XclExpXmlStartHeaderFooterElementRecord : public XclExpXmlElementRecord +{ +public: + explicit XclExpXmlStartHeaderFooterElementRecord(sal_Int32 const nElement, bool const bDifferentOddEven = false, bool const bDifferentFirst = false) + : XclExpXmlElementRecord(nElement), mbDifferentOddEven(bDifferentOddEven), mbDifferentFirst(bDifferentFirst) {} + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +private: + bool mbDifferentOddEven; + bool mbDifferentFirst; +}; + +} + +void XclExpXmlStartHeaderFooterElementRecord::SaveXml(XclExpXmlStream& rStrm) +{ + // OOXTODO: we currently only emit oddHeader/oddFooter elements, and + // do not support the first/even/odd page distinction. + sax_fastparser::FSHelperPtr& rStream = rStrm.GetCurrentStream(); + rStream->startElement( mnElement, + // OOXTODO: XML_alignWithMargins, + XML_differentFirst, mbDifferentFirst ? "true" : "false", + XML_differentOddEven, mbDifferentOddEven ? "true" : "false" + // OOXTODO: XML_scaleWithDoc + ); +} + +void XclExpPageSettings::Save( XclExpStream& rStrm ) +{ + XclExpBoolRecord( EXC_ID_PRINTHEADERS, maData.mbPrintHeadings ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_PRINTGRIDLINES, maData.mbPrintGrid ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_GRIDSET, true ).Save( rStrm ); + XclExpPageBreaks( EXC_ID_HORPAGEBREAKS, maData.maHorPageBreaks, static_cast< sal_uInt16 >( GetXclMaxPos().Col() ) ).Save( rStrm ); + XclExpPageBreaks( EXC_ID_VERPAGEBREAKS, maData.maVerPageBreaks, static_cast< sal_uInt16 >( GetXclMaxPos().Row() ) ).Save( rStrm ); + XclExpHeaderFooter( EXC_ID_HEADER, maData.maHeader ).Save( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER, maData.maFooter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_HCENTER, maData.mbHorCenter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_VCENTER, maData.mbVerCenter ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_LEFTMARGIN, maData.mfLeftMargin ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_RIGHTMARGIN, maData.mfRightMargin ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_TOPMARGIN, maData.mfTopMargin ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_BOTTOMMARGIN, maData.mfBottomMargin ).Save( rStrm ); + XclExpSetup( maData ).Save( rStrm ); + + if( (GetBiff() == EXC_BIFF8) && maData.mxBrushItem ) + if( const Graphic* pGraphic = maData.mxBrushItem->GetGraphic() ) + XclExpImgData( *pGraphic, EXC_ID8_IMGDATA ).Save( rStrm ); +} + +void XclExpPageSettings::SaveXml( XclExpXmlStream& rStrm ) +{ + XclExpXmlStartSingleElementRecord( XML_printOptions ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_PRINTHEADERS, maData.mbPrintHeadings, XML_headings ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_PRINTGRIDLINES, maData.mbPrintGrid, XML_gridLines ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_GRIDSET, true, XML_gridLinesSet ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_HCENTER, maData.mbHorCenter, XML_horizontalCentered ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_VCENTER, maData.mbVerCenter, XML_verticalCentered ).SaveXml( rStrm ); + XclExpXmlEndSingleElementRecord().SaveXml( rStrm ); // XML_printOptions + + XclExpXmlStartSingleElementRecord( XML_pageMargins ).SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_LEFTMARGIN, maData.mfLeftMargin ).SetAttribute( XML_left )->SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_RIGHTMARGIN, maData.mfRightMargin ).SetAttribute( XML_right )->SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_TOPMARGIN, maData.mfTopMargin ).SetAttribute( XML_top )->SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_BOTTOMMARGIN, maData.mfBottomMargin ).SetAttribute( XML_bottom )->SaveXml( rStrm ); + XclExpDoubleRecord( 0, maData.mfHeaderMargin).SetAttribute( XML_header )->SaveXml( rStrm ); + XclExpDoubleRecord( 0, maData.mfFooterMargin).SetAttribute( XML_footer )->SaveXml( rStrm ); + XclExpXmlEndSingleElementRecord().SaveXml( rStrm ); // XML_pageMargins + + XclExpSetup( maData ).SaveXml( rStrm ); + + XclExpXmlStartHeaderFooterElementRecord(XML_headerFooter, maData.mbUseEvenHF, maData.mbUseFirstHF).SaveXml(rStrm); + XclExpHeaderFooter( EXC_ID_HEADER, maData.maHeader ).SaveXml( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER, maData.maFooter ).SaveXml( rStrm ); + if (maData.mbUseEvenHF) + { + XclExpHeaderFooter( EXC_ID_HEADER_EVEN, maData.maHeaderEven ).SaveXml( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER_EVEN, maData.maFooterEven ).SaveXml( rStrm ); + } + if (maData.mbUseFirstHF) + { + XclExpHeaderFooter( EXC_ID_HEADER_FIRST, maData.maHeaderFirst ).SaveXml( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER_FIRST, maData.maFooterFirst ).SaveXml( rStrm ); + } + XclExpXmlEndElementRecord( XML_headerFooter ).SaveXml( rStrm ); + + XclExpPageBreaks( EXC_ID_HORPAGEBREAKS, maData.maHorPageBreaks, + static_cast< sal_uInt16 >( GetXclMaxPos().Col() ) ).SaveXml( rStrm ); + XclExpPageBreaks( EXC_ID_VERPAGEBREAKS, maData.maVerPageBreaks, + static_cast< sal_uInt16 >( GetXclMaxPos().Row() ) ).SaveXml( rStrm ); +} + +XclExpImgData* XclExpPageSettings::getGraphicExport() +{ + if( const Graphic* pGraphic = maData.mxBrushItem->GetGraphic() ) + return new XclExpImgData( *pGraphic, EXC_ID8_IMGDATA ); + + return nullptr; +} + +XclExpChartPageSettings::XclExpChartPageSettings( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpChartPageSettings::Save( XclExpStream& rStrm ) +{ + XclExpHeaderFooter( EXC_ID_HEADER, maData.maHeader ).Save( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER, maData.maFooter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_HCENTER, maData.mbHorCenter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_VCENTER, maData.mbVerCenter ).Save( rStrm ); + XclExpSetup( maData ).Save( rStrm ); + XclExpUInt16Record( EXC_ID_PRINTSIZE, EXC_PRINTSIZE_FULL ).Save( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xepivot.cxx b/sc/source/filter/excel/xepivot.cxx new file mode 100644 index 000000000..34564e30e --- /dev/null +++ b/sc/source/filter/excel/xepivot.cxx @@ -0,0 +1,1702 @@ +/* -*- 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 <xepivot.hxx> +#include <xehelper.hxx> +#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldReference.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp> +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> + +#include <algorithm> +#include <math.h> +#include <string_view> + +#include <osl/diagnose.h> +#include <sot/storage.hxx> +#include <document.hxx> +#include <dpcache.hxx> +#include <dpgroup.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <dpdimsave.hxx> +#include <dpshttab.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <xestring.hxx> +#include <xelink.hxx> +#include <dputil.hxx> +#include <generalfunction.hxx> +#include <svl/numformat.hxx> + +using namespace ::oox; + +using ::com::sun::star::sheet::DataPilotFieldOrientation; +using ::com::sun::star::sheet::DataPilotFieldOrientation_ROW; +using ::com::sun::star::sheet::DataPilotFieldOrientation_COLUMN; +using ::com::sun::star::sheet::DataPilotFieldOrientation_PAGE; +using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA; +using ::com::sun::star::sheet::DataPilotFieldSortInfo; +using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo; +using ::com::sun::star::sheet::DataPilotFieldLayoutInfo; +using ::com::sun::star::sheet::DataPilotFieldReference; + +// Pivot cache + +namespace { + +// constants to track occurrence of specific data types +const sal_uInt16 EXC_PCITEM_DATA_STRING = 0x0001; /// String, empty, boolean, error. +const sal_uInt16 EXC_PCITEM_DATA_DOUBLE = 0x0002; /// Double with fraction. +const sal_uInt16 EXC_PCITEM_DATA_INTEGER = 0x0004; /// Integer, double without fraction. +const sal_uInt16 EXC_PCITEM_DATA_DATE = 0x0008; /// Date, time, date/time. + +/** Maps a bitfield consisting of EXC_PCITEM_DATA_* flags above to SXFIELD data type bitfield. */ +const sal_uInt16 spnPCItemFlags[] = +{ // STR DBL INT DAT + EXC_SXFIELD_DATA_NONE, + EXC_SXFIELD_DATA_STR, // x + EXC_SXFIELD_DATA_INT, // x + EXC_SXFIELD_DATA_STR_INT, // x x + EXC_SXFIELD_DATA_DBL, // x + EXC_SXFIELD_DATA_STR_DBL, // x x + EXC_SXFIELD_DATA_INT, // x x + EXC_SXFIELD_DATA_STR_INT, // x x x + EXC_SXFIELD_DATA_DATE, // x + EXC_SXFIELD_DATA_DATE_STR, // x x + EXC_SXFIELD_DATA_DATE_NUM, // x x + EXC_SXFIELD_DATA_DATE_STR, // x x x + EXC_SXFIELD_DATA_DATE_NUM, // x x + EXC_SXFIELD_DATA_DATE_STR, // x x x + EXC_SXFIELD_DATA_DATE_NUM, // x x x + EXC_SXFIELD_DATA_DATE_STR // x x x x +}; + +} // namespace + +XclExpPCItem::XclExpPCItem( const OUString& rText ) : + XclExpRecord( (!rText.isEmpty()) ? EXC_ID_SXSTRING : EXC_ID_SXEMPTY, 0 ), + mnTypeFlag( EXC_PCITEM_DATA_STRING ) +{ + if( !rText.isEmpty() ) + SetText( rText ); + else + SetEmpty(); +} + +XclExpPCItem::XclExpPCItem( double fValue, const OUString& rText ) : + XclExpRecord( EXC_ID_SXDOUBLE, 8 ) +{ + SetDouble( fValue, rText ); + mnTypeFlag = (fValue - floor( fValue ) == 0.0) ? + EXC_PCITEM_DATA_INTEGER : EXC_PCITEM_DATA_DOUBLE; +} + +XclExpPCItem::XclExpPCItem( const DateTime& rDateTime, const OUString& rText ) : + XclExpRecord( EXC_ID_SXDATETIME, 8 ) +{ + SetDateTime( rDateTime, rText ); + mnTypeFlag = EXC_PCITEM_DATA_DATE; +} + +XclExpPCItem::XclExpPCItem( sal_Int16 nValue ) : + XclExpRecord( EXC_ID_SXINTEGER, 2 ), + mnTypeFlag( EXC_PCITEM_DATA_INTEGER ) +{ + SetInteger( nValue ); +} + +XclExpPCItem::XclExpPCItem( bool bValue, const OUString& rText ) : + XclExpRecord( EXC_ID_SXBOOLEAN, 2 ), + mnTypeFlag( EXC_PCITEM_DATA_STRING ) +{ + SetBool( bValue, rText ); +} + +bool XclExpPCItem::EqualsText( std::u16string_view rText ) const +{ + return rText.empty() ? IsEmpty() : (GetText() && (*GetText() == rText)); +} + +bool XclExpPCItem::EqualsDouble( double fValue ) const +{ + return GetDouble() && (*GetDouble() == fValue); +} + +bool XclExpPCItem::EqualsDateTime( const DateTime& rDateTime ) const +{ + return GetDateTime() && (*GetDateTime() == rDateTime); +} + +bool XclExpPCItem::EqualsBool( bool bValue ) const +{ + return GetBool() && (*GetBool() == bValue); +} + +void XclExpPCItem::WriteBody( XclExpStream& rStrm ) +{ + if( const OUString* pText = GetText() ) + { + rStrm << XclExpString( *pText ); + } + else if( const double* pfValue = GetDouble() ) + { + rStrm << *pfValue; + } + else if( const sal_Int16* pnValue = GetInteger() ) + { + rStrm << *pnValue; + } + else if( const DateTime* pDateTime = GetDateTime() ) + { + sal_uInt16 nYear = static_cast< sal_uInt16 >( pDateTime->GetYear() ); + sal_uInt16 nMonth = pDateTime->GetMonth(); + sal_uInt8 nDay = static_cast< sal_uInt8 >( pDateTime->GetDay() ); + sal_uInt8 nHour = static_cast< sal_uInt8 >( pDateTime->GetHour() ); + sal_uInt8 nMin = static_cast< sal_uInt8 >( pDateTime->GetMin() ); + sal_uInt8 nSec = static_cast< sal_uInt8 >( pDateTime->GetSec() ); + if( nYear < 1900 ) { nYear = 1900; nMonth = 1; nDay = 0; } + rStrm << nYear << nMonth << nDay << nHour << nMin << nSec; + } + else if( const bool* pbValue = GetBool() ) + { + rStrm << static_cast< sal_uInt16 >( *pbValue ? 1 : 0 ); + } + else + { + // nothing to do for SXEMPTY + OSL_ENSURE( IsEmpty(), "XclExpPCItem::WriteBody - no data found" ); + } +} + +XclExpPCField::XclExpPCField( + const XclExpRoot& rRoot, sal_uInt16 nFieldIdx, + const ScDPObject& rDPObj, const ScRange& rRange ) : + XclExpRecord( EXC_ID_SXFIELD ), + XclPCField( EXC_PCFIELD_STANDARD, nFieldIdx ), + XclExpRoot( rRoot ), + mnTypeFlags( 0 ) +{ + // general settings for the standard field, insert all items from source range + InitStandardField( rRange ); + + // add special settings for inplace numeric grouping + if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() ) + { + if( const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData() ) + { + if( const ScDPSaveNumGroupDimension* pNumGroupDim = pSaveDimData->GetNumGroupDim( GetFieldName() ) ) + { + const ScDPNumGroupInfo& rNumInfo = pNumGroupDim->GetInfo(); + const ScDPNumGroupInfo& rDateInfo = pNumGroupDim->GetDateInfo(); + OSL_ENSURE( !rNumInfo.mbEnable || !rDateInfo.mbEnable, + "XclExpPCField::XclExpPCField - numeric and date grouping enabled" ); + + if( rNumInfo.mbEnable ) + InitNumGroupField( rDPObj, rNumInfo ); + else if( rDateInfo.mbEnable ) + InitDateGroupField( rDPObj, rDateInfo, pNumGroupDim->GetDatePart() ); + } + } + } + + // final settings (flags, item numbers) + Finalize(); +} + +XclExpPCField::XclExpPCField( + const XclExpRoot& rRoot, sal_uInt16 nFieldIdx, + const ScDPObject& rDPObj, const ScDPSaveGroupDimension& rGroupDim, const XclExpPCField& rBaseField ) : + XclExpRecord( EXC_ID_SXFIELD ), + XclPCField( EXC_PCFIELD_STDGROUP, nFieldIdx ), + XclExpRoot( rRoot ), + mnTypeFlags( 0 ) +{ + // add base field info (always using first base field, not predecessor of this field) *** + OSL_ENSURE( rBaseField.GetFieldName() == rGroupDim.GetSourceDimName(), + "XclExpPCField::FillFromGroup - wrong base cache field" ); + maFieldInfo.maName = rGroupDim.GetGroupDimName(); + maFieldInfo.mnGroupBase = rBaseField.GetFieldIndex(); + + // add standard group info or date group info + const ScDPNumGroupInfo& rDateInfo = rGroupDim.GetDateInfo(); + if( rDateInfo.mbEnable && (rGroupDim.GetDatePart() != 0) ) + InitDateGroupField( rDPObj, rDateInfo, rGroupDim.GetDatePart() ); + else + InitStdGroupField( rBaseField, rGroupDim ); + + // final settings (flags, item numbers) + Finalize(); +} + +XclExpPCField::~XclExpPCField() +{ +} + +void XclExpPCField::SetGroupChildField( const XclExpPCField& rChildField ) +{ + OSL_ENSURE( !::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ), + "XclExpPCField::SetGroupChildIndex - field already has a grouping child field" ); + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ); + maFieldInfo.mnGroupChild = rChildField.GetFieldIndex(); +} + +sal_uInt16 XclExpPCField::GetItemCount() const +{ + return static_cast< sal_uInt16 >( GetVisItemList().GetSize() ); +} + +const XclExpPCItem* XclExpPCField::GetItem( sal_uInt16 nItemIdx ) const +{ + return GetVisItemList().GetRecord( nItemIdx ); +} + +sal_uInt16 XclExpPCField::GetItemIndex( std::u16string_view rItemName ) const +{ + const XclExpPCItemList& rItemList = GetVisItemList(); + for( size_t nPos = 0, nSize = rItemList.GetSize(); nPos < nSize; ++nPos ) + if( rItemList.GetRecord( nPos )->ConvertToText() == rItemName ) + return static_cast< sal_uInt16 >( nPos ); + return EXC_PC_NOITEM; +} + +std::size_t XclExpPCField::GetIndexSize() const +{ + return Has16BitIndexes() ? 2 : 1; +} + +void XclExpPCField::WriteIndex( XclExpStream& rStrm, sal_uInt32 nSrcRow ) const +{ + // only standard fields write item indexes + if( nSrcRow < maIndexVec.size() ) + { + sal_uInt16 nIndex = maIndexVec[ nSrcRow ]; + if( Has16BitIndexes() ) + rStrm << nIndex; + else + rStrm << static_cast< sal_uInt8 >( nIndex ); + } +} + +void XclExpPCField::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( IsSupportedField(), "XclExpPCField::Save - unknown field type" ); + // SXFIELD + XclExpRecord::Save( rStrm ); + // SXFDBTYPE + XclExpUInt16Record( EXC_ID_SXFDBTYPE, EXC_SXFDBTYPE_DEFAULT ).Save( rStrm ); + // list of grouping items + maGroupItemList.Save( rStrm ); + // SXGROUPINFO + WriteSxgroupinfo( rStrm ); + // SXNUMGROUP and additional grouping items (grouping limit settings) + WriteSxnumgroup( rStrm ); + // list of original items + maOrigItemList.Save( rStrm ); +} + +// private -------------------------------------------------------------------- + +const XclExpPCField::XclExpPCItemList& XclExpPCField::GetVisItemList() const +{ + OSL_ENSURE( IsStandardField() == maGroupItemList.IsEmpty(), + "XclExpPCField::GetVisItemList - unexpected additional items in standard field" ); + return IsStandardField() ? maOrigItemList : maGroupItemList; +} + +void XclExpPCField::InitStandardField( const ScRange& rRange ) +{ + OSL_ENSURE( IsStandardField(), "XclExpPCField::InitStandardField - only for standard fields" ); + OSL_ENSURE( rRange.aStart.Col() == rRange.aEnd.Col(), "XclExpPCField::InitStandardField - cell range with multiple columns" ); + + ScDocument& rDoc = GetDoc(); + SvNumberFormatter& rFormatter = GetFormatter(); + + // field name is in top cell of the range + ScAddress aPos( rRange.aStart ); + maFieldInfo.maName = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab()); + // #i76047# maximum field name length in pivot cache is 255 + if (maFieldInfo.maName.getLength() > EXC_PC_MAXSTRLEN) + maFieldInfo.maName = maFieldInfo.maName.copy(0, EXC_PC_MAXSTRLEN); + + // loop over all cells, create pivot cache items + for( aPos.IncRow(); (aPos.Row() <= rRange.aEnd.Row()) && (maOrigItemList.GetSize() < EXC_PC_MAXITEMCOUNT); aPos.IncRow() ) + { + OUString aText = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab()); + if( rDoc.HasValueData( aPos.Col(), aPos.Row(), aPos.Tab() ) ) + { + double fValue = rDoc.GetValue( aPos ); + SvNumFormatType nFmtType = rFormatter.GetType( rDoc.GetNumberFormat( rDoc.GetNonThreadedContext(), aPos ) ); + if( nFmtType == SvNumFormatType::LOGICAL ) + InsertOrigBoolItem( fValue != 0, aText ); + else if( nFmtType & SvNumFormatType::DATETIME ) + InsertOrigDateTimeItem( GetDateTimeFromDouble( ::std::max( fValue, 0.0 ) ), aText ); + else + InsertOrigDoubleItem( fValue, aText ); + } + else + { + InsertOrigTextItem( aText ); + } + } +} + +void XclExpPCField::InitStdGroupField( const XclExpPCField& rBaseField, const ScDPSaveGroupDimension& rGroupDim ) +{ + OSL_ENSURE( IsGroupField(), "XclExpPCField::InitStdGroupField - only for standard grouping fields" ); + + maFieldInfo.mnBaseItems = rBaseField.GetItemCount(); + maGroupOrder.resize( maFieldInfo.mnBaseItems, EXC_PC_NOITEM ); + + // loop over all groups of this field + for( tools::Long nGroupIdx = 0, nGroupCount = rGroupDim.GetGroupCount(); nGroupIdx < nGroupCount; ++nGroupIdx ) + { + const ScDPSaveGroupItem& rGroupItem = rGroupDim.GetGroupByIndex( nGroupIdx ); + // the index of the new item containing the grouping name + sal_uInt16 nGroupItemIdx = EXC_PC_NOITEM; + // loop over all elements of one group + for( size_t nElemIdx = 0, nElemCount = rGroupItem.GetElementCount(); nElemIdx < nElemCount; ++nElemIdx ) + { + if (const OUString* pElemName = rGroupItem.GetElementByIndex(nElemIdx)) + { + // try to find the item that is part of the group in the base field + sal_uInt16 nBaseItemIdx = rBaseField.GetItemIndex( *pElemName ); + if( nBaseItemIdx < maFieldInfo.mnBaseItems ) + { + // add group name item only if there are any valid base items + if( nGroupItemIdx == EXC_PC_NOITEM ) + nGroupItemIdx = InsertGroupItem( new XclExpPCItem( rGroupItem.GetGroupName() ) ); + maGroupOrder[ nBaseItemIdx ] = nGroupItemIdx; + } + } + } + } + + // add items and base item indexes of all ungrouped elements + for( sal_uInt16 nBaseItemIdx = 0; nBaseItemIdx < maFieldInfo.mnBaseItems; ++nBaseItemIdx ) + // items that are not part of a group still have the EXC_PC_NOITEM entry + if( maGroupOrder[ nBaseItemIdx ] == EXC_PC_NOITEM ) + // try to find the base item + if( const XclExpPCItem* pBaseItem = rBaseField.GetItem( nBaseItemIdx ) ) + // create a clone of the base item, insert its index into item order list + maGroupOrder[ nBaseItemIdx ] = InsertGroupItem( new XclExpPCItem( *pBaseItem ) ); +} + +void XclExpPCField::InitNumGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo ) +{ + OSL_ENSURE( IsStandardField(), "XclExpPCField::InitNumGroupField - only for standard fields" ); + OSL_ENSURE( rNumInfo.mbEnable, "XclExpPCField::InitNumGroupField - numeric grouping not enabled" ); + + // new field type, date type, limit settings (min/max/step/auto) + if( rNumInfo.mbDateValues ) + { + // special case: group by days with step count + meFieldType = EXC_PCFIELD_DATEGROUP; + maNumGroupInfo.SetScDateType( css::sheet::DataPilotFieldGroupBy::DAYS ); + SetDateGroupLimit( rNumInfo, true ); + } + else + { + meFieldType = EXC_PCFIELD_NUMGROUP; + maNumGroupInfo.SetNumType(); + SetNumGroupLimit( rNumInfo ); + } + + // generate visible items + InsertNumDateGroupItems( rDPObj, rNumInfo ); +} + +void XclExpPCField::InitDateGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nDatePart ) +{ + OSL_ENSURE( IsStandardField() || IsStdGroupField(), "XclExpPCField::InitDateGroupField - only for standard fields" ); + OSL_ENSURE( rDateInfo.mbEnable, "XclExpPCField::InitDateGroupField - date grouping not enabled" ); + + // new field type + meFieldType = IsStandardField() ? EXC_PCFIELD_DATEGROUP : EXC_PCFIELD_DATECHILD; + + // date type, limit settings (min/max/step/auto) + maNumGroupInfo.SetScDateType( nDatePart ); + SetDateGroupLimit( rDateInfo, false ); + + // generate visible items + InsertNumDateGroupItems( rDPObj, rDateInfo, nDatePart ); +} + +void XclExpPCField::InsertItemArrayIndex( size_t nListPos ) +{ + OSL_ENSURE( IsStandardField(), "XclExpPCField::InsertItemArrayIndex - only for standard fields" ); + maIndexVec.push_back( static_cast< sal_uInt16 >( nListPos ) ); +} + +void XclExpPCField::InsertOrigItem( XclExpPCItem* pNewItem ) +{ + size_t nItemIdx = maOrigItemList.GetSize(); + maOrigItemList.AppendNewRecord( pNewItem ); + InsertItemArrayIndex( nItemIdx ); + mnTypeFlags |= pNewItem->GetTypeFlag(); +} + +void XclExpPCField::InsertOrigTextItem( const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + // #i76047# maximum item text length in pivot cache is 255 + OUString aShortText = rText.copy( 0, ::std::min(rText.getLength(), EXC_PC_MAXSTRLEN ) ); + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsText( aShortText )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( aShortText ) ); +} + +void XclExpPCField::InsertOrigDoubleItem( double fValue, const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDouble( fValue )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( fValue, rText ) ); +} + +void XclExpPCField::InsertOrigDateTimeItem( const DateTime& rDateTime, const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDateTime( rDateTime )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( rDateTime, rText ) ); +} + +void XclExpPCField::InsertOrigBoolItem( bool bValue, const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsBool( bValue )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( bValue, rText ) ); +} + +sal_uInt16 XclExpPCField::InsertGroupItem( XclExpPCItem* pNewItem ) +{ + maGroupItemList.AppendNewRecord( pNewItem ); + return static_cast< sal_uInt16 >( maGroupItemList.GetSize() - 1 ); +} + +void XclExpPCField::InsertNumDateGroupItems( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nDatePart ) +{ + OSL_ENSURE( rDPObj.GetSheetDesc(), "XclExpPCField::InsertNumDateGroupItems - cannot generate element list" ); + const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc(); + if(!pSrcDesc) + return; + + // get the string collection with original source elements + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + const ScDPDimensionSaveData* pDimData = nullptr; + if (pSaveData) + pDimData = pSaveData->GetExistingDimensionData(); + + const ScDPCache* pCache = pSrcDesc->CreateCache(pDimData); + if (!pCache) + return; + + ScSheetDPData aDPData(&GetDoc(), *pSrcDesc, *pCache); + tools::Long nDim = GetFieldIndex(); + // get the string collection with generated grouping elements + ScDPNumGroupDimension aTmpDim( rNumInfo ); + if( nDatePart != 0 ) + aTmpDim.SetDateDimension(); + const std::vector<SCROW>& aMemberIds = aTmpDim.GetNumEntries( + static_cast<SCCOL>(nDim), pCache); + for (SCROW nMemberId : aMemberIds) + { + const ScDPItemData* pData = aDPData.GetMemberById(nDim, nMemberId); + if ( pData ) + { + OUString aStr = pCache->GetFormattedString(nDim, *pData, false); + InsertGroupItem(new XclExpPCItem(aStr)); + } + } +} + +void XclExpPCField::SetNumGroupLimit( const ScDPNumGroupInfo& rNumInfo ) +{ + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rNumInfo.mbAutoStart ); + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rNumInfo.mbAutoEnd ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStart ) ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfEnd ) ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStep ) ); +} + +void XclExpPCField::SetDateGroupLimit( const ScDPNumGroupInfo& rDateInfo, bool bUseStep ) +{ + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rDateInfo.mbAutoStart ); + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rDateInfo.mbAutoEnd ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfStart ) ) ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfEnd ) ) ); + sal_Int16 nStep = bUseStep ? limit_cast< sal_Int16 >( rDateInfo.mfStep, 1, SAL_MAX_INT16 ) : 1; + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( nStep ) ); +} + +void XclExpPCField::Finalize() +{ + // flags + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS, !GetVisItemList().IsEmpty() ); + // Excel writes long indexes even for 0x0100 items (indexes from 0x00 to 0xFF) + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_16BIT, maOrigItemList.GetSize() >= 0x0100 ); + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP, IsNumGroupField() || IsDateGroupField() ); + /* mnTypeFlags is updated in all Insert***Item() functions. Now the flags + for the current combination of item types is added to the flags. */ + ::set_flag( maFieldInfo.mnFlags, spnPCItemFlags[ mnTypeFlags ] ); + + // item count fields + maFieldInfo.mnVisItems = static_cast< sal_uInt16 >( GetVisItemList().GetSize() ); + maFieldInfo.mnGroupItems = static_cast< sal_uInt16 >( maGroupItemList.GetSize() ); + // maFieldInfo.mnBaseItems set in InitStdGroupField() + maFieldInfo.mnOrigItems = static_cast< sal_uInt16 >( maOrigItemList.GetSize() ); +} + +void XclExpPCField::WriteSxnumgroup( XclExpStream& rStrm ) +{ + if( IsNumGroupField() || IsDateGroupField() ) + { + // SXNUMGROUP record + rStrm.StartRecord( EXC_ID_SXNUMGROUP, 2 ); + rStrm << maNumGroupInfo; + rStrm.EndRecord(); + + // limits (min/max/step) for numeric grouping + OSL_ENSURE( maNumGroupLimits.GetSize() == 3, + "XclExpPCField::WriteSxnumgroup - missing numeric grouping limits" ); + maNumGroupLimits.Save( rStrm ); + } +} + +void XclExpPCField::WriteSxgroupinfo( XclExpStream& rStrm ) +{ + OSL_ENSURE( IsStdGroupField() != maGroupOrder.empty(), + "XclExpPCField::WriteSxgroupinfo - missing grouping info" ); + if( IsStdGroupField() && !maGroupOrder.empty() ) + { + rStrm.StartRecord( EXC_ID_SXGROUPINFO, 2 * maGroupOrder.size() ); + for( const auto& rItem : maGroupOrder ) + rStrm << rItem; + rStrm.EndRecord(); + } +} + +void XclExpPCField::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maFieldInfo; +} + +XclExpPivotCache::XclExpPivotCache( const XclExpRoot& rRoot, const ScDPObject& rDPObj, sal_uInt16 nListIdx ) : + XclExpRoot( rRoot ), + mnListIdx( nListIdx ), + mbValid( false ) +{ + // source from sheet only + const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc(); + if(!pSrcDesc) + return; + + /* maOrigSrcRange: Range received from the DataPilot object. + maExpSrcRange: Range written to the DCONREF record. + maDocSrcRange: Range used to get source data from Calc document. + This range may be shorter than maExpSrcRange to improve export + performance (#i22541#). */ + maOrigSrcRange = maExpSrcRange = maDocSrcRange = pSrcDesc->GetSourceRange(); + maSrcRangeName = pSrcDesc->GetRangeName(); + + // internal sheet data only + SCTAB nScTab = maExpSrcRange.aStart.Tab(); + if( !((nScTab == maExpSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( nScTab )) ) + return; + + // ValidateRange() restricts source range to valid Excel limits + if( !GetAddressConverter().ValidateRange( maExpSrcRange, true ) ) + return; + + // #i22541# skip empty cell areas (performance) + SCCOL nDocCol1, nDocCol2; + SCROW nDocRow1, nDocRow2; + GetDoc().GetDataStart( nScTab, nDocCol1, nDocRow1 ); + GetDoc().GetPrintArea( nScTab, nDocCol2, nDocRow2, false ); + SCCOL nSrcCol1 = maExpSrcRange.aStart.Col(); + SCROW nSrcRow1 = maExpSrcRange.aStart.Row(); + SCCOL nSrcCol2 = maExpSrcRange.aEnd.Col(); + SCROW nSrcRow2 = maExpSrcRange.aEnd.Row(); + + // #i22541# do not store index list for too big ranges + if( 2 * (nDocRow2 - nDocRow1) < (nSrcRow2 - nSrcRow1) ) + ::set_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA, false ); + + // adjust row indexes, keep one row of empty area to surely have the empty cache item + if( nSrcRow1 < nDocRow1 ) + nSrcRow1 = nDocRow1 - 1; + if( nSrcRow2 > nDocRow2 ) + nSrcRow2 = nDocRow2 + 1; + + maDocSrcRange.aStart.SetCol( ::std::max( nDocCol1, nSrcCol1 ) ); + maDocSrcRange.aStart.SetRow( nSrcRow1 ); + maDocSrcRange.aEnd.SetCol( ::std::min( nDocCol2, nSrcCol2 ) ); + maDocSrcRange.aEnd.SetRow( nSrcRow2 ); + + GetDoc().GetName( nScTab, maTabName ); + maPCInfo.mnSrcRecs = static_cast< sal_uInt32 >( maExpSrcRange.aEnd.Row() - maExpSrcRange.aStart.Row() ); + maPCInfo.mnStrmId = nListIdx + 1; + maPCInfo.mnSrcType = EXC_SXDB_SRC_SHEET; + + AddFields( rDPObj ); + + mbValid = true; +} + +bool XclExpPivotCache::HasItemIndexList() const +{ + return ::get_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA ); +} + +sal_uInt16 XclExpPivotCache::GetFieldCount() const +{ + return static_cast< sal_uInt16 >( maFieldList.GetSize() ); +} + +const XclExpPCField* XclExpPivotCache::GetField( sal_uInt16 nFieldIdx ) const +{ + return maFieldList.GetRecord( nFieldIdx ); +} + +bool XclExpPivotCache::HasAddFields() const +{ + // pivot cache can be shared, if there are no additional cache fields + return maPCInfo.mnStdFields < maPCInfo.mnTotalFields; +} + +bool XclExpPivotCache::HasEqualDataSource( const ScDPObject& rDPObj ) const +{ + /* For now, only sheet sources are supported, therefore it is enough to + compare the ScSheetSourceDesc. Later, there should be done more complicated + comparisons regarding the source type of rDPObj and this cache. */ + if( const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc() ) + return pSrcDesc->GetSourceRange() == maOrigSrcRange; + return false; +} + +void XclExpPivotCache::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( mbValid, "XclExpPivotCache::Save - invalid pivot cache" ); + // SXIDSTM + XclExpUInt16Record( EXC_ID_SXIDSTM, maPCInfo.mnStrmId ).Save( rStrm ); + // SXVS + XclExpUInt16Record( EXC_ID_SXVS, EXC_SXVS_SHEET ).Save( rStrm ); + + if (!maSrcRangeName.isEmpty()) + // DCONNAME + WriteDConName(rStrm); + else + // DCONREF + WriteDconref(rStrm); + + // create the pivot cache storage stream + WriteCacheStream(); +} + +void XclExpPivotCache::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ +} + +void XclExpPivotCache::AddFields( const ScDPObject& rDPObj ) +{ + AddStdFields( rDPObj ); + maPCInfo.mnStdFields = GetFieldCount(); + AddGroupFields( rDPObj ); + maPCInfo.mnTotalFields = GetFieldCount(); +}; + +void XclExpPivotCache::AddStdFields( const ScDPObject& rDPObj ) +{ + // if item index list is not written, used shortened source range (maDocSrcRange) for performance + const ScRange& rRange = HasItemIndexList() ? maExpSrcRange : maDocSrcRange; + // create a standard pivot cache field for each source column + for( SCCOL nScCol = rRange.aStart.Col(), nEndScCol = rRange.aEnd.Col(); nScCol <= nEndScCol; ++nScCol ) + { + ScRange aColRange( rRange ); + aColRange.aStart.SetCol( nScCol ); + aColRange.aEnd.SetCol( nScCol ); + maFieldList.AppendNewRecord( new XclExpPCField( + GetRoot(), GetFieldCount(), rDPObj, aColRange ) ); + } +} + +void XclExpPivotCache::AddGroupFields( const ScDPObject& rDPObj ) +{ + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + if(!pSaveData) + return; + const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData(); + if( !pSaveDimData ) + return; + + // loop over all existing standard fields to find their group fields + for( sal_uInt16 nFieldIdx = 0; nFieldIdx < maPCInfo.mnStdFields; ++nFieldIdx ) + { + if( XclExpPCField* pCurrStdField = maFieldList.GetRecord( nFieldIdx ) ) + { + const ScDPSaveGroupDimension* pGroupDim = pSaveDimData->GetGroupDimForBase( pCurrStdField->GetFieldName() ); + XclExpPCField* pLastGroupField = pCurrStdField; + while( pGroupDim ) + { + // insert the new grouping field + XclExpPCFieldRef xNewGroupField = new XclExpPCField( + GetRoot(), GetFieldCount(), rDPObj, *pGroupDim, *pCurrStdField ); + maFieldList.AppendRecord( xNewGroupField ); + + // register new grouping field at current grouping field, building a chain + pLastGroupField->SetGroupChildField( *xNewGroupField ); + + // next grouping dimension + pGroupDim = pSaveDimData->GetGroupDimForBase( pGroupDim->GetGroupDimName() ); + pLastGroupField = xNewGroupField.get(); + } + } + } +} + +void XclExpPivotCache::WriteDconref( XclExpStream& rStrm ) const +{ + XclExpString aRef( XclExpUrlHelper::EncodeUrl( GetRoot(), u"", &maTabName ) ); + rStrm.StartRecord( EXC_ID_DCONREF, 7 + aRef.GetSize() ); + rStrm << static_cast< sal_uInt16 >( maExpSrcRange.aStart.Row() ) + << static_cast< sal_uInt16 >( maExpSrcRange.aEnd.Row() ) + << static_cast< sal_uInt8 >( maExpSrcRange.aStart.Col() ) + << static_cast< sal_uInt8 >( maExpSrcRange.aEnd.Col() ) + << aRef + << sal_uInt8( 0 ); + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteDConName( XclExpStream& rStrm ) const +{ + XclExpString aName(maSrcRangeName); + rStrm.StartRecord(EXC_ID_DCONNAME, aName.GetSize() + 2); + rStrm << aName << sal_uInt16(0); + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteCacheStream() +{ + tools::SvRef<SotStorage> xSvStrg = OpenStorage( EXC_STORAGE_PTCACHE ); + tools::SvRef<SotStorageStream> xSvStrm = OpenStream( xSvStrg, ScfTools::GetHexStr( maPCInfo.mnStrmId ) ); + if( !xSvStrm.is() ) + return; + + XclExpStream aStrm( *xSvStrm, GetRoot() ); + // SXDB + WriteSxdb( aStrm ); + // SXDBEX + WriteSxdbex( aStrm ); + // field list (SXFIELD and items) + maFieldList.Save( aStrm ); + // index table (list of SXINDEXLIST) + WriteSxindexlistList( aStrm ); + // EOF + XclExpEmptyRecord( EXC_ID_EOF ).Save( aStrm ); +} + +void XclExpPivotCache::WriteSxdb( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXDB, 21 ); + rStrm << maPCInfo; + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteSxdbex( XclExpStream& rStrm ) +{ + rStrm.StartRecord( EXC_ID_SXDBEX, 12 ); + rStrm << EXC_SXDBEX_CREATION_DATE + << sal_uInt32( 0 ); // number of SXFORMULA records + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteSxindexlistList( XclExpStream& rStrm ) const +{ + if( !HasItemIndexList() ) + return; + + std::size_t nRecSize = 0; + size_t nPos, nSize = maFieldList.GetSize(); + for( nPos = 0; nPos < nSize; ++nPos ) + nRecSize += maFieldList.GetRecord( nPos )->GetIndexSize(); + + for( sal_uInt32 nSrcRow = 0; nSrcRow < maPCInfo.mnSrcRecs; ++nSrcRow ) + { + rStrm.StartRecord( EXC_ID_SXINDEXLIST, nRecSize ); + for( nPos = 0; nPos < nSize; ++nPos ) + maFieldList.GetRecord( nPos )->WriteIndex( rStrm, nSrcRow ); + rStrm.EndRecord(); + } +} + +// Pivot table + +namespace { + +/** Returns a display string for a data field containing the field name and aggregation function. */ +OUString lclGetDataFieldCaption( std::u16string_view rFieldName, ScGeneralFunction eFunc ) +{ + OUString aCaption; + + TranslateId pResIdx; + switch( eFunc ) + { + case ScGeneralFunction::SUM: pResIdx = STR_FUN_TEXT_SUM; break; + case ScGeneralFunction::COUNT: pResIdx = STR_FUN_TEXT_COUNT; break; + case ScGeneralFunction::AVERAGE: pResIdx = STR_FUN_TEXT_AVG; break; + case ScGeneralFunction::MAX: pResIdx = STR_FUN_TEXT_MAX; break; + case ScGeneralFunction::MIN: pResIdx = STR_FUN_TEXT_MIN; break; + case ScGeneralFunction::PRODUCT: pResIdx = STR_FUN_TEXT_PRODUCT; break; + case ScGeneralFunction::COUNTNUMS: pResIdx = STR_FUN_TEXT_COUNT; break; + case ScGeneralFunction::STDEV: pResIdx = STR_FUN_TEXT_STDDEV; break; + case ScGeneralFunction::STDEVP: pResIdx = STR_FUN_TEXT_STDDEV; break; + case ScGeneralFunction::VAR: pResIdx = STR_FUN_TEXT_VAR; break; + case ScGeneralFunction::VARP: pResIdx = STR_FUN_TEXT_VAR; break; + default:; + } + if (pResIdx) + aCaption = ScResId(pResIdx) + " - "; + aCaption += rFieldName; + return aCaption; +} + +} // namespace + +XclExpPTItem::XclExpPTItem( const XclExpPCField& rCacheField, sal_uInt16 nCacheIdx ) : + XclExpRecord( EXC_ID_SXVI, 8 ), + mpCacheItem( rCacheField.GetItem( nCacheIdx ) ) +{ + maItemInfo.mnType = EXC_SXVI_TYPE_DATA; + maItemInfo.mnCacheIdx = nCacheIdx; + maItemInfo.maVisName.mbUseCache = mpCacheItem != nullptr; +} + +XclExpPTItem::XclExpPTItem( sal_uInt16 nItemType, sal_uInt16 nCacheIdx ) : + XclExpRecord( EXC_ID_SXVI, 8 ), + mpCacheItem( nullptr ) +{ + maItemInfo.mnType = nItemType; + maItemInfo.mnCacheIdx = nCacheIdx; + maItemInfo.maVisName.mbUseCache = true; +} + +OUString XclExpPTItem::GetItemName() const +{ + return mpCacheItem ? mpCacheItem->ConvertToText() : OUString(); +} + +void XclExpPTItem::SetPropertiesFromMember( const ScDPSaveMember& rSaveMem ) +{ + // #i115659# GetIsVisible() is not valid if HasIsVisible() returns false, default is 'visible' then + ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN, rSaveMem.HasIsVisible() && !rSaveMem.GetIsVisible() ); + // #i115659# GetShowDetails() is not valid if HasShowDetails() returns false, default is 'show detail' then + ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL, rSaveMem.HasShowDetails() && !rSaveMem.GetShowDetails() ); + + // visible name + const std::optional<OUString> & pVisName = rSaveMem.GetLayoutName(); + if (pVisName && *pVisName != GetItemName()) + maItemInfo.SetVisName(*pVisName); +} + +void XclExpPTItem::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maItemInfo; +} + +XclExpPTField::XclExpPTField( const XclExpPivotTable& rPTable, sal_uInt16 nCacheIdx ) : + mrPTable( rPTable ), + mpCacheField( rPTable.GetCacheField( nCacheIdx ) ) +{ + maFieldInfo.mnCacheIdx = nCacheIdx; + + // create field items + if( mpCacheField ) + for( sal_uInt16 nItemIdx = 0, nItemCount = mpCacheField->GetItemCount(); nItemIdx < nItemCount; ++nItemIdx ) + maItemList.AppendNewRecord( new XclExpPTItem( *mpCacheField, nItemIdx ) ); + maFieldInfo.mnItemCount = static_cast< sal_uInt16 >( maItemList.GetSize() ); +} + +// data access ---------------------------------------------------------------- + +OUString XclExpPTField::GetFieldName() const +{ + return mpCacheField ? mpCacheField->GetFieldName() : OUString(); +} + +sal_uInt16 XclExpPTField::GetLastDataInfoIndex() const +{ + OSL_ENSURE( !maDataInfoVec.empty(), "XclExpPTField::GetLastDataInfoIndex - no data info found" ); + // will return 0xFFFF for empty vector -> ok + return static_cast< sal_uInt16 >( maDataInfoVec.size() - 1 ); +} + +sal_uInt16 XclExpPTField::GetItemIndex( std::u16string_view rName, sal_uInt16 nDefaultIdx ) const +{ + for( size_t nPos = 0, nSize = maItemList.GetSize(); nPos < nSize; ++nPos ) + if( maItemList.GetRecord( nPos )->GetItemName() == rName ) + return static_cast< sal_uInt16 >( nPos ); + return nDefaultIdx; +} + +// fill data -------------------------------------------------------------- + +/** + * Calc's subtotal names are escaped with backslashes ('\'), while Excel's + * are not escaped at all. + */ +static OUString lcl_convertCalcSubtotalName(const OUString& rName) +{ + OUStringBuffer aBuf; + const sal_Unicode* p = rName.getStr(); + sal_Int32 n = rName.getLength(); + bool bEscaped = false; + for (sal_Int32 i = 0; i < n; ++i) + { + const sal_Unicode c = p[i]; + if (!bEscaped && c == '\\') + { + bEscaped = true; + continue; + } + + aBuf.append(c); + bEscaped = false; + } + return aBuf.makeStringAndClear(); +} + +void XclExpPTField::SetPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + // orientation + DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation(); + OSL_ENSURE( eOrient != DataPilotFieldOrientation_DATA, "XclExpPTField::SetPropertiesFromDim - called for data field" ); + maFieldInfo.AddApiOrient( eOrient ); + + // show empty items (#i115659# GetShowEmpty() is not valid if HasShowEmpty() returns false, default is false then) + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL, rSaveDim.HasShowEmpty() && rSaveDim.GetShowEmpty() ); + + // visible name + const std::optional<OUString> & pLayoutName = rSaveDim.GetLayoutName(); + if (pLayoutName && *pLayoutName != GetFieldName()) + maFieldInfo.SetVisName(*pLayoutName); + + const std::optional<OUString> & pSubtotalName = rSaveDim.GetSubtotalName(); + if (pSubtotalName) + { + OUString aSubName = lcl_convertCalcSubtotalName(*pSubtotalName); + maFieldExtInfo.mpFieldTotalName = aSubName; + } + + // subtotals + XclPTSubtotalVec aSubtotals; + aSubtotals.reserve( static_cast< size_t >( rSaveDim.GetSubTotalsCount() ) ); + for( tools::Long nSubtIdx = 0, nSubtCount = rSaveDim.GetSubTotalsCount(); nSubtIdx < nSubtCount; ++nSubtIdx ) + aSubtotals.push_back( rSaveDim.GetSubTotalFunc( nSubtIdx ) ); + maFieldInfo.SetSubtotals( aSubtotals ); + + // sorting + if( const DataPilotFieldSortInfo* pSortInfo = rSaveDim.GetSortInfo() ) + { + maFieldExtInfo.SetApiSortMode( pSortInfo->Mode ); + if( pSortInfo->Mode == css::sheet::DataPilotFieldSortMode::DATA ) + maFieldExtInfo.mnSortField = mrPTable.GetDataFieldIndex( pSortInfo->Field, EXC_SXVDEX_SORT_OWN ); + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC, pSortInfo->IsAscending ); + } + + // auto show + if( const DataPilotFieldAutoShowInfo* pShowInfo = rSaveDim.GetAutoShowInfo() ) + { + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW, pShowInfo->IsEnabled ); + maFieldExtInfo.SetApiAutoShowMode( pShowInfo->ShowItemsMode ); + maFieldExtInfo.SetApiAutoShowCount( pShowInfo->ItemCount ); + maFieldExtInfo.mnShowField = mrPTable.GetDataFieldIndex( pShowInfo->DataField, EXC_SXVDEX_SHOW_NONE ); + } + + // layout + if( const DataPilotFieldLayoutInfo* pLayoutInfo = rSaveDim.GetLayoutInfo() ) + { + maFieldExtInfo.SetApiLayoutMode( pLayoutInfo->LayoutMode ); + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK, pLayoutInfo->AddEmptyLines ); + } + + // special page field properties + if( eOrient == DataPilotFieldOrientation_PAGE ) + { + maPageInfo.mnField = GetFieldIndex(); + maPageInfo.mnSelItem = EXC_SXPI_ALLITEMS; + } + + // item properties + const ScDPSaveDimension::MemberList &rMembers = rSaveDim.GetMembers(); + for (const auto& pMember : rMembers) + if( XclExpPTItem* pItem = GetItemAcc( pMember->GetName() ) ) + pItem->SetPropertiesFromMember( *pMember ); +} + +void XclExpPTField::SetDataPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + maDataInfoVec.emplace_back( ); + XclPTDataFieldInfo& rDataInfo = maDataInfoVec.back(); + rDataInfo.mnField = GetFieldIndex(); + + // orientation + maFieldInfo.AddApiOrient( DataPilotFieldOrientation_DATA ); + + // aggregation function + ScGeneralFunction eFunc = rSaveDim.GetFunction(); + rDataInfo.SetApiAggFunc( eFunc ); + + // visible name + const std::optional<OUString> & pVisName = rSaveDim.GetLayoutName(); + if (pVisName) + rDataInfo.SetVisName(*pVisName); + else + rDataInfo.SetVisName( lclGetDataFieldCaption( GetFieldName(), eFunc ) ); + + // result field reference + if( const DataPilotFieldReference* pFieldRef = rSaveDim.GetReferenceValue() ) + { + rDataInfo.SetApiRefType( pFieldRef->ReferenceType ); + rDataInfo.SetApiRefItemType( pFieldRef->ReferenceItemType ); + if( const XclExpPTField* pRefField = mrPTable.GetField( pFieldRef->ReferenceField ) ) + { + rDataInfo.mnRefField = pRefField->GetFieldIndex(); + if( pFieldRef->ReferenceItemType == css::sheet::DataPilotFieldReferenceItemType::NAMED ) + rDataInfo.mnRefItem = pRefField->GetItemIndex( pFieldRef->ReferenceItemName, 0 ); + } + } +} + +void XclExpPTField::AppendSubtotalItems() +{ + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_DEFAULT ) AppendSubtotalItem( EXC_SXVI_TYPE_DEFAULT ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_SUM ) AppendSubtotalItem( EXC_SXVI_TYPE_SUM ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNT ) AppendSubtotalItem( EXC_SXVI_TYPE_COUNT ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_AVERAGE ) AppendSubtotalItem( EXC_SXVI_TYPE_AVERAGE ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MAX ) AppendSubtotalItem( EXC_SXVI_TYPE_MAX ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MIN ) AppendSubtotalItem( EXC_SXVI_TYPE_MIN ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_PROD ) AppendSubtotalItem( EXC_SXVI_TYPE_PROD ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNTNUM ) AppendSubtotalItem( EXC_SXVI_TYPE_COUNTNUM ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEV ) AppendSubtotalItem( EXC_SXVI_TYPE_STDDEV ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEVP ) AppendSubtotalItem( EXC_SXVI_TYPE_STDDEVP ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VAR ) AppendSubtotalItem( EXC_SXVI_TYPE_VAR ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VARP ) AppendSubtotalItem( EXC_SXVI_TYPE_VARP ); +} + +// records -------------------------------------------------------------------- + +void XclExpPTField::WriteSxpiEntry( XclExpStream& rStrm ) const +{ + rStrm << maPageInfo; +} + +void XclExpPTField::WriteSxdi( XclExpStream& rStrm, sal_uInt16 nDataInfoIdx ) const +{ + OSL_ENSURE( nDataInfoIdx < maDataInfoVec.size(), "XclExpPTField::WriteSxdi - data field not found" ); + if( nDataInfoIdx < maDataInfoVec.size() ) + { + rStrm.StartRecord( EXC_ID_SXDI, 12 ); + rStrm << maDataInfoVec[ nDataInfoIdx ]; + rStrm.EndRecord(); + } +} + +void XclExpPTField::Save( XclExpStream& rStrm ) +{ + // SXVD + WriteSxvd( rStrm ); + // list of SXVI records + maItemList.Save( rStrm ); + // SXVDEX + WriteSxvdex( rStrm ); +} + +// private -------------------------------------------------------------------- + +XclExpPTItem* XclExpPTField::GetItemAcc( std::u16string_view rName ) +{ + XclExpPTItem* pItem = nullptr; + for( size_t nPos = 0, nSize = maItemList.GetSize(); !pItem && (nPos < nSize); ++nPos ) + if( maItemList.GetRecord( nPos )->GetItemName() == rName ) + pItem = maItemList.GetRecord( nPos ); + return pItem; +} + +void XclExpPTField::AppendSubtotalItem( sal_uInt16 nItemType ) +{ + maItemList.AppendNewRecord( new XclExpPTItem( nItemType, EXC_SXVI_DEFAULT_CACHE ) ); + ++maFieldInfo.mnItemCount; +} + +void XclExpPTField::WriteSxvd( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXVD, 10 ); + rStrm << maFieldInfo; + rStrm.EndRecord(); +} + +void XclExpPTField::WriteSxvdex( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXVDEX, 20 ); + rStrm << maFieldExtInfo; + rStrm.EndRecord(); +} + +XclExpPivotTable::XclExpPivotTable( const XclExpRoot& rRoot, const ScDPObject& rDPObj, const XclExpPivotCache& rPCache ) : + XclExpRoot( rRoot ), + mrPCache( rPCache ), + maDataOrientField( *this, EXC_SXIVD_DATA ), + mnOutScTab( 0 ), + mbValid( false ), + mbFilterBtn( false ) +{ + const ScRange& rOutScRange = rDPObj.GetOutRange(); + if( !GetAddressConverter().ConvertRange( maPTInfo.maOutXclRange, rOutScRange, true ) ) + return; + + // DataPilot properties ----------------------------------------------- + + // pivot table properties from DP object + mnOutScTab = rOutScRange.aStart.Tab(); + maPTInfo.maTableName = rDPObj.GetName(); + maPTInfo.mnCacheIdx = mrPCache.GetCacheIndex(); + + maPTViewEx9Info.Init( rDPObj ); + + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + if( !pSaveData ) + return; + + // additional properties from ScDPSaveData + SetPropertiesFromDP( *pSaveData ); + + // loop over all dimensions --------------------------------------- + + /* 1) Default-construct all pivot table fields for all pivot cache fields. */ + for( sal_uInt16 nFieldIdx = 0, nFieldCount = mrPCache.GetFieldCount(); nFieldIdx < nFieldCount; ++nFieldIdx ) + maFieldList.AppendNewRecord( new XclExpPTField( *this, nFieldIdx ) ); + + const ScDPSaveData::DimsType& rDimList = pSaveData->GetDimensions(); + + /* 2) First process all data dimensions, they are needed for extended + settings of row/column/page fields (sorting/auto show). */ + for (auto const& iter : rDimList) + { + if (iter->GetOrientation() == DataPilotFieldOrientation_DATA) + SetDataFieldPropertiesFromDim(*iter); + } + + /* 3) Row/column/page/hidden fields. */ + for (auto const& iter : rDimList) + { + if (iter->GetOrientation() != DataPilotFieldOrientation_DATA) + SetFieldPropertiesFromDim(*iter); + } + + // Finalize ------------------------------------------------------- + + Finalize(); + mbValid = true; +} + +const XclExpPCField* XclExpPivotTable::GetCacheField( sal_uInt16 nCacheIdx ) const +{ + return mrPCache.GetField( nCacheIdx ); +} + +const XclExpPTField* XclExpPivotTable::GetField( sal_uInt16 nFieldIdx ) const +{ + return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField : maFieldList.GetRecord( nFieldIdx ); +} + +const XclExpPTField* XclExpPivotTable::GetField( std::u16string_view rName ) const +{ + return const_cast< XclExpPivotTable* >( this )->GetFieldAcc( rName ); +} + +sal_uInt16 XclExpPivotTable::GetDataFieldIndex( const OUString& rName, sal_uInt16 nDefaultIdx ) const +{ + auto aIt = std::find_if(maDataFields.begin(), maDataFields.end(), + [this, &rName](const XclPTDataFieldPos& rDataField) { + const XclExpPTField* pField = GetField( rDataField.first ); + return pField && pField->GetFieldName() == rName; + }); + if (aIt != maDataFields.end()) + return static_cast< sal_uInt16 >( std::distance(maDataFields.begin(), aIt) ); + return nDefaultIdx; +} + +void XclExpPivotTable::Save( XclExpStream& rStrm ) +{ + if( !mbValid ) + return; + + // SXVIEW + WriteSxview( rStrm ); + // pivot table fields (SXVD, SXVDEX, and item records) + maFieldList.Save( rStrm ); + // SXIVD records for row and column fields + WriteSxivd( rStrm, maRowFields ); + WriteSxivd( rStrm, maColFields ); + // SXPI + WriteSxpi( rStrm ); + // list of SXDI records containing data field info + WriteSxdiList( rStrm ); + // SXLI records + WriteSxli( rStrm, maPTInfo.mnDataRows, maPTInfo.mnRowFields ); + WriteSxli( rStrm, maPTInfo.mnDataCols, maPTInfo.mnColFields ); + // SXEX + WriteSxex( rStrm ); + // QSISXTAG + WriteQsiSxTag( rStrm ); + // SXVIEWEX9 + WriteSxViewEx9( rStrm ); +} + +XclExpPTField* XclExpPivotTable::GetFieldAcc( std::u16string_view rName ) +{ + XclExpPTField* pField = nullptr; + for( size_t nPos = 0, nSize = maFieldList.GetSize(); !pField && (nPos < nSize); ++nPos ) + if( maFieldList.GetRecord( nPos )->GetFieldName() == rName ) + pField = maFieldList.GetRecord( nPos ); + return pField; +} + +XclExpPTField* XclExpPivotTable::GetFieldAcc( const ScDPSaveDimension& rSaveDim ) +{ + // data field orientation field? + if( rSaveDim.IsDataLayout() ) + return &maDataOrientField; + + // a real dimension + OUString aFieldName = ScDPUtil::getSourceDimensionName(rSaveDim.GetName()); + return aFieldName.isEmpty() ? nullptr : GetFieldAcc(aFieldName); +} + +// fill data -------------------------------------------------------------- + +void XclExpPivotTable::SetPropertiesFromDP( const ScDPSaveData& rSaveData ) +{ + ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND, rSaveData.GetRowGrand() ); + ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND, rSaveData.GetColumnGrand() ); + ::set_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN, rSaveData.GetDrillDown() ); + mbFilterBtn = rSaveData.GetFilterButton(); + const ScDPSaveDimension* pDim = rSaveData.GetExistingDataLayoutDimension(); + + if (pDim && pDim->GetLayoutName()) + maPTInfo.maDataName = *pDim->GetLayoutName(); + else + maPTInfo.maDataName = ScResId(STR_PIVOT_DATA); +} + +void XclExpPivotTable::SetFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + XclExpPTField* pField = GetFieldAcc( rSaveDim ); + if(!pField) + return; + + // field properties + pField->SetPropertiesFromDim( rSaveDim ); + + // update the corresponding field position list + DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation(); + sal_uInt16 nFieldIdx = pField->GetFieldIndex(); + bool bDataLayout = nFieldIdx == EXC_SXIVD_DATA; + bool bMultiData = maDataFields.size() > 1; + + if( bDataLayout && !bMultiData ) + return; + + switch( eOrient ) + { + case DataPilotFieldOrientation_ROW: + maRowFields.push_back( nFieldIdx ); + if( bDataLayout ) + maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW; + break; + case DataPilotFieldOrientation_COLUMN: + maColFields.push_back( nFieldIdx ); + if( bDataLayout ) + maPTInfo.mnDataAxis = EXC_SXVD_AXIS_COL; + break; + case DataPilotFieldOrientation_PAGE: + maPageFields.push_back( nFieldIdx ); + OSL_ENSURE( !bDataLayout, "XclExpPivotTable::SetFieldPropertiesFromDim - wrong orientation for data fields" ); + break; + case DataPilotFieldOrientation_DATA: + OSL_FAIL( "XclExpPivotTable::SetFieldPropertiesFromDim - called for data field" ); + break; + default:; + } +} + +void XclExpPivotTable::SetDataFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + if( XclExpPTField* pField = GetFieldAcc( rSaveDim ) ) + { + // field properties + pField->SetDataPropertiesFromDim( rSaveDim ); + // update the data field position list + maDataFields.emplace_back( pField->GetFieldIndex(), pField->GetLastDataInfoIndex() ); + } +} + +void XclExpPivotTable::Finalize() +{ + // field numbers + maPTInfo.mnFields = static_cast< sal_uInt16 >( maFieldList.GetSize() ); + maPTInfo.mnRowFields = static_cast< sal_uInt16 >( maRowFields.size() ); + maPTInfo.mnColFields = static_cast< sal_uInt16 >( maColFields.size() ); + maPTInfo.mnPageFields = static_cast< sal_uInt16 >( maPageFields.size() ); + maPTInfo.mnDataFields = static_cast< sal_uInt16 >( maDataFields.size() ); + + maPTExtInfo.mnPagePerRow = maPTInfo.mnPageFields; + maPTExtInfo.mnPagePerCol = (maPTInfo.mnPageFields > 0) ? 1 : 0; + + // subtotal items + for( size_t nPos = 0, nSize = maFieldList.GetSize(); nPos < nSize; ++nPos ) + maFieldList.GetRecord( nPos )->AppendSubtotalItems(); + + // find data field orientation field + maPTInfo.mnDataPos = EXC_SXVIEW_DATALAST; + const ScfUInt16Vec* pFieldVec = nullptr; + switch( maPTInfo.mnDataAxis ) + { + case EXC_SXVD_AXIS_ROW: pFieldVec = &maRowFields; break; + case EXC_SXVD_AXIS_COL: pFieldVec = &maColFields; break; + } + + if( pFieldVec && !pFieldVec->empty() && (pFieldVec->back() != EXC_SXIVD_DATA) ) + { + ScfUInt16Vec::const_iterator aIt = ::std::find( pFieldVec->begin(), pFieldVec->end(), EXC_SXIVD_DATA ); + if( aIt != pFieldVec->end() ) + maPTInfo.mnDataPos = static_cast< sal_uInt16 >( std::distance(pFieldVec->begin(), aIt) ); + } + + // single data field is always row oriented + if( maPTInfo.mnDataAxis == EXC_SXVD_AXIS_NONE ) + maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW; + + // update output range (initialized in ctor) + sal_uInt16& rnXclCol1 = maPTInfo.maOutXclRange.maFirst.mnCol; + sal_uInt32& rnXclRow1 = maPTInfo.maOutXclRange.maFirst.mnRow; + sal_uInt16& rnXclCol2 = maPTInfo.maOutXclRange.maLast.mnCol; + sal_uInt32& rnXclRow2 = maPTInfo.maOutXclRange.maLast.mnRow; + // exclude page fields from output range + rnXclRow1 = rnXclRow1 + maPTInfo.mnPageFields; + // exclude filter button from output range + if( mbFilterBtn ) + ++rnXclRow1; + // exclude empty row between (filter button and/or page fields) and table + if( mbFilterBtn || maPTInfo.mnPageFields ) + ++rnXclRow1; + + // data area + sal_uInt16& rnDataXclCol = maPTInfo.maDataXclPos.mnCol; + sal_uInt32& rnDataXclRow = maPTInfo.maDataXclPos.mnRow; + rnDataXclCol = rnXclCol1 + maPTInfo.mnRowFields; + rnDataXclRow = rnXclRow1 + maPTInfo.mnColFields + 1; + if( maDataFields.empty() ) + ++rnDataXclRow; + + bool bExtraHeaderRow = (0 == maPTViewEx9Info.mnGridLayout && maPTInfo.mnColFields == 0); + if (bExtraHeaderRow) + // Insert an extra row only when there is no column field. + ++rnDataXclRow; + + rnXclCol2 = ::std::max( rnXclCol2, rnDataXclCol ); + rnXclRow2 = ::std::max( rnXclRow2, rnDataXclRow ); + maPTInfo.mnDataCols = rnXclCol2 - rnDataXclCol + 1; + maPTInfo.mnDataRows = rnXclRow2 - rnDataXclRow + 1; + + // first heading + maPTInfo.mnFirstHeadRow = rnXclRow1 + 1; + if (bExtraHeaderRow) + maPTInfo.mnFirstHeadRow += 1; +} + +// records ---------------------------------------------------------------- + +void XclExpPivotTable::WriteSxview( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXVIEW, 46 + maPTInfo.maTableName.getLength() + maPTInfo.maDataName.getLength() ); + rStrm << maPTInfo; + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteSxivd( XclExpStream& rStrm, const ScfUInt16Vec& rFields ) +{ + if( !rFields.empty() ) + { + rStrm.StartRecord( EXC_ID_SXIVD, rFields.size() * 2 ); + for( const auto& rField : rFields ) + rStrm << rField; + rStrm.EndRecord(); + } +} + +void XclExpPivotTable::WriteSxpi( XclExpStream& rStrm ) const +{ + if( !maPageFields.empty() ) + { + rStrm.StartRecord( EXC_ID_SXPI, maPageFields.size() * 6 ); + rStrm.SetSliceSize( 6 ); + for( const auto& rPageField : maPageFields ) + { + XclExpPTFieldRef xField = maFieldList.GetRecord( rPageField ); + if( xField ) + xField->WriteSxpiEntry( rStrm ); + } + rStrm.EndRecord(); + } +} + +void XclExpPivotTable::WriteSxdiList( XclExpStream& rStrm ) const +{ + for( const auto& [rFieldIdx, rDataInfoIdx] : maDataFields ) + { + XclExpPTFieldRef xField = maFieldList.GetRecord( rFieldIdx ); + if( xField ) + xField->WriteSxdi( rStrm, rDataInfoIdx ); + } +} + +void XclExpPivotTable::WriteSxli( XclExpStream& rStrm, sal_uInt16 nLineCount, sal_uInt16 nIndexCount ) +{ + if( nLineCount <= 0 ) + return; + + std::size_t nLineSize = 8 + 2 * nIndexCount; + rStrm.StartRecord( EXC_ID_SXLI, nLineSize * nLineCount ); + + /* Excel expects the records to be filled completely, do not + set a segment size... */ +// rStrm.SetSliceSize( nLineSize ); + + for( sal_uInt16 nLine = 0; nLine < nLineCount; ++nLine ) + { + // Excel XP needs a partly initialized SXLI record + rStrm << sal_uInt16( 0 ) // number of equal index entries + << EXC_SXVI_TYPE_DATA + << nIndexCount + << EXC_SXLI_DEFAULTFLAGS; + rStrm.WriteZeroBytes( 2 * nIndexCount ); + } + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteSxex( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXEX, 24 ); + rStrm << maPTExtInfo; + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteQsiSxTag( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( 0x0802, 32 ); + + sal_uInt16 const nRecordType = 0x0802; + sal_uInt16 const nDummyFlags = 0x0000; + sal_uInt16 const nTableType = 1; // 0 = query table : 1 = pivot table + + rStrm << nRecordType << nDummyFlags << nTableType; + + // General flags + sal_uInt16 const nFlags = 0x0001; +#if 0 + // for doc purpose + sal_uInt16 nFlags = 0x0000; + bool bEnableRefresh = true; + bool bPCacheInvalid = false; + bool bOlapPTReport = false; + + if (bEnableRefresh) nFlags |= 0x0001; + if (bPCacheInvalid) nFlags |= 0x0002; + if (bOlapPTReport) nFlags |= 0x0004; +#endif + rStrm << nFlags; + + // Feature-specific options. The value differs depending on the table + // type, but we assume the table type is always pivot table. + sal_uInt32 const nOptions = 0x00000000; +#if 0 + // documentation for which bit is for what + bool bNoStencil = false; + bool bHideTotal = false; + bool bEmptyRows = false; + bool bEmptyCols = false; + if (bNoStencil) nOptions |= 0x00000001; + if (bHideTotal) nOptions |= 0x00000002; + if (bEmptyRows) nOptions |= 0x00000008; + if (bEmptyCols) nOptions |= 0x00000010; +#endif + rStrm << nOptions; + + sal_uInt8 eXclVer = 0; // Excel2000 + sal_uInt8 const nOffsetBytes = 16; + rStrm << eXclVer // version table last refreshed + << eXclVer // minimum version to refresh + << nOffsetBytes + << eXclVer; // first version created + + rStrm << XclExpString(maPTInfo.maTableName); + rStrm << static_cast<sal_uInt16>(0x0001); // no idea what this is for. + + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteSxViewEx9( XclExpStream& rStrm ) const +{ + // Until we sync the autoformat ids only export if using grid header layout + // That could only have been set via xls import so far. + if ( 0 == maPTViewEx9Info.mnGridLayout ) + { + rStrm.StartRecord( EXC_ID_SXVIEWEX9, 17 ); + rStrm << maPTViewEx9Info; + rStrm.EndRecord(); + } +} + +namespace { + +const SCTAB EXC_PTMGR_PIVOTCACHES = SCTAB_MAX; + +/** Record wrapper class to write the pivot caches or pivot tables. */ +class XclExpPivotRecWrapper : public XclExpRecordBase +{ +public: + explicit XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab ); + virtual void Save( XclExpStream& rStrm ) override; +private: + XclExpPivotTableManager& mrPTMgr; + SCTAB mnScTab; +}; + +XclExpPivotRecWrapper::XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab ) : + mrPTMgr( rPTMgr ), + mnScTab( nScTab ) +{ +} + +void XclExpPivotRecWrapper::Save( XclExpStream& rStrm ) +{ + if( mnScTab == EXC_PTMGR_PIVOTCACHES ) + mrPTMgr.WritePivotCaches( rStrm ); + else + mrPTMgr.WritePivotTables( rStrm, mnScTab ); +} + +} // namespace + +XclExpPivotTableManager::XclExpPivotTableManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpPivotTableManager::CreatePivotTables() +{ + if( ScDPCollection* pDPColl = GetDoc().GetDPCollection() ) + for( size_t nDPObj = 0, nCount = pDPColl->GetCount(); nDPObj < nCount; ++nDPObj ) + { + ScDPObject& rDPObj = (*pDPColl)[ nDPObj ]; + if( const XclExpPivotCache* pPCache = CreatePivotCache( rDPObj ) ) + maPTableList.AppendNewRecord( new XclExpPivotTable( GetRoot(), rDPObj, *pPCache ) ); + } +} + +XclExpRecordRef XclExpPivotTableManager::CreatePivotCachesRecord() +{ + return new XclExpPivotRecWrapper( *this, EXC_PTMGR_PIVOTCACHES ); +} + +XclExpRecordRef XclExpPivotTableManager::CreatePivotTablesRecord( SCTAB nScTab ) +{ + return new XclExpPivotRecWrapper( *this, nScTab ); +} + +void XclExpPivotTableManager::WritePivotCaches( XclExpStream& rStrm ) +{ + maPCacheList.Save( rStrm ); +} + +void XclExpPivotTableManager::WritePivotTables( XclExpStream& rStrm, SCTAB nScTab ) +{ + for( size_t nPos = 0, nSize = maPTableList.GetSize(); nPos < nSize; ++nPos ) + { + XclExpPivotTableRef xPTable = maPTableList.GetRecord( nPos ); + if( xPTable->GetScTab() == nScTab ) + xPTable->Save( rStrm ); + } +} + +const XclExpPivotCache* XclExpPivotTableManager::CreatePivotCache( const ScDPObject& rDPObj ) +{ + // try to find a pivot cache with the same data source + /* #i25110# In Excel, the pivot cache contains additional fields + (i.e. grouping info, calculated fields). If the passed DataPilot object + or the found cache contains this data, do not share the cache with + multiple pivot tables. */ + if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() ) + { + const ScDPDimensionSaveData* pDimSaveData = pSaveData->GetExistingDimensionData(); + // no dimension save data at all or save data does not contain grouping info + if( !pDimSaveData || !pDimSaveData->HasGroupDimensions() ) + { + // check all existing pivot caches + for( size_t nPos = 0, nSize = maPCacheList.GetSize(); nPos < nSize; ++nPos ) + { + XclExpPivotCache* pPCache = maPCacheList.GetRecord( nPos ); + // pivot cache does not have grouping info and source data is equal + if( !pPCache->HasAddFields() && pPCache->HasEqualDataSource( rDPObj ) ) + return pPCache; + } + } + } + + // create a new pivot cache + sal_uInt16 nNewCacheIdx = static_cast< sal_uInt16 >( maPCacheList.GetSize() ); + XclExpPivotCacheRef xNewPCache = new XclExpPivotCache( GetRoot(), rDPObj, nNewCacheIdx ); + if( xNewPCache->IsValid() ) + { + maPCacheList.AppendRecord( xNewPCache.get() ); + return xNewPCache.get(); + } + + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xepivotxml.cxx b/sc/source/filter/excel/xepivotxml.cxx new file mode 100644 index 000000000..ecc39caae --- /dev/null +++ b/sc/source/filter/excel/xepivotxml.cxx @@ -0,0 +1,1187 @@ +/* -*- 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/. + */ + +#include <xepivotxml.hxx> +#include <dpcache.hxx> +#include <dpdimsave.hxx> +#include <dpitemdata.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <dputil.hxx> +#include <document.hxx> +#include <generalfunction.hxx> +#include <unonames.hxx> +#include <xestyle.hxx> +#include <xeroot.hxx> + +#include <o3tl/temporary.hxx> +#include <o3tl/safeint.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> +#include <sal/log.hxx> +#include <sax/tools/converter.hxx> +#include <sax/fastattribs.hxx> +#include <svl/numformat.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp> +#include <com/sun/star/sheet/DataPilotOutputRangeType.hpp> +#include <com/sun/star/sheet/XDimensionsSupplier.hpp> + +#include <vector> + +using namespace oox; +using namespace com::sun::star; + +namespace { + +void savePivotCacheRecordsXml( XclExpXmlStream& rStrm, const ScDPCache& rCache ) +{ + SCROW nCount = rCache.GetDataSize(); + size_t nFieldCount = rCache.GetFieldCount(); + + sax_fastparser::FSHelperPtr& pRecStrm = rStrm.GetCurrentStream(); + pRecStrm->startElement(XML_pivotCacheRecords, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + XML_count, OString::number(static_cast<tools::Long>(nCount))); + + for (SCROW i = 0; i < nCount; ++i) + { + pRecStrm->startElement(XML_r); + for (size_t nField = 0; nField < nFieldCount; ++nField) + { + const ScDPCache::IndexArrayType* pArray = rCache.GetFieldIndexArray(nField); + assert(pArray); + assert(o3tl::make_unsigned(i) < pArray->size()); + + // We are using XML_x reference (like: <x v="0"/>), instead of values here (eg: <s v="No Discount"/>). + // That's why in SavePivotCacheXml method, we need to list all items. + pRecStrm->singleElement(XML_x, XML_v, OString::number((*pArray)[i])); + } + pRecStrm->endElement(XML_r); + } + + pRecStrm->endElement(XML_pivotCacheRecords); +} + +const char* toOOXMLAxisType( sheet::DataPilotFieldOrientation eOrient ) +{ + switch (eOrient) + { + case sheet::DataPilotFieldOrientation_COLUMN: + return "axisCol"; + case sheet::DataPilotFieldOrientation_ROW: + return "axisRow"; + case sheet::DataPilotFieldOrientation_PAGE: + return "axisPage"; + case sheet::DataPilotFieldOrientation_DATA: + return "axisValues"; + case sheet::DataPilotFieldOrientation_HIDDEN: + default: + ; + } + + return ""; +} + +const char* toOOXMLSubtotalType(ScGeneralFunction eFunc) +{ + switch (eFunc) + { + case ScGeneralFunction::SUM: + return "sum"; + case ScGeneralFunction::COUNT: + return "count"; + case ScGeneralFunction::AVERAGE: + return "average"; + case ScGeneralFunction::MAX: + return "max"; + case ScGeneralFunction::MIN: + return "min"; + case ScGeneralFunction::PRODUCT: + return "product"; + case ScGeneralFunction::COUNTNUMS: + return "countNums"; + case ScGeneralFunction::STDEV: + return "stdDev"; + case ScGeneralFunction::STDEVP: + return "stdDevp"; + case ScGeneralFunction::VAR: + return "var"; + case ScGeneralFunction::VARP: + return "varp"; + default: + ; + } + return nullptr; +} + +} + +XclExpXmlPivotCaches::XclExpXmlPivotCaches( const XclExpRoot& rRoot ) : + XclExpRoot(rRoot) {} + +void XclExpXmlPivotCaches::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& pWorkbookStrm = rStrm.GetCurrentStream(); + pWorkbookStrm->startElement(XML_pivotCaches); + + for (size_t i = 0, n = maCaches.size(); i < n; ++i) + { + const Entry& rEntry = maCaches[i]; + + sal_Int32 nCacheId = i + 1; + OUString aRelId; + sax_fastparser::FSHelperPtr pPCStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheDefinition", nCacheId), + XclXmlUtils::GetStreamName(nullptr, "pivotCache/pivotCacheDefinition", nCacheId), + rStrm.GetCurrentStream()->getOutputStream(), + CREATE_XL_CONTENT_TYPE("pivotCacheDefinition"), + CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"), + &aRelId); + + pWorkbookStrm->singleElement(XML_pivotCache, + XML_cacheId, OString::number(nCacheId), + FSNS(XML_r, XML_id), aRelId.toUtf8()); + + rStrm.PushStream(pPCStrm); + SavePivotCacheXml(rStrm, rEntry, nCacheId); + rStrm.PopStream(); + } + + pWorkbookStrm->endElement(XML_pivotCaches); +} + +void XclExpXmlPivotCaches::SetCaches( std::vector<Entry>&& rCaches ) +{ + maCaches = std::move(rCaches); +} + +bool XclExpXmlPivotCaches::HasCaches() const +{ + return !maCaches.empty(); +} + +const XclExpXmlPivotCaches::Entry* XclExpXmlPivotCaches::GetCache( sal_Int32 nCacheId ) const +{ + if (nCacheId <= 0) + // cache ID is 1-based. + return nullptr; + + size_t nPos = nCacheId - 1; + if (nPos >= maCaches.size()) + return nullptr; + + return &maCaches[nPos]; +} + +namespace { +/** + * Create combined date and time string according the requirements of Excel. + * A single point in time can be represented by concatenating a complete date expression, + * the letter T as a delimiter, and a valid time expression. For example, "2007-04-05T14:30". + * + * fSerialDateTime - a number representing the number of days since 1900-Jan-0 (integer portion of the number), + * plus a fractional portion of a 24 hour day (fractional portion of the number). + */ +OUString GetExcelFormattedDate( double fSerialDateTime, const SvNumberFormatter& rFormatter ) +{ + // tdf#125055: properly round the value to seconds when truncating nanoseconds below + constexpr double fHalfSecond = 1 / 86400.0 * 0.5; + css::util::DateTime aUDateTime + = (DateTime(rFormatter.GetNullDate()) + fSerialDateTime + fHalfSecond).GetUNODateTime(); + // We need to reset nanoseconds, to avoid string like: "1982-02-18T16:04:47.999999849" + aUDateTime.NanoSeconds = 0; + OUStringBuffer sBuf; + ::sax::Converter::convertDateTime(sBuf, aUDateTime, nullptr, true); + return sBuf.makeStringAndClear(); +} + +// Excel seems to expect different order of group item values; we need to rearrange elements +// to output "<date1" first, then elements, then ">date2" last. +// Since ScDPItemData::DateFirst is -1, ScDPItemData::DateLast is 10000, and other date group +// items would fit between those in order (like 0 = Jan, 1 = Feb, etc.), we can simply sort +// the items by value. +std::vector<OUString> SortGroupItems(const ScDPCache& rCache, tools::Long nDim) +{ + struct ItemData + { + sal_Int32 nVal; + const ScDPItemData* pData; + }; + std::vector<ItemData> aDataToSort; + ScfInt32Vec aGIIds; + rCache.GetGroupDimMemberIds(nDim, aGIIds); + for (sal_Int32 id : aGIIds) + { + const ScDPItemData* pGIData = rCache.GetItemDataById(nDim, id); + if (pGIData->GetType() == ScDPItemData::GroupValue) + { + auto aGroupVal = pGIData->GetGroupValue(); + aDataToSort.push_back({ aGroupVal.mnValue, pGIData }); + } + } + std::sort(aDataToSort.begin(), aDataToSort.end(), + [](const ItemData& a, const ItemData& b) { return a.nVal < b.nVal; }); + + std::vector<OUString> aSortedResult; + for (const auto& el : aDataToSort) + { + aSortedResult.push_back(rCache.GetFormattedString(nDim, *el.pData, false)); + } + return aSortedResult; +} +} // namespace + +void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entry& rEntry, sal_Int32 nCounter ) +{ + assert(rEntry.mpCache); + const ScDPCache& rCache = *rEntry.mpCache; + + sax_fastparser::FSHelperPtr& pDefStrm = rStrm.GetCurrentStream(); + + OUString aRelId; + sax_fastparser::FSHelperPtr pRecStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheRecords", nCounter), + XclXmlUtils::GetStreamName(nullptr, "pivotCacheRecords", nCounter), + pDefStrm->getOutputStream(), + CREATE_XL_CONTENT_TYPE("pivotCacheRecords"), + CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheRecords"), + &aRelId); + + rStrm.PushStream(pRecStrm); + savePivotCacheRecordsXml(rStrm, rCache); + rStrm.PopStream(); + + pDefStrm->startElement(XML_pivotCacheDefinition, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_r, XML_id), aRelId.toUtf8(), + XML_recordCount, OString::number(rEntry.mpCache->GetDataSize()), + XML_createdVersion, "3"); // MS Excel 2007, tdf#112936: setting version number makes MSO to handle the pivot table differently + + pDefStrm->startElement(XML_cacheSource, XML_type, "worksheet"); + + OUString aSheetName; + GetDoc().GetName(rEntry.maSrcRange.aStart.Tab(), aSheetName); + pDefStrm->singleElement(XML_worksheetSource, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), rEntry.maSrcRange), + XML_sheet, aSheetName.toUtf8()); + + pDefStrm->endElement(XML_cacheSource); + + size_t nCount = rCache.GetFieldCount(); + const size_t nGroupFieldCount = rCache.GetGroupFieldCount(); + pDefStrm->startElement(XML_cacheFields, + XML_count, OString::number(static_cast<tools::Long>(nCount + nGroupFieldCount))); + + auto WriteFieldGroup = [this, &rCache, pDefStrm](size_t i, size_t base) { + const sal_Int32 nDatePart = rCache.GetGroupType(i); + if (!nDatePart) + return; + OString sGroupBy; + switch (nDatePart) + { + case sheet::DataPilotFieldGroupBy::SECONDS: + sGroupBy = "seconds"; + break; + case sheet::DataPilotFieldGroupBy::MINUTES: + sGroupBy = "minutes"; + break; + case sheet::DataPilotFieldGroupBy::HOURS: + sGroupBy = "hours"; + break; + case sheet::DataPilotFieldGroupBy::DAYS: + sGroupBy = "days"; + break; + case sheet::DataPilotFieldGroupBy::MONTHS: + sGroupBy = "months"; + break; + case sheet::DataPilotFieldGroupBy::QUARTERS: + sGroupBy = "quarters"; + break; + case sheet::DataPilotFieldGroupBy::YEARS: + sGroupBy = "years"; + break; + } + + // fieldGroup element + pDefStrm->startElement(XML_fieldGroup, XML_base, OString::number(base)); + + SvNumberFormatter& rFormatter = GetFormatter(); + + // rangePr element + const ScDPNumGroupInfo* pGI = rCache.GetNumGroupInfo(i); + auto pGroupAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + pGroupAttList->add(XML_groupBy, sGroupBy); + // Possible TODO: find out when to write autoStart attribute for years grouping + pGroupAttList->add(XML_startDate, GetExcelFormattedDate(pGI->mfStart, rFormatter).toUtf8()); + pGroupAttList->add(XML_endDate, GetExcelFormattedDate(pGI->mfEnd, rFormatter).toUtf8()); + if (pGI->mfStep) + pGroupAttList->add(XML_groupInterval, OString::number(pGI->mfStep)); + pDefStrm->singleElement(XML_rangePr, pGroupAttList); + + // groupItems element + auto aElemVec = SortGroupItems(rCache, i); + pDefStrm->startElement(XML_groupItems, XML_count, OString::number(aElemVec.size())); + for (const auto& sElem : aElemVec) + { + pDefStrm->singleElement(XML_s, XML_v, sElem.toUtf8()); + } + pDefStrm->endElement(XML_groupItems); + pDefStrm->endElement(XML_fieldGroup); + }; + + for (size_t i = 0; i < nCount; ++i) + { + OUString aName = rCache.GetDimensionName(i); + + pDefStrm->startElement(XML_cacheField, + XML_name, aName.toUtf8(), + XML_numFmtId, OString::number(0)); + + const ScDPCache::ScDPItemDataVec& rFieldItems = rCache.GetDimMemberValues(i); + + std::set<ScDPItemData::Type> aDPTypes; + double fMin = std::numeric_limits<double>::infinity(), fMax = -std::numeric_limits<double>::infinity(); + bool isValueInteger = true; + bool isContainsDate = rCache.IsDateDimension(i); + bool isLongText = false; + for (const auto& rFieldItem : rFieldItems) + { + ScDPItemData::Type eType = rFieldItem.GetType(); + // tdf#123939 : error and string are same for cache; if both are present, keep only one + if (eType == ScDPItemData::Error) + eType = ScDPItemData::String; + aDPTypes.insert(eType); + if (eType == ScDPItemData::Value) + { + double fVal = rFieldItem.GetValue(); + fMin = std::min(fMin, fVal); + fMax = std::max(fMax, fVal); + + // Check if all values are integers + if (isValueInteger && (modf(fVal, &o3tl::temporary(double())) != 0.0)) + { + isValueInteger = false; + } + } + else if (eType == ScDPItemData::String && !isLongText) + { + isLongText = rFieldItem.GetString().getLength() > 255; + } + } + + auto pAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + // TODO In same cases, disable listing of items, as it is done in MS Excel. + // Exporting savePivotCacheRecordsXml method needs to be updated accordingly + //bool bListItems = true; + + std::set<ScDPItemData::Type> aDPTypesWithoutBlank = aDPTypes; + aDPTypesWithoutBlank.erase(ScDPItemData::Empty); + + const bool isContainsString = aDPTypesWithoutBlank.count(ScDPItemData::String) > 0; + const bool isContainsBlank = aDPTypes.count(ScDPItemData::Empty) > 0; + const bool isContainsNumber + = !isContainsDate && aDPTypesWithoutBlank.count(ScDPItemData::Value) > 0; + bool isContainsNonDate = !(isContainsDate && aDPTypesWithoutBlank.size() <= 1); + + // XML_containsSemiMixedTypes possible values: + // 1 - (Default) at least one text value, or can also contain a mix of other data types and blank values, + // or blank values only + // 0 - the field does not have a mix of text and other values + if (!(isContainsString || (aDPTypes.size() > 1) || (isContainsBlank && aDPTypesWithoutBlank.empty()))) + pAttList->add(XML_containsSemiMixedTypes, ToPsz10(false)); + + if (!isContainsNonDate) + pAttList->add(XML_containsNonDate, ToPsz10(false)); + + if (isContainsDate) + pAttList->add(XML_containsDate, ToPsz10(true)); + + // default for containsString field is true, so we are writing only when is false + if (!isContainsString) + pAttList->add(XML_containsString, ToPsz10(false)); + + if (isContainsBlank) + pAttList->add(XML_containsBlank, ToPsz10(true)); + + // XML_containsMixedType possible values: + // 1 - field contains more than one data type + // 0 - (Default) only one data type. The field can still contain blank values (that's why we are using aDPTypesWithoutBlank) + if (aDPTypesWithoutBlank.size() > 1) + pAttList->add(XML_containsMixedTypes, ToPsz10(true)); + + // If field contain mixed types (Date and Numbers), MS Excel is saving only "minDate" and "maxDate" and not "minValue" and "maxValue" + // Example how Excel is saving mixed Date and Numbers: + // <sharedItems containsSemiMixedTypes="0" containsDate="1" containsString="0" containsMixedTypes="1" minDate="1900-01-03T22:26:04" maxDate="1900-01-07T14:02:04" /> + // Example how Excel is saving Dates only: + // <sharedItems containsSemiMixedTypes="0" containsNonDate="0" containsDate="1" containsString="0" minDate="1903-08-24T07:40:48" maxDate="2024-05-23T07:12:00"/> + if (isContainsNumber) + pAttList->add(XML_containsNumber, ToPsz10(true)); + + if (isValueInteger && isContainsNumber) + pAttList->add(XML_containsInteger, ToPsz10(true)); + + + // Number type fields could be mixed with blank types, and it shouldn't be treated as listed items. + // Example: + // <cacheField name="employeeID" numFmtId="0"> + // <sharedItems containsString="0" containsBlank="1" containsNumber="1" containsInteger="1" minValue="35" maxValue="89"/> + // </cacheField> + if (isContainsNumber) + { + pAttList->add(XML_minValue, OString::number(fMin)); + pAttList->add(XML_maxValue, OString::number(fMax)); + } + + if (isContainsDate) + { + pAttList->add(XML_minDate, GetExcelFormattedDate(fMin, GetFormatter()).toUtf8()); + pAttList->add(XML_maxDate, GetExcelFormattedDate(fMax, GetFormatter()).toUtf8()); + } + + //if (bListItems) // see TODO above + { + pAttList->add(XML_count, OString::number(static_cast<tools::Long>(rFieldItems.size()))); + } + + if (isLongText) + { + pAttList->add(XML_longText, ToPsz10(true)); + } + + pDefStrm->startElement(XML_sharedItems, pAttList); + + //if (bListItems) // see TODO above + { + for (const ScDPItemData& rItem : rFieldItems) + { + switch (rItem.GetType()) + { + case ScDPItemData::String: + pDefStrm->singleElement(XML_s, XML_v, rItem.GetString().toUtf8()); + break; + case ScDPItemData::Value: + if (isContainsDate) + { + pDefStrm->singleElement(XML_d, + XML_v, GetExcelFormattedDate(rItem.GetValue(), GetFormatter()).toUtf8()); + } + else + pDefStrm->singleElement(XML_n, + XML_v, OString::number(rItem.GetValue())); + break; + case ScDPItemData::Empty: + pDefStrm->singleElement(XML_m); + break; + case ScDPItemData::Error: + pDefStrm->singleElement(XML_e, + XML_v, rItem.GetString().toUtf8()); + break; + case ScDPItemData::GroupValue: // Should not happen here! + case ScDPItemData::RangeStart: + // TODO : What do we do with these types? + pDefStrm->singleElement(XML_m); + break; + default: + ; + } + } + } + + pDefStrm->endElement(XML_sharedItems); + + WriteFieldGroup(i, i); + + pDefStrm->endElement(XML_cacheField); + } + + ScDPObject* pDPObject + = rCache.GetAllReferences().empty() ? nullptr : *rCache.GetAllReferences().begin(); + + for (size_t i = nCount; pDPObject && i < nCount + nGroupFieldCount; ++i) + { + const OUString aName = pDPObject->GetDimName(i, o3tl::temporary(bool())); + // tdf#126748: DPObject might not reference all group fields, when there are several + // DPObjects referencing this cache. Trying to get a dimension data for a field not used + // in a given DPObject will give nullptr, and dereferencing it then will crash. To avoid + // the crash, until there's a correct method to find the names of group fields in cache, + // just skip the fields, creating bad cache data, which is of course a temporary hack. + // TODO: reimplement the whole block to get the names from another source, not from first + // cache reference. + if (aName.isEmpty()) + break; + + ScDPSaveData* pSaveData = pDPObject->GetSaveData(); + assert(pSaveData); + + const ScDPSaveGroupDimension* pDim = pSaveData->GetDimensionData()->GetNamedGroupDim(aName); + assert(pDim); + + const SCCOL nBase = rCache.GetDimensionIndex(pDim->GetSourceDimName()); + assert(nBase >= 0); + + pDefStrm->startElement(XML_cacheField, XML_name, aName.toUtf8(), XML_numFmtId, + OString::number(0), XML_databaseField, ToPsz10(false)); + WriteFieldGroup(i, nBase); + pDefStrm->endElement(XML_cacheField); + } + + pDefStrm->endElement(XML_cacheFields); + + pDefStrm->endElement(XML_pivotCacheDefinition); +} + +XclExpXmlPivotTableManager::XclExpXmlPivotTableManager( const XclExpRoot& rRoot ) : + XclExpRoot(rRoot), maCaches(rRoot) {} + +void XclExpXmlPivotTableManager::Initialize() +{ + ScDocument& rDoc = GetDoc(); + if (!rDoc.HasPivotTable()) + // No pivot table to export. + return; + + ScDPCollection* pDPColl = rDoc.GetDPCollection(); + if (!pDPColl) + return; + + // Update caches from DPObject + for (size_t i = 0; i < pDPColl->GetCount(); ++i) + { + ScDPObject& rDPObj = (*pDPColl)[i]; + rDPObj.SyncAllDimensionMembers(); + (void)rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE); + } + + // Go through the caches first. + + std::vector<XclExpXmlPivotCaches::Entry> aCaches; + const ScDPCollection::SheetCaches& rSheetCaches = pDPColl->GetSheetCaches(); + const std::vector<ScRange>& rRanges = rSheetCaches.getAllRanges(); + for (const auto & rRange : rRanges) + { + const ScDPCache* pCache = rSheetCaches.getExistingCache(rRange); + if (!pCache) + continue; + + // Get all pivot objects that reference this cache, and set up an + // object to cache ID mapping. + const ScDPCache::ScDPObjectSet& rRefs = pCache->GetAllReferences(); + for (const auto& rRef : rRefs) + maCacheIdMap.emplace(rRef, aCaches.size()+1); + + XclExpXmlPivotCaches::Entry aEntry; + aEntry.mpCache = pCache; + aEntry.maSrcRange = rRange; + aCaches.push_back(aEntry); // Cache ID equals position + 1. + } + + // TODO : Handle name and database caches as well. + + for (size_t i = 0, n = pDPColl->GetCount(); i < n; ++i) + { + const ScDPObject& rDPObj = (*pDPColl)[i]; + + // Get the cache ID for this pivot table. + CacheIdMapType::iterator itCache = maCacheIdMap.find(&rDPObj); + if (itCache == maCacheIdMap.end()) + // No cache ID found. Something is wrong here... + continue; + + sal_Int32 nCacheId = itCache->second; + SCTAB nTab = rDPObj.GetOutRange().aStart.Tab(); + + TablesType::iterator it = m_Tables.find(nTab); + if (it == m_Tables.end()) + { + // Insert a new instance for this sheet index. + std::pair<TablesType::iterator, bool> r = + m_Tables.insert(std::make_pair(nTab, std::make_unique<XclExpXmlPivotTables>(GetRoot(), maCaches))); + it = r.first; + } + + XclExpXmlPivotTables *const p = it->second.get(); + p->AppendTable(&rDPObj, nCacheId, i+1); + } + + maCaches.SetCaches(std::move(aCaches)); +} + +XclExpXmlPivotCaches& XclExpXmlPivotTableManager::GetCaches() +{ + return maCaches; +} + +XclExpXmlPivotTables* XclExpXmlPivotTableManager::GetTablesBySheet( SCTAB nTab ) +{ + TablesType::iterator const it = m_Tables.find(nTab); + return it == m_Tables.end() ? nullptr : it->second.get(); +} + +XclExpXmlPivotTables::Entry::Entry( const ScDPObject* pTable, sal_Int32 nCacheId, sal_Int32 nPivotId ) : + mpTable(pTable), mnCacheId(nCacheId), mnPivotId(nPivotId) {} + +XclExpXmlPivotTables::XclExpXmlPivotTables( const XclExpRoot& rRoot, const XclExpXmlPivotCaches& rCaches ) : + XclExpRoot(rRoot), mrCaches(rCaches) {} + +void XclExpXmlPivotTables::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& pWSStrm = rStrm.GetCurrentStream(); // worksheet stream + + for (const auto& rTable : maTables) + { + const ScDPObject& rObj = *rTable.mpTable; + sal_Int32 nCacheId = rTable.mnCacheId; + sal_Int32 nPivotId = rTable.mnPivotId; + + sax_fastparser::FSHelperPtr pPivotStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/pivotTables/", "pivotTable", nPivotId), + XclXmlUtils::GetStreamName(nullptr, "../pivotTables/pivotTable", nPivotId), + pWSStrm->getOutputStream(), + CREATE_XL_CONTENT_TYPE("pivotTable"), + CREATE_OFFICEDOC_RELATION_TYPE("pivotTable")); + + rStrm.PushStream(pPivotStrm); + SavePivotTableXml(rStrm, rObj, nCacheId); + rStrm.PopStream(); + } +} + +namespace { + +struct DataField +{ + tools::Long mnPos; // field index in pivot cache. + const ScDPSaveDimension* mpDim; + + DataField( tools::Long nPos, const ScDPSaveDimension* pDim ) : mnPos(nPos), mpDim(pDim) {} +}; + +/** Returns an OOXML subtotal function name string. See ECMA-376-1:2016 18.18.43 */ +OString GetSubtotalFuncName(ScGeneralFunction eFunc) +{ + switch (eFunc) + { + case ScGeneralFunction::SUM: return "sum"; + case ScGeneralFunction::COUNT: return "count"; + case ScGeneralFunction::AVERAGE: return "avg"; + case ScGeneralFunction::MAX: return "max"; + case ScGeneralFunction::MIN: return "min"; + case ScGeneralFunction::PRODUCT: return "product"; + case ScGeneralFunction::COUNTNUMS: return "countA"; + case ScGeneralFunction::STDEV: return "stdDev"; + case ScGeneralFunction::STDEVP: return "stdDevP"; + case ScGeneralFunction::VAR: return "var"; + case ScGeneralFunction::VARP: return "varP"; + default:; + } + return "default"; +} + +sal_Int32 GetSubtotalAttrToken(ScGeneralFunction eFunc) +{ + switch (eFunc) + { + case ScGeneralFunction::SUM: return XML_sumSubtotal; + case ScGeneralFunction::COUNT: return XML_countSubtotal; + case ScGeneralFunction::AVERAGE: return XML_avgSubtotal; + case ScGeneralFunction::MAX: return XML_maxSubtotal; + case ScGeneralFunction::MIN: return XML_minSubtotal; + case ScGeneralFunction::PRODUCT: return XML_productSubtotal; + case ScGeneralFunction::COUNTNUMS: return XML_countASubtotal; + case ScGeneralFunction::STDEV: return XML_stdDevSubtotal; + case ScGeneralFunction::STDEVP: return XML_stdDevPSubtotal; + case ScGeneralFunction::VAR: return XML_varSubtotal; + case ScGeneralFunction::VARP: return XML_varPSubtotal; + default:; + } + return XML_defaultSubtotal; +} + +// An item is expected to contain sequences of css::xml::FastAttribute and css::xml::Attribute +void WriteGrabBagItemToStream(XclExpXmlStream& rStrm, sal_Int32 tokenId, const css::uno::Any& rItem) +{ + css::uno::Sequence<css::uno::Any> aSeqs; + if(!(rItem >>= aSeqs)) + return; + + auto& pStrm = rStrm.GetCurrentStream(); + pStrm->write("<")->writeId(tokenId); + + css::uno::Sequence<css::xml::FastAttribute> aFastSeq; + css::uno::Sequence<css::xml::Attribute> aUnkSeq; + for (const auto& a : std::as_const(aSeqs)) + { + if (a >>= aFastSeq) + { + for (const auto& rAttr : std::as_const(aFastSeq)) + rStrm.WriteAttributes(rAttr.Token, rAttr.Value); + } + else if (a >>= aUnkSeq) + { + for (const auto& rAttr : std::as_const(aUnkSeq)) + pStrm->write(" ") + ->write(rAttr.Name) + ->write("=\"") + ->writeEscaped(rAttr.Value) + ->write("\""); + } + } + + pStrm->write("/>"); +} +} + +void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream& rStrm, const ScDPObject& rDPObj, sal_Int32 nCacheId ) +{ + typedef std::unordered_map<OUString, long> NameToIdMapType; + + const XclExpXmlPivotCaches::Entry* pCacheEntry = mrCaches.GetCache(nCacheId); + if (!pCacheEntry) + // Something is horribly wrong. Check your logic. + return; + + const ScDPCache& rCache = *pCacheEntry->mpCache; + + const ScDPSaveData& rSaveData = *rDPObj.GetSaveData(); + + size_t nFieldCount = rCache.GetFieldCount() + rCache.GetGroupFieldCount(); + std::vector<const ScDPSaveDimension*> aCachedDims; + NameToIdMapType aNameToIdMap; + + aCachedDims.reserve(nFieldCount); + for (size_t i = 0; i < nFieldCount; ++i) + { + OUString aName = const_cast<ScDPObject&>(rDPObj).GetDimName(i, o3tl::temporary(bool())); + aNameToIdMap.emplace(aName, aCachedDims.size()); + const ScDPSaveDimension* pDim = rSaveData.GetExistingDimensionByName(aName); + aCachedDims.push_back(pDim); + } + + std::vector<tools::Long> aRowFields; + std::vector<tools::Long> aColFields; + std::vector<tools::Long> aPageFields; + std::vector<DataField> aDataFields; + + tools::Long nDataDimCount = rSaveData.GetDataDimensionCount(); + // Use dimensions in the save data to get their correct ordering. + // Dimension order here is significant as they specify the order of + // appearance in each axis. + const ScDPSaveData::DimsType& rDims = rSaveData.GetDimensions(); + bool bTabularMode = false; + for (const auto & i : rDims) + { + const ScDPSaveDimension& rDim = *i; + + tools::Long nPos = -1; // position in cache + if (rDim.IsDataLayout()) + nPos = -2; // Excel uses an index of -2 to indicate a data layout field. + else + { + OUString aSrcName = ScDPUtil::getSourceDimensionName(rDim.GetName()); + NameToIdMapType::iterator it = aNameToIdMap.find(aSrcName); + if (it != aNameToIdMap.end()) + nPos = it->second; + + if (nPos == -1) + continue; + + if (!aCachedDims[nPos]) + continue; + } + + sheet::DataPilotFieldOrientation eOrient = rDim.GetOrientation(); + + switch (eOrient) + { + case sheet::DataPilotFieldOrientation_COLUMN: + if (nPos == -2 && nDataDimCount <= 1) + break; + aColFields.push_back(nPos); + break; + case sheet::DataPilotFieldOrientation_ROW: + aRowFields.push_back(nPos); + break; + case sheet::DataPilotFieldOrientation_PAGE: + aPageFields.push_back(nPos); + break; + case sheet::DataPilotFieldOrientation_DATA: + aDataFields.emplace_back(nPos, &rDim); + break; + case sheet::DataPilotFieldOrientation_HIDDEN: + default: + ; + } + if(rDim.GetLayoutInfo()) + bTabularMode |= (rDim.GetLayoutInfo()->LayoutMode == sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT); + } + + sax_fastparser::FSHelperPtr& pPivotStrm = rStrm.GetCurrentStream(); + pPivotStrm->startElement(XML_pivotTableDefinition, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + XML_name, rDPObj.GetName().toUtf8(), + XML_cacheId, OString::number(nCacheId), + XML_applyNumberFormats, ToPsz10(false), + XML_applyBorderFormats, ToPsz10(false), + XML_applyFontFormats, ToPsz10(false), + XML_applyPatternFormats, ToPsz10(false), + XML_applyAlignmentFormats, ToPsz10(false), + XML_applyWidthHeightFormats, ToPsz10(false), + XML_dataCaption, "Values", + XML_useAutoFormatting, ToPsz10(false), + XML_itemPrintTitles, ToPsz10(true), + XML_indent, ToPsz10(false), + XML_outline, ToPsz10(!bTabularMode), + XML_outlineData, ToPsz10(!bTabularMode), + XML_compact, ToPsz10(false), + XML_compactData, ToPsz10(false)); + + // NB: Excel's range does not include page field area (if any). + ScRange aOutRange = rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE); + + sal_Int32 nFirstHeaderRow = rDPObj.GetHeaderLayout() ? 2 : 1; + sal_Int32 nFirstDataRow = 2; + sal_Int32 nFirstDataCol = 1; + ScRange aResRange = rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::RESULT); + + if (!aOutRange.IsValid()) + aOutRange = rDPObj.GetOutRange(); + + if (aOutRange.IsValid() && aResRange.IsValid()) + { + nFirstDataRow = aResRange.aStart.Row() - aOutRange.aStart.Row(); + nFirstDataCol = aResRange.aStart.Col() - aOutRange.aStart.Col(); + } + + pPivotStrm->write("<")->writeId(XML_location); + rStrm.WriteAttributes(XML_ref, + XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aOutRange), + XML_firstHeaderRow, OUString::number(nFirstHeaderRow), + XML_firstDataRow, OUString::number(nFirstDataRow), + XML_firstDataCol, OUString::number(nFirstDataCol)); + + if (!aPageFields.empty()) + { + rStrm.WriteAttributes(XML_rowPageCount, OUString::number(static_cast<tools::Long>(aPageFields.size()))); + rStrm.WriteAttributes(XML_colPageCount, OUString::number(1)); + } + + pPivotStrm->write("/>"); + + // <pivotFields> - It must contain all fields in the pivot cache even if + // only some of them are used in the pivot table. The order must be as + // they appear in the cache. + + pPivotStrm->startElement(XML_pivotFields, + XML_count, OString::number(static_cast<tools::Long>(aCachedDims.size()))); + + for (size_t i = 0; i < nFieldCount; ++i) + { + const ScDPSaveDimension* pDim = aCachedDims[i]; + if (!pDim) + { + pPivotStrm->singleElement(XML_pivotField, + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false)); + continue; + } + + bool bDimInTabularMode = false; + if(pDim->GetLayoutInfo()) + bDimInTabularMode = (pDim->GetLayoutInfo()->LayoutMode == sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT); + + sheet::DataPilotFieldOrientation eOrient = pDim->GetOrientation(); + + if (eOrient == sheet::DataPilotFieldOrientation_HIDDEN) + { + if(bDimInTabularMode) + { + pPivotStrm->singleElement(XML_pivotField, + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false), + XML_outline, ToPsz10(false)); + } + else + { + pPivotStrm->singleElement(XML_pivotField, + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false)); + } + continue; + } + + if (eOrient == sheet::DataPilotFieldOrientation_DATA) + { + if(bDimInTabularMode) + { + pPivotStrm->singleElement(XML_pivotField, + XML_dataField, ToPsz10(true), + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false), + XML_outline, ToPsz10(false)); + } + else + { + pPivotStrm->singleElement(XML_pivotField, + XML_dataField, ToPsz10(true), + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false)); + } + continue; + } + + // Dump field items. + std::vector<ScDPLabelData::Member> aMembers; + { + // We need to get the members in actual order, getting which requires non-const reference here + auto& dpo = const_cast<ScDPObject&>(rDPObj); + dpo.GetMembers(i, dpo.GetUsedHierarchy(i), aMembers); + } + + std::vector<OUString> aCacheFieldItems; + if (i < rCache.GetFieldCount() && !rCache.GetGroupType(i)) + { + for (const auto& it : rCache.GetDimMemberValues(i)) + { + OUString sFormattedName; + if (it.HasStringData() || it.IsEmpty()) + sFormattedName = it.GetString(); + else + sFormattedName = const_cast<ScDPObject&>(rDPObj).GetFormattedString( + pDim->GetName(), it.GetValue()); + aCacheFieldItems.push_back(sFormattedName); + } + } + else + { + aCacheFieldItems = SortGroupItems(rCache, i); + } + // The pair contains the member index in cache and if it is hidden + std::vector< std::pair<size_t, bool> > aMemberSequence; + std::set<size_t> aUsedCachePositions; + for (const auto & rMember : aMembers) + { + auto it = std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), rMember.maName); + if (it != aCacheFieldItems.end()) + { + size_t nCachePos = static_cast<size_t>(std::distance(aCacheFieldItems.begin(), it)); + auto aInserted = aUsedCachePositions.insert(nCachePos); + if (aInserted.second) + aMemberSequence.emplace_back(std::make_pair(nCachePos, !rMember.mbVisible)); + } + } + // Now add all remaining cache items as hidden + for (size_t nItem = 0; nItem < aCacheFieldItems.size(); ++nItem) + { + if (aUsedCachePositions.find(nItem) == aUsedCachePositions.end()) + aMemberSequence.emplace_back(nItem, true); + } + + // tdf#125086: check if this field *also* appears in Data region + bool bAppearsInData = false; + { + OUString aSrcName = ScDPUtil::getSourceDimensionName(pDim->GetName()); + const auto it = std::find_if( + aDataFields.begin(), aDataFields.end(), [&aSrcName](const DataField& rDataField) { + OUString aThisName + = ScDPUtil::getSourceDimensionName(rDataField.mpDim->GetName()); + return aThisName == aSrcName; + }); + if (it != aDataFields.end()) + bAppearsInData = true; + } + + auto pAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + pAttList->add(XML_axis, toOOXMLAxisType(eOrient)); + if (bAppearsInData) + pAttList->add(XML_dataField, ToPsz10(true)); + pAttList->add(XML_compact, ToPsz10(false)); + pAttList->add(XML_showAll, ToPsz10(false)); + + tools::Long nSubTotalCount = pDim->GetSubTotalsCount(); + std::vector<OString> aSubtotalSequence; + bool bHasDefaultSubtotal = false; + for (tools::Long nSubTotal = 0; nSubTotal < nSubTotalCount; ++nSubTotal) + { + ScGeneralFunction eFunc = pDim->GetSubTotalFunc(nSubTotal); + aSubtotalSequence.push_back(GetSubtotalFuncName(eFunc)); + sal_Int32 nAttToken = GetSubtotalAttrToken(eFunc); + if (nAttToken == XML_defaultSubtotal) + bHasDefaultSubtotal = true; + else if (!pAttList->hasAttribute(nAttToken)) + pAttList->add(nAttToken, ToPsz10(true)); + } + // XML_defaultSubtotal is true by default; only write it if it's false + if (!bHasDefaultSubtotal) + pAttList->add(XML_defaultSubtotal, ToPsz10(false)); + + if(bDimInTabularMode) + pAttList->add( XML_outline, ToPsz10(false)); + pPivotStrm->startElement(XML_pivotField, pAttList); + + pPivotStrm->startElement(XML_items, + XML_count, OString::number(static_cast<tools::Long>(aMemberSequence.size() + aSubtotalSequence.size()))); + + for (const auto & nMember : aMemberSequence) + { + auto pItemAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + if (nMember.second) + pItemAttList->add(XML_h, ToPsz10(true)); + pItemAttList->add(XML_x, OString::number(static_cast<tools::Long>(nMember.first))); + pPivotStrm->singleElement(XML_item, pItemAttList); + } + + for (const OString& sSubtotal : aSubtotalSequence) + { + pPivotStrm->singleElement(XML_item, XML_t, sSubtotal); + } + + pPivotStrm->endElement(XML_items); + pPivotStrm->endElement(XML_pivotField); + } + + pPivotStrm->endElement(XML_pivotFields); + + // <rowFields> + + if (!aRowFields.empty()) + { + pPivotStrm->startElement(XML_rowFields, + XML_count, OString::number(static_cast<tools::Long>(aRowFields.size()))); + + for (const auto& rRowField : aRowFields) + { + pPivotStrm->singleElement(XML_field, XML_x, OString::number(rRowField)); + } + + pPivotStrm->endElement(XML_rowFields); + } + + // <rowItems> + + // <colFields> + + if (!aColFields.empty()) + { + pPivotStrm->startElement(XML_colFields, + XML_count, OString::number(static_cast<tools::Long>(aColFields.size()))); + + for (const auto& rColField : aColFields) + { + pPivotStrm->singleElement(XML_field, XML_x, OString::number(rColField)); + } + + pPivotStrm->endElement(XML_colFields); + } + + // <colItems> + + // <pageFields> + + if (!aPageFields.empty()) + { + pPivotStrm->startElement(XML_pageFields, + XML_count, OString::number(static_cast<tools::Long>(aPageFields.size()))); + + for (const auto& rPageField : aPageFields) + { + pPivotStrm->singleElement(XML_pageField, + XML_fld, OString::number(rPageField), + XML_hier, OString::number(-1)); // TODO : handle this correctly. + } + + pPivotStrm->endElement(XML_pageFields); + } + + // <dataFields> + + if (!aDataFields.empty()) + { + css::uno::Reference<css::container::XNameAccess> xDimsByName; + if (auto xDimSupplier = const_cast<ScDPObject&>(rDPObj).GetSource()) + xDimsByName = xDimSupplier->getDimensions(); + + pPivotStrm->startElement(XML_dataFields, + XML_count, OString::number(static_cast<tools::Long>(aDataFields.size()))); + + for (const auto& rDataField : aDataFields) + { + tools::Long nDimIdx = rDataField.mnPos; + assert(nDimIdx == -2 || aCachedDims[nDimIdx]); // the loop above should have screened for NULL's, skip check for -2 "data field" + const ScDPSaveDimension& rDim = *rDataField.mpDim; + std::optional<OUString> pName = rDim.GetLayoutName(); + // tdf#124651: despite being optional in CT_DataField according to ECMA-376 Part 1, + // Excel (at least 2016) seems to insist on the presence of "name" attribute in + // dataField element. + // tdf#124881: try to create a meaningful name; don't use empty string. + if (!pName) + pName = ScDPUtil::getDisplayedMeasureName( + rDim.GetName(), ScDPUtil::toSubTotalFunc(rDim.GetFunction())); + auto pItemAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + pItemAttList->add(XML_name, pName->toUtf8()); + pItemAttList->add(XML_fld, OString::number(nDimIdx)); + const char* pSubtotal = toOOXMLSubtotalType(rDim.GetFunction()); + if (pSubtotal) + pItemAttList->add(XML_subtotal, pSubtotal); + if (xDimsByName) + { + try + { + css::uno::Reference<css::beans::XPropertySet> xDimProps( + xDimsByName->getByName(rDim.GetName()), uno::UNO_QUERY_THROW); + css::uno::Any aVal = xDimProps->getPropertyValue(SC_UNONAME_NUMFMT); + sal_uInt32 nScNumFmt = aVal.get<sal_uInt32>(); + sal_uInt16 nXclNumFmt = GetRoot().GetNumFmtBuffer().Insert(nScNumFmt); + pItemAttList->add(XML_numFmtId, OString::number(nXclNumFmt)); + } + catch (uno::Exception&) + { + SAL_WARN("sc.filter", + "Couldn't get number format for data field " << rDim.GetName()); + // Just skip exporting number format + } + } + pPivotStrm->singleElement(XML_dataField, pItemAttList); + } + + pPivotStrm->endElement(XML_dataFields); + } + + // Now add style info (use grab bag, or just a set which is default on Excel 2007 through 2016) + if (const auto [bHas, aVal] = rDPObj.GetInteropGrabBagValue("pivotTableStyleInfo"); bHas) + WriteGrabBagItemToStream(rStrm, XML_pivotTableStyleInfo, aVal); + else + pPivotStrm->singleElement(XML_pivotTableStyleInfo, XML_name, "PivotStyleLight16", + XML_showRowHeaders, "1", XML_showColHeaders, "1", + XML_showRowStripes, "0", XML_showColStripes, "0", + XML_showLastColumn, "1"); + + OUString aBuf = "../pivotCache/pivotCacheDefinition" + + OUString::number(nCacheId) + + ".xml"; + + rStrm.addRelation( + pPivotStrm->getOutputStream(), + CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"), + aBuf); + + pPivotStrm->endElement(XML_pivotTableDefinition); +} + +void XclExpXmlPivotTables::AppendTable( const ScDPObject* pTable, sal_Int32 nCacheId, sal_Int32 nPivotId ) +{ + maTables.emplace_back(pTable, nCacheId, nPivotId); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xerecord.cxx b/sc/source/filter/excel/xerecord.cxx new file mode 100644 index 000000000..8778d75b5 --- /dev/null +++ b/sc/source/filter/excel/xerecord.cxx @@ -0,0 +1,264 @@ +/* -*- 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 <xerecord.hxx> +#include <xeroot.hxx> +#include <xltools.hxx> + +#include <oox/token/tokens.hxx> +#include <oox/export/utils.hxx> +#include <osl/diagnose.h> + +using namespace ::oox; + +// Base classes to export Excel records ======================================= + +XclExpRecordBase::~XclExpRecordBase() +{ +} + +void XclExpRecordBase::Save( XclExpStream& /*rStrm*/ ) +{ +} + +void XclExpRecordBase::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ +} + +XclExpDelegatingRecord::XclExpDelegatingRecord( XclExpRecordBase* pRecord ) : + mpRecord( pRecord ) +{ +} + +XclExpDelegatingRecord::~XclExpDelegatingRecord() +{ + // Do Nothing; we use Delegating Record for other objects we "know" will + // survive... +} + +void XclExpDelegatingRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mpRecord ) + mpRecord->SaveXml( rStrm ); +} + +XclExpXmlElementRecord::XclExpXmlElementRecord(sal_Int32 const nElement) + : mnElement( nElement ) +{ +} + +XclExpXmlElementRecord::~XclExpXmlElementRecord() +{ +} + +XclExpXmlStartElementRecord::XclExpXmlStartElementRecord(sal_Int32 const nElement) + : XclExpXmlElementRecord(nElement) +{ +} + +XclExpXmlStartElementRecord::~XclExpXmlStartElementRecord() +{ +} + +void XclExpXmlStartElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStream = rStrm.GetCurrentStream(); + // TODO: no generic way to add attributes here, but it appears to + // not be needed yet + rStream->startElement(mnElement); +} + +XclExpXmlEndElementRecord::XclExpXmlEndElementRecord( sal_Int32 nElement ) + : XclExpXmlElementRecord( nElement ) +{ +} + +XclExpXmlEndElementRecord::~XclExpXmlEndElementRecord() +{ +} + +void XclExpXmlEndElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->endElement( mnElement ); +} + +XclExpXmlStartSingleElementRecord::XclExpXmlStartSingleElementRecord( + sal_Int32 const nElement) + : XclExpXmlElementRecord( nElement ) +{ +} + +XclExpXmlStartSingleElementRecord::~XclExpXmlStartSingleElementRecord() +{ +} + +void XclExpXmlStartSingleElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStream = rStrm.GetCurrentStream(); + rStream->write( "<" )->writeId( mnElement ); +} + +XclExpXmlEndSingleElementRecord::XclExpXmlEndSingleElementRecord() +{ +} + +XclExpXmlEndSingleElementRecord::~XclExpXmlEndSingleElementRecord() +{ +} + +void XclExpXmlEndSingleElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->write( "/>" ); +} + +XclExpRecord::XclExpRecord( sal_uInt16 nRecId, std::size_t nRecSize ) : + mnRecSize( nRecSize ), + mnRecId( nRecId ) +{ +} + +XclExpRecord::~XclExpRecord() +{ +} + +void XclExpRecord::SetRecHeader( sal_uInt16 nRecId, std::size_t nRecSize ) +{ + SetRecId( nRecId ); + SetRecSize( nRecSize ); +} + +void XclExpRecord::WriteBody( XclExpStream& /*rStrm*/ ) +{ +} + +void XclExpRecord::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( mnRecId != EXC_ID_UNKNOWN, "XclExpRecord::Save - record ID uninitialized" ); + rStrm.StartRecord( mnRecId, mnRecSize ); + WriteBody( rStrm ); + rStrm.EndRecord(); +} + +template<> +void XclExpValueRecord<double>::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mnAttribute == -1 ) + return; + rStrm.WriteAttributes(mnAttribute, OUString::number(maValue)); +} + +void XclExpBoolRecord::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt16 >( mbValue ? 1 : 0 ); +} + +void XclExpBoolRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mnAttribute == -1 ) + return; + + rStrm.WriteAttributes( + // HACK: HIDEOBJ (excdoc.cxx) should be its own object to handle XML_showObjects + mnAttribute, mnAttribute == XML_showObjects ? "all" : ToPsz( mbValue )); +} + +XclExpDummyRecord::XclExpDummyRecord( sal_uInt16 nRecId, const void* pRecData, std::size_t nRecSize ) : + XclExpRecord( nRecId ) +{ + SetData( pRecData, nRecSize ); +} + +void XclExpDummyRecord::SetData( const void* pRecData, std::size_t nRecSize ) +{ + mpData = pRecData; + SetRecSize( pRecData ? nRecSize : 0 ); +} + +void XclExpDummyRecord::WriteBody( XclExpStream& rStrm ) +{ + rStrm.Write( mpData, GetRecSize() ); +} + +// Future records ============================================================= + +XclExpFutureRecord::XclExpFutureRecord( XclFutureRecType eRecType, sal_uInt16 nRecId, std::size_t nRecSize ) : + XclExpRecord( nRecId, nRecSize ), + meRecType( eRecType ) +{ +} + +void XclExpFutureRecord::Save( XclExpStream& rStrm ) +{ + rStrm.StartRecord( GetRecId(), GetRecSize() + ((meRecType == EXC_FUTUREREC_UNUSEDREF) ? 12 : 4) ); + rStrm << GetRecId() << sal_uInt16( 0 ); + if( meRecType == EXC_FUTUREREC_UNUSEDREF ) + rStrm.WriteZeroBytes( 8 ); + WriteBody( rStrm ); + rStrm.EndRecord(); +} + +XclExpSubStream::XclExpSubStream( sal_uInt16 nSubStrmType ) : + mnSubStrmType( nSubStrmType ) +{ +} + +void XclExpSubStream::Save( XclExpStream& rStrm ) +{ + // BOF record + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + rStrm.StartRecord( EXC_ID2_BOF, 4 ); + rStrm << sal_uInt16( 7 ) << mnSubStrmType; + rStrm.EndRecord(); + break; + case EXC_BIFF3: + rStrm.StartRecord( EXC_ID3_BOF, 6 ); + rStrm << sal_uInt16( 0 ) << mnSubStrmType << sal_uInt16( 2104 ); + rStrm.EndRecord(); + break; + case EXC_BIFF4: + rStrm.StartRecord( EXC_ID4_BOF, 6 ); + rStrm << sal_uInt16( 0 ) << mnSubStrmType << sal_uInt16( 1705 ); + rStrm.EndRecord(); + break; + case EXC_BIFF5: + rStrm.StartRecord( EXC_ID5_BOF, 8 ); + rStrm << EXC_BOF_BIFF5 << mnSubStrmType << sal_uInt16( 4915 ) << sal_uInt16( 1994 ); + rStrm.EndRecord(); + break; + case EXC_BIFF8: + rStrm.StartRecord( EXC_ID5_BOF, 16 ); + rStrm << EXC_BOF_BIFF8 << mnSubStrmType << sal_uInt16( 3612 ) << sal_uInt16( 1996 ); + rStrm << sal_uInt32( 1 ) << sal_uInt32( 6 ); + rStrm.EndRecord(); + break; + default: + DBG_ERROR_BIFF(); + } + + // substream records + XclExpRecordList<>::Save( rStrm ); + + // EOF record + rStrm.StartRecord( EXC_ID_EOF, 0 ); + rStrm.EndRecord(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeroot.cxx b/sc/source/filter/excel/xeroot.cxx new file mode 100644 index 000000000..3bb35d301 --- /dev/null +++ b/sc/source/filter/excel/xeroot.cxx @@ -0,0 +1,360 @@ +/* -*- 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 <officecfg/Office/Common.hxx> +#include <rtl/random.h> +#include <sal/log.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <xecontent.hxx> +#include <xeescher.hxx> +#include <xeformula.hxx> +#include <xehelper.hxx> +#include <xelink.hxx> +#include <xename.hxx> +#include <xepivot.hxx> +#include <xestyle.hxx> +#include <xeroot.hxx> +#include <xepivotxml.hxx> +#include <xedbdata.hxx> +#include <xlcontent.hxx> +#include <xlname.hxx> +#include <xllink.hxx> + +#include <excrecds.hxx> +#include <tabprotection.hxx> +#include <document.hxx> + +#include <formulabase.hxx> +#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +using namespace ::com::sun::star; + +// Global data ================================================================ + +XclExpRootData::XclExpRootData( XclBiff eBiff, SfxMedium& rMedium, + const tools::SvRef<SotStorage>& xRootStrg, ScDocument& rDoc, rtl_TextEncoding eTextEnc ) : + XclRootData( eBiff, rMedium, xRootStrg, rDoc, eTextEnc, true ) +{ + mbRelUrl = mrMedium.IsRemote() + ? officecfg::Office::Common::Save::URL::Internet::get() + : officecfg::Office::Common::Save::URL::FileSystem::get(); + maStringBuf.setLength(0); +} + +XclExpRootData::~XclExpRootData() +{ +} + +XclExpRoot::XclExpRoot( XclExpRootData& rExpRootData ) : + XclRoot( rExpRootData ), + mrExpData( rExpRootData ) +{ +} + +XclExpTabInfo& XclExpRoot::GetTabInfo() const +{ + OSL_ENSURE( mrExpData.mxTabInfo, "XclExpRoot::GetTabInfo - missing object (wrong BIFF?)" ); + return *mrExpData.mxTabInfo; +} + +XclExpAddressConverter& XclExpRoot::GetAddressConverter() const +{ + OSL_ENSURE( mrExpData.mxAddrConv, "XclExpRoot::GetAddressConverter - missing object (wrong BIFF?)" ); + return *mrExpData.mxAddrConv; +} + +XclExpFormulaCompiler& XclExpRoot::GetFormulaCompiler() const +{ + OSL_ENSURE( mrExpData.mxFmlaComp, "XclExpRoot::GetFormulaCompiler - missing object (wrong BIFF?)" ); + return *mrExpData.mxFmlaComp; +} + +XclExpProgressBar& XclExpRoot::GetProgressBar() const +{ + OSL_ENSURE( mrExpData.mxProgress, "XclExpRoot::GetProgressBar - missing object (wrong BIFF?)" ); + return *mrExpData.mxProgress; +} + +XclExpSst& XclExpRoot::GetSst() const +{ + OSL_ENSURE( mrExpData.mxSst, "XclExpRoot::GetSst - missing object (wrong BIFF?)" ); + return *mrExpData.mxSst; +} + +XclExpPalette& XclExpRoot::GetPalette() const +{ + OSL_ENSURE( mrExpData.mxPalette, "XclExpRoot::GetPalette - missing object (wrong BIFF?)" ); + return *mrExpData.mxPalette; +} + +XclExpFontBuffer& XclExpRoot::GetFontBuffer() const +{ + OSL_ENSURE( mrExpData.mxFontBfr, "XclExpRoot::GetFontBuffer - missing object (wrong BIFF?)" ); + return *mrExpData.mxFontBfr; +} + +XclExpNumFmtBuffer& XclExpRoot::GetNumFmtBuffer() const +{ + OSL_ENSURE( mrExpData.mxNumFmtBfr, "XclExpRoot::GetNumFmtBuffer - missing object (wrong BIFF?)" ); + return *mrExpData.mxNumFmtBfr; +} + +XclExpXFBuffer& XclExpRoot::GetXFBuffer() const +{ + OSL_ENSURE( mrExpData.mxXFBfr, "XclExpRoot::GetXFBuffer - missing object (wrong BIFF?)" ); + return *mrExpData.mxXFBfr; +} + +XclExpLinkManager& XclExpRoot::GetGlobalLinkManager() const +{ + OSL_ENSURE( mrExpData.mxGlobLinkMgr, "XclExpRoot::GetGlobalLinkManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxGlobLinkMgr; +} + +XclExpLinkManager& XclExpRoot::GetLocalLinkManager() const +{ + OSL_ENSURE( GetLocalLinkMgrRef(), "XclExpRoot::GetLocalLinkManager - missing object (wrong BIFF?)" ); + return *GetLocalLinkMgrRef(); +} + +XclExpNameManager& XclExpRoot::GetNameManager() const +{ + OSL_ENSURE( mrExpData.mxNameMgr, "XclExpRoot::GetNameManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxNameMgr; +} + +XclExpObjectManager& XclExpRoot::GetObjectManager() const +{ + OSL_ENSURE( mrExpData.mxObjMgr, "XclExpRoot::GetObjectManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxObjMgr; +} + +XclExpFilterManager& XclExpRoot::GetFilterManager() const +{ + OSL_ENSURE( mrExpData.mxFilterMgr, "XclExpRoot::GetFilterManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxFilterMgr; +} + +XclExpDxfs& XclExpRoot::GetDxfs() const +{ + OSL_ENSURE( mrExpData.mxDxfs, "XclExpRoot::GetDxfs - missing object ( wrong BIFF?)" ); + return *mrExpData.mxDxfs; +} + +XclExpPivotTableManager& XclExpRoot::GetPivotTableManager() const +{ + OSL_ENSURE( mrExpData.mxPTableMgr, "XclExpRoot::GetPivotTableManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxPTableMgr; +} + +XclExpXmlPivotTableManager& XclExpRoot::GetXmlPivotTableManager() +{ + assert(mrExpData.mxXmlPTableMgr); + return *mrExpData.mxXmlPTableMgr; +} + +XclExpTablesManager& XclExpRoot::GetTablesManager() +{ + assert(mrExpData.mxTablesMgr); + return *mrExpData.mxTablesMgr; +} + +void XclExpRoot::InitializeConvert() +{ + mrExpData.mxTabInfo = std::make_shared<XclExpTabInfo>( GetRoot() ); + mrExpData.mxAddrConv = std::make_shared<XclExpAddressConverter>( GetRoot() ); + mrExpData.mxFmlaComp = std::make_shared<XclExpFormulaCompiler>( GetRoot() ); + mrExpData.mxProgress = std::make_shared<XclExpProgressBar>( GetRoot() ); + + GetProgressBar().Initialize(); +} + +void XclExpRoot::InitializeGlobals() +{ + SetCurrScTab( SCTAB_GLOBAL ); + + if( GetBiff() >= EXC_BIFF5 ) + { + mrExpData.mxPalette = new XclExpPalette( GetRoot() ); + mrExpData.mxFontBfr = new XclExpFontBuffer( GetRoot() ); + mrExpData.mxNumFmtBfr = new XclExpNumFmtBuffer( GetRoot() ); + mrExpData.mxXFBfr = new XclExpXFBuffer( GetRoot() ); + mrExpData.mxGlobLinkMgr = new XclExpLinkManager( GetRoot() ); + mrExpData.mxNameMgr = new XclExpNameManager( GetRoot() ); + } + + if( GetBiff() == EXC_BIFF8 ) + { + mrExpData.mxSst = new XclExpSst(); + mrExpData.mxObjMgr = std::make_shared<XclExpObjectManager>( GetRoot() ); + mrExpData.mxFilterMgr = std::make_shared<XclExpFilterManager>( GetRoot() ); + mrExpData.mxPTableMgr = std::make_shared<XclExpPivotTableManager>( GetRoot() ); + // BIFF8: only one link manager for all sheets + mrExpData.mxLocLinkMgr = mrExpData.mxGlobLinkMgr; + mrExpData.mxDxfs = new XclExpDxfs( GetRoot() ); + } + + if( GetOutput() == EXC_OUTPUT_XML_2007 ) + { + mrExpData.mxXmlPTableMgr = std::make_shared<XclExpXmlPivotTableManager>(GetRoot()); + mrExpData.mxTablesMgr = std::make_shared<XclExpTablesManager>(GetRoot()); + + do + { + ScDocument& rDoc = GetDoc(); + // Pass the model factory to OpCodeProvider, not the process + // service factory, otherwise a FormulaOpCodeMapperObj would be + // instantiated instead of a ScFormulaOpCodeMapperObj and the + // ScCompiler virtuals not be called! Which would be the case with + // the current (2013-01-24) rDoc.GetServiceManager() + const SfxObjectShell* pShell = rDoc.GetDocumentShell(); + if (!pShell) + { + SAL_WARN( "sc", "XclExpRoot::InitializeGlobals - no object shell"); + break; + } + uno::Reference< lang::XComponent > xComponent = pShell->GetModel(); + if (!xComponent.is()) + { + SAL_WARN( "sc", "XclExpRoot::InitializeGlobals - no component"); + break; + } + uno::Reference< lang::XMultiServiceFactory > xModelFactory( xComponent, uno::UNO_QUERY); + oox::xls::OpCodeProvider aOpCodeProvider(xModelFactory, false); + // Compiler mocks about non-matching ctor or conversion from + // Sequence<...> to Sequence<const ...> if directly created or passed, + // conversion through Any works around. + uno::Any aAny( aOpCodeProvider.getOoxParserMap()); + uno::Sequence< const sheet::FormulaOpCodeMapEntry > aOpCodeMapping; + if (!(aAny >>= aOpCodeMapping)) + { + SAL_WARN( "sc", "XclExpRoot::InitializeGlobals - no OpCodeMap"); + break; + } + ScCompiler aCompiler( rDoc, ScAddress(), rDoc.GetGrammar()); + mrExpData.mxOpCodeMap = formula::FormulaCompiler::CreateOpCodeMap( aOpCodeMapping, true); + } while(false); + } + + GetXFBuffer().Initialize(); + GetNameManager().Initialize(); +} + +void XclExpRoot::InitializeTable( SCTAB nScTab ) +{ + SetCurrScTab( nScTab ); + if( GetBiff() == EXC_BIFF5 ) + { + // local link manager per sheet + mrExpData.mxLocLinkMgr = new XclExpLinkManager( GetRoot() ); + } +} + +void XclExpRoot::InitializeSave() +{ + GetPalette().Finalize(); + GetXFBuffer().Finalize(); +} + +XclExpRecordRef XclExpRoot::CreateRecord( sal_uInt16 nRecId ) const +{ + XclExpRecordRef xRec; + switch( nRecId ) + { + case EXC_ID_PALETTE: xRec = mrExpData.mxPalette; break; + case EXC_ID_FONTLIST: xRec = mrExpData.mxFontBfr; break; + case EXC_ID_FORMATLIST: xRec = mrExpData.mxNumFmtBfr; break; + case EXC_ID_XFLIST: xRec = mrExpData.mxXFBfr; break; + case EXC_ID_SST: xRec = mrExpData.mxSst; break; + case EXC_ID_EXTERNSHEET: xRec = GetLocalLinkMgrRef(); break; + case EXC_ID_NAME: xRec = mrExpData.mxNameMgr; break; + case EXC_ID_DXFS: xRec = mrExpData.mxDxfs; break; + } + OSL_ENSURE( xRec, "XclExpRoot::CreateRecord - unknown record ID or missing object" ); + return xRec; +} + +bool XclExpRoot::IsDocumentEncrypted() const +{ + // We need to encrypt the content when the document structure is protected. + const ScDocProtection* pDocProt = GetDoc().GetDocProtection(); + if (pDocProt && pDocProt->isProtected() && pDocProt->isOptionEnabled(ScDocProtection::STRUCTURE)) + return true; + + // Whether password is entered directly into the save dialog. + return GetEncryptionData().hasElements(); +} + +uno::Sequence< beans::NamedValue > XclExpRoot::GenerateEncryptionData( const OUString& aPass ) +{ + uno::Sequence< beans::NamedValue > aEncryptionData; + + if ( !aPass.isEmpty() && aPass.getLength() < 16 ) + { + rtlRandomPool aRandomPool = rtl_random_createPool (); + sal_uInt8 pnDocId[16]; + rtl_random_getBytes( aRandomPool, pnDocId, 16 ); + + rtl_random_destroyPool( aRandomPool ); + + sal_uInt16 pnPasswd[16] = {}; + for( sal_Int32 nChar = 0; nChar < aPass.getLength(); ++nChar ) + pnPasswd[nChar] = aPass[nChar]; + + ::msfilter::MSCodec_Std97 aCodec; + aCodec.InitKey( pnPasswd, pnDocId ); + aEncryptionData = aCodec.GetEncryptionData(); + } + + return aEncryptionData; +} + +uno::Sequence< beans::NamedValue > XclExpRoot::GetEncryptionData() const +{ + uno::Sequence< beans::NamedValue > aEncryptionData; + const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem<SfxUnoAnyItem>(GetMedium().GetItemSet(), SID_ENCRYPTIONDATA, false); + if ( pEncryptionDataItem ) + pEncryptionDataItem->GetValue() >>= aEncryptionData; + else + { + // try to get the encryption data from the password + const SfxStringItem* pPasswordItem = SfxItemSet::GetItem<SfxStringItem>(GetMedium().GetItemSet(), SID_PASSWORD, false); + if ( pPasswordItem && !pPasswordItem->GetValue().isEmpty() ) + aEncryptionData = GenerateEncryptionData( pPasswordItem->GetValue() ); + } + + return aEncryptionData; +} + +uno::Sequence< beans::NamedValue > XclExpRoot::GenerateDefaultEncryptionData() +{ + return GenerateEncryptionData( GetDefaultPassword() ); +} + +XclExpRootData::XclExpLinkMgrRef const & XclExpRoot::GetLocalLinkMgrRef() const +{ + return IsInGlobals() ? mrExpData.mxGlobLinkMgr : mrExpData.mxLocLinkMgr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xestream.cxx b/sc/source/filter/excel/xestream.cxx new file mode 100644 index 000000000..f0486cc37 --- /dev/null +++ b/sc/source/filter/excel/xestream.cxx @@ -0,0 +1,1259 @@ +/* -*- 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 <stdio.h> +#include <string.h> +#include <utility> + +#include <filter/msfilter/util.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/random.h> +#include <sax/fshelper.hxx> +#include <unotools/streamwrap.hxx> +#include <sot/storage.hxx> +#include <tools/urlobj.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <officecfg/Office/Calc.hxx> + +#include <docuno.hxx> +#include <xestream.hxx> +#include <xladdress.hxx> +#include <xlstring.hxx> +#include <xltools.hxx> +#include <xeroot.hxx> +#include <xestring.hxx> +#include <xlstyle.hxx> +#include <rangelst.hxx> +#include <compiler.hxx> +#include <formulacell.hxx> +#include <tokenarray.hxx> +#include <tokenstringcontext.hxx> +#include <refreshtimerprotector.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <root.hxx> +#include <sfx2/app.hxx> + +#include <docsh.hxx> +#include <viewdata.hxx> +#include <excdoc.hxx> + +#include <oox/token/tokens.hxx> +#include <oox/token/relationship.hxx> +#include <oox/export/drawingml.hxx> +#include <oox/export/utils.hxx> +#include <formula/grammar.hxx> +#include <oox/ole/vbaexport.hxx> +#include <excelvbaproject.hxx> + +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <memory> +#include <comphelper/storagehelper.hxx> + +#include <externalrefmgr.hxx> + +#define DEBUG_XL_ENCRYPTION 0 + +using ::com::sun::star::uno::XInterface; +using ::std::vector; + +using namespace com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sheet; +using namespace ::com::sun::star::uno; +using namespace ::formula; +using namespace ::oox; + +XclExpStream::XclExpStream( SvStream& rOutStrm, const XclExpRoot& rRoot, sal_uInt16 nMaxRecSize ) : + mrStrm( rOutStrm ), + mrRoot( rRoot ), + mbUseEncrypter( false ), + mnMaxRecSize( nMaxRecSize ), + mnCurrMaxSize( 0 ), + mnMaxSliceSize( 0 ), + mnHeaderSize( 0 ), + mnCurrSize( 0 ), + mnSliceSize( 0 ), + mnPredictSize( 0 ), + mnLastSizePos( 0 ), + mbInRec( false ) +{ + if( mnMaxRecSize == 0 ) + mnMaxRecSize = (mrRoot.GetBiff() <= EXC_BIFF5) ? EXC_MAXRECSIZE_BIFF5 : EXC_MAXRECSIZE_BIFF8; + mnMaxContSize = mnMaxRecSize; +} + +XclExpStream::~XclExpStream() +{ + mrStrm.FlushBuffer(); +} + +void XclExpStream::StartRecord( sal_uInt16 nRecId, std::size_t nRecSize ) +{ + OSL_ENSURE( !mbInRec, "XclExpStream::StartRecord - another record still open" ); + DisableEncryption(); + mnMaxContSize = mnCurrMaxSize = mnMaxRecSize; + mnPredictSize = nRecSize; + mbInRec = true; + InitRecord( nRecId ); + SetSliceSize( 0 ); + EnableEncryption(); +} + +void XclExpStream::EndRecord() +{ + OSL_ENSURE( mbInRec, "XclExpStream::EndRecord - no record open" ); + DisableEncryption(); + UpdateRecSize(); + mrStrm.Seek( STREAM_SEEK_TO_END ); + mbInRec = false; +} + +void XclExpStream::SetSliceSize( sal_uInt16 nSize ) +{ + mnMaxSliceSize = nSize; + mnSliceSize = 0; +} + +XclExpStream& XclExpStream::operator<<( sal_Int8 nValue ) +{ + PrepareWrite( 1 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteSChar( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_uInt8 nValue ) +{ + PrepareWrite( 1 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteUChar( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_Int16 nValue ) +{ + PrepareWrite( 2 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteInt16( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_uInt16 nValue ) +{ + PrepareWrite( 2 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteUInt16( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_Int32 nValue ) +{ + PrepareWrite( 4 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteInt32( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_uInt32 nValue ) +{ + PrepareWrite( 4 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteUInt32( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( float fValue ) +{ + PrepareWrite( 4 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, fValue); + else + mrStrm.WriteFloat( fValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( double fValue ) +{ + PrepareWrite( 8 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, fValue); + else + mrStrm.WriteDouble( fValue ); + return *this; +} + +std::size_t XclExpStream::Write( const void* pData, std::size_t nBytes ) +{ + std::size_t nRet = 0; + if( pData && (nBytes > 0) ) + { + if( mbInRec ) + { + const sal_uInt8* pBuffer = static_cast< const sal_uInt8* >( pData ); + std::size_t nBytesLeft = nBytes; + bool bValid = true; + + while( bValid && (nBytesLeft > 0) ) + { + std::size_t nWriteLen = ::std::min< std::size_t >( PrepareWrite(), nBytesLeft ); + std::size_t nWriteRet = nWriteLen; + if (mbUseEncrypter && HasValidEncrypter()) + { + OSL_ENSURE(nWriteLen > 0, "XclExpStream::Write: write length is 0!"); + vector<sal_uInt8> aBytes(nWriteLen); + memcpy(aBytes.data(), pBuffer, nWriteLen); + mxEncrypter->EncryptBytes(mrStrm, aBytes); + // TODO: How do I check if all the bytes have been successfully written ? + } + else + { + nWriteRet = mrStrm.WriteBytes(pBuffer, nWriteLen); + bValid = (nWriteLen == nWriteRet); + OSL_ENSURE( bValid, "XclExpStream::Write - stream write error" ); + } + pBuffer += nWriteRet; + nRet += nWriteRet; + nBytesLeft -= nWriteRet; + UpdateSizeVars( nWriteRet ); + } + } + else + nRet = mrStrm.WriteBytes(pData, nBytes); + } + return nRet; +} + +void XclExpStream::WriteZeroBytes( std::size_t nBytes ) +{ + if( mbInRec ) + { + std::size_t nBytesLeft = nBytes; + while( nBytesLeft > 0 ) + { + std::size_t nWriteLen = ::std::min< std::size_t >( PrepareWrite(), nBytesLeft ); + WriteRawZeroBytes( nWriteLen ); + nBytesLeft -= nWriteLen; + UpdateSizeVars( nWriteLen ); + } + } + else + WriteRawZeroBytes( nBytes ); +} + +void XclExpStream::WriteZeroBytesToRecord( std::size_t nBytes ) +{ + if (!mbInRec) + // not in record. + return; + + for (std::size_t i = 0; i < nBytes; ++i) + *this << sal_uInt8(0)/*nZero*/; +} + +void XclExpStream::CopyFromStream(SvStream& rInStrm, sal_uInt64 const nBytes) +{ + sal_uInt64 const nRemaining(rInStrm.remainingSize()); + sal_uInt64 nBytesLeft = ::std::min(nBytes, nRemaining); + if( nBytesLeft <= 0 ) + return; + + const std::size_t nMaxBuffer = 4096; + std::unique_ptr<sal_uInt8[]> pBuffer( + new sal_uInt8[ ::std::min<std::size_t>(nBytesLeft, nMaxBuffer) ]); + bool bValid = true; + + while( bValid && (nBytesLeft > 0) ) + { + std::size_t nWriteLen = ::std::min<std::size_t>(nBytesLeft, nMaxBuffer); + rInStrm.ReadBytes(pBuffer.get(), nWriteLen); + std::size_t nWriteRet = Write( pBuffer.get(), nWriteLen ); + bValid = (nWriteLen == nWriteRet); + nBytesLeft -= nWriteRet; + } +} + +void XclExpStream::WriteUnicodeBuffer( const ScfUInt16Vec& rBuffer, sal_uInt8 nFlags ) +{ + SetSliceSize( 0 ); + nFlags &= EXC_STRF_16BIT; // repeat only 16bit flag + sal_uInt16 nCharLen = nFlags ? 2 : 1; + + for( const auto& rItem : rBuffer ) + { + if( mbInRec && (mnCurrSize + nCharLen > mnCurrMaxSize) ) + { + StartContinue(); + operator<<( nFlags ); + } + if( nCharLen == 2 ) + operator<<( rItem ); + else + operator<<( static_cast< sal_uInt8 >( rItem ) ); + } +} + +// Xcl has an obscure sense of whether starting a new record or not, +// and crashes if it encounters the string header at the very end of a record. +// Thus we add 1 to give some room, seems like they do it that way but with another count (10?) +void XclExpStream::WriteByteString( const OString& rString ) +{ + SetSliceSize( 0 ); + std::size_t nLen = ::std::min< std::size_t >( rString.getLength(), 0x00FF ); + nLen = ::std::min< std::size_t >( nLen, 0xFF ); + + sal_uInt16 nLeft = PrepareWrite(); + if( mbInRec && (nLeft <= 1) ) + StartContinue(); + + operator<<( static_cast< sal_uInt8 >( nLen ) ); + Write( rString.getStr(), nLen ); +} + +void XclExpStream::WriteCharBuffer( const ScfUInt8Vec& rBuffer ) +{ + SetSliceSize( 0 ); + Write( rBuffer.data(), rBuffer.size() ); +} + +void XclExpStream::SetEncrypter( XclExpEncrypterRef const & xEncrypter ) +{ + mxEncrypter = xEncrypter; +} + +bool XclExpStream::HasValidEncrypter() const +{ + return mxEncrypter && mxEncrypter->IsValid(); +} + +void XclExpStream::EnableEncryption( bool bEnable ) +{ + mbUseEncrypter = bEnable && HasValidEncrypter(); +} + +void XclExpStream::DisableEncryption() +{ + EnableEncryption(false); +} + +void XclExpStream::SetSvStreamPos(sal_uInt64 const nPos) +{ + OSL_ENSURE( !mbInRec, "XclExpStream::SetSvStreamPos - not allowed inside of a record" ); + mbInRec ? 0 : mrStrm.Seek( nPos ); +} + +// private -------------------------------------------------------------------- + +void XclExpStream::InitRecord( sal_uInt16 nRecId ) +{ + mrStrm.Seek( STREAM_SEEK_TO_END ); + mrStrm.WriteUInt16( nRecId ); + + mnLastSizePos = mrStrm.Tell(); + mnHeaderSize = static_cast< sal_uInt16 >( ::std::min< std::size_t >( mnPredictSize, mnCurrMaxSize ) ); + mrStrm.WriteUInt16( mnHeaderSize ); + mnCurrSize = mnSliceSize = 0; +} + +void XclExpStream::UpdateRecSize() +{ + if( mnCurrSize != mnHeaderSize ) + { + mrStrm.Seek( mnLastSizePos ); + mrStrm.WriteUInt16( mnCurrSize ); + } +} + +void XclExpStream::UpdateSizeVars( std::size_t nSize ) +{ + OSL_ENSURE( mnCurrSize + nSize <= mnCurrMaxSize, "XclExpStream::UpdateSizeVars - record overwritten" ); + mnCurrSize = mnCurrSize + static_cast< sal_uInt16 >( nSize ); + + if( mnMaxSliceSize > 0 ) + { + OSL_ENSURE( mnSliceSize + nSize <= mnMaxSliceSize, "XclExpStream::UpdateSizeVars - slice overwritten" ); + mnSliceSize = mnSliceSize + static_cast< sal_uInt16 >( nSize ); + if( mnSliceSize >= mnMaxSliceSize ) + mnSliceSize = 0; + } +} + +void XclExpStream::StartContinue() +{ + UpdateRecSize(); + mnCurrMaxSize = mnMaxContSize; + mnPredictSize -= mnCurrSize; + InitRecord( EXC_ID_CONT ); +} + +void XclExpStream::PrepareWrite( sal_uInt16 nSize ) +{ + if( mbInRec ) + { + if( (mnCurrSize + nSize > mnCurrMaxSize) || + ((mnMaxSliceSize > 0) && (mnSliceSize == 0) && (mnCurrSize + mnMaxSliceSize > mnCurrMaxSize)) ) + StartContinue(); + UpdateSizeVars( nSize ); + } +} + +sal_uInt16 XclExpStream::PrepareWrite() +{ + sal_uInt16 nRet = 0; + if( mbInRec ) + { + if( (mnCurrSize >= mnCurrMaxSize) || + ((mnMaxSliceSize > 0) && (mnSliceSize == 0) && (mnCurrSize + mnMaxSliceSize > mnCurrMaxSize)) ) + StartContinue(); + UpdateSizeVars( 0 ); + + nRet = (mnMaxSliceSize > 0) ? (mnMaxSliceSize - mnSliceSize) : (mnCurrMaxSize - mnCurrSize); + } + return nRet; +} + +void XclExpStream::WriteRawZeroBytes( std::size_t nBytes ) +{ + const sal_uInt32 nData = 0; + std::size_t nBytesLeft = nBytes; + while( nBytesLeft >= sizeof( nData ) ) + { + mrStrm.WriteUInt32( nData ); + nBytesLeft -= sizeof( nData ); + } + if( nBytesLeft ) + mrStrm.WriteBytes(&nData, nBytesLeft); +} + +XclExpBiff8Encrypter::XclExpBiff8Encrypter( const XclExpRoot& rRoot ) : + mnOldPos(STREAM_SEEK_TO_END), + mbValid(false) +{ + Sequence< NamedValue > aEncryptionData = rRoot.GetEncryptionData(); + if( !aEncryptionData.hasElements() ) + // Empty password. Get the default biff8 password. + aEncryptionData = XclExpRoot::GenerateDefaultEncryptionData(); + Init( aEncryptionData ); +} + +XclExpBiff8Encrypter::~XclExpBiff8Encrypter() +{ +} + +void XclExpBiff8Encrypter::GetSaltDigest( sal_uInt8 pnSaltDigest[16] ) const +{ + if ( sizeof( mpnSaltDigest ) == 16 ) + memcpy( pnSaltDigest, mpnSaltDigest, 16 ); +} + +void XclExpBiff8Encrypter::GetSalt( sal_uInt8 pnSalt[16] ) const +{ + if ( sizeof( mpnSalt ) == 16 ) + memcpy( pnSalt, mpnSalt, 16 ); +} + +void XclExpBiff8Encrypter::GetDocId( sal_uInt8 pnDocId[16] ) const +{ + if ( sizeof( mpnDocId ) == 16 ) + memcpy( pnDocId, mpnDocId, 16 ); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt8 nData ) +{ + vector<sal_uInt8> aByte { nData }; + EncryptBytes(rStrm, aByte); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt16 nData ) +{ + ::std::vector<sal_uInt8> pnBytes + { + o3tl::narrowing<sal_uInt8>(nData & 0xFF), + o3tl::narrowing<sal_uInt8>((nData >> 8) & 0xFF) + }; + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt32 nData ) +{ + ::std::vector<sal_uInt8> pnBytes + { + o3tl::narrowing<sal_uInt8>(nData & 0xFF), + o3tl::narrowing<sal_uInt8>((nData >> 8) & 0xFF), + o3tl::narrowing<sal_uInt8>((nData >> 16) & 0xFF), + o3tl::narrowing<sal_uInt8>((nData >> 24) & 0xFF) + }; + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, float fValue ) +{ + ::std::vector<sal_uInt8> pnBytes(4); + memcpy(pnBytes.data(), &fValue, 4); + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, double fValue ) +{ + ::std::vector<sal_uInt8> pnBytes(8); + memcpy(pnBytes.data(), &fValue, 8); + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int8 nData ) +{ + Encrypt(rStrm, static_cast<sal_uInt8>(nData)); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int16 nData ) +{ + Encrypt(rStrm, static_cast<sal_uInt16>(nData)); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int32 nData ) +{ + Encrypt(rStrm, static_cast<sal_uInt32>(nData)); +} + +void XclExpBiff8Encrypter::Init( const Sequence< NamedValue >& rEncryptionData ) +{ + mbValid = false; + + if( !maCodec.InitCodec( rEncryptionData ) ) + return; + + maCodec.GetDocId( mpnDocId ); + + // generate the salt here + rtlRandomPool aRandomPool = rtl_random_createPool (); + rtl_random_getBytes( aRandomPool, mpnSalt, 16 ); + rtl_random_destroyPool( aRandomPool ); + + memset( mpnSaltDigest, 0, sizeof( mpnSaltDigest ) ); + + // generate salt hash. + ::msfilter::MSCodec_Std97 aCodec; + aCodec.InitCodec( rEncryptionData ); + aCodec.CreateSaltDigest( mpnSalt, mpnSaltDigest ); + + // verify to make sure it's in good shape. + mbValid = maCodec.VerifyKey( mpnSalt, mpnSaltDigest ); +} + +sal_uInt32 XclExpBiff8Encrypter::GetBlockPos( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt32 >( nStrmPos / EXC_ENCR_BLOCKSIZE ); +} + +sal_uInt16 XclExpBiff8Encrypter::GetOffsetInBlock( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt16 >( nStrmPos % EXC_ENCR_BLOCKSIZE ); +} + +void XclExpBiff8Encrypter::EncryptBytes( SvStream& rStrm, vector<sal_uInt8>& aBytes ) +{ + sal_uInt64 nStrmPos = rStrm.Tell(); + sal_uInt16 nBlockOffset = GetOffsetInBlock(nStrmPos); + sal_uInt32 nBlockPos = GetBlockPos(nStrmPos); + +#if DEBUG_XL_ENCRYPTION + fprintf(stdout, "XclExpBiff8Encrypter::EncryptBytes: stream pos = %ld offset in block = %d block pos = %ld\n", + nStrmPos, nBlockOffset, nBlockPos); +#endif + + sal_uInt16 nSize = static_cast< sal_uInt16 >( aBytes.size() ); + if (nSize == 0) + return; + +#if DEBUG_XL_ENCRYPTION + fprintf(stdout, "RAW: "); + for (sal_uInt16 i = 0; i < nSize; ++i) + fprintf(stdout, "%2.2X ", aBytes[i]); + fprintf(stdout, "\n"); +#endif + + if (mnOldPos != nStrmPos) + { + sal_uInt16 nOldOffset = GetOffsetInBlock(mnOldPos); + sal_uInt32 nOldBlockPos = GetBlockPos(mnOldPos); + + if ( (nBlockPos != nOldBlockPos) || (nBlockOffset < nOldOffset) ) + { + maCodec.InitCipher(nBlockPos); + nOldOffset = 0; + } + + if (nBlockOffset > nOldOffset) + maCodec.Skip(nBlockOffset - nOldOffset); + } + + sal_uInt16 nBytesLeft = nSize; + sal_uInt16 nPos = 0; + while (nBytesLeft > 0) + { + sal_uInt16 nBlockLeft = EXC_ENCR_BLOCKSIZE - nBlockOffset; + sal_uInt16 nEncBytes = ::std::min(nBlockLeft, nBytesLeft); + + bool bRet = maCodec.Encode(&aBytes[nPos], nEncBytes, &aBytes[nPos], nEncBytes); + OSL_ENSURE(bRet, "XclExpBiff8Encrypter::EncryptBytes: encryption failed!!"); + + std::size_t nRet = rStrm.WriteBytes(&aBytes[nPos], nEncBytes); + OSL_ENSURE(nRet == nEncBytes, "XclExpBiff8Encrypter::EncryptBytes: fail to write to stream!!"); + + nStrmPos = rStrm.Tell(); + nBlockOffset = GetOffsetInBlock(nStrmPos); + nBlockPos = GetBlockPos(nStrmPos); + if (nBlockOffset == 0) + maCodec.InitCipher(nBlockPos); + + nBytesLeft -= nEncBytes; + nPos += nEncBytes; + } + mnOldPos = nStrmPos; +} + +static const char* lcl_GetErrorString( FormulaError nScErrCode ) +{ + sal_uInt8 nXclErrCode = XclTools::GetXclErrorCode( nScErrCode ); + switch( nXclErrCode ) + { + case EXC_ERR_NULL: return "#NULL!"; + case EXC_ERR_DIV0: return "#DIV/0!"; + case EXC_ERR_VALUE: return "#VALUE!"; + case EXC_ERR_REF: return "#REF!"; + case EXC_ERR_NAME: return "#NAME?"; + case EXC_ERR_NUM: return "#NUM!"; + case EXC_ERR_NA: + default: return "#N/A"; + } +} + +void XclXmlUtils::GetFormulaTypeAndValue( ScFormulaCell& rCell, const char*& rsType, OUString& rsValue ) +{ + sc::FormulaResultValue aResValue = rCell.GetResult(); + + switch (aResValue.meType) + { + case sc::FormulaResultValue::Error: + rsType = "e"; + rsValue = ToOUString(lcl_GetErrorString(aResValue.mnError)); + break; + case sc::FormulaResultValue::Value: + rsType = rCell.GetFormatType() == SvNumFormatType::LOGICAL + && (aResValue.mfValue == 0.0 || aResValue.mfValue == 1.0) + ? "b" + : "n"; + rsValue = OUString::number(aResValue.mfValue); + break; + case sc::FormulaResultValue::String: + rsType = "str"; + rsValue = rCell.GetString().getString(); + break; + case sc::FormulaResultValue::Invalid: + default: + // TODO : double-check this to see if this is correct. + rsType = "inlineStr"; + rsValue = rCell.GetString().getString(); + } +} + +OUString XclXmlUtils::GetStreamName( const char* sStreamDir, const char* sStream, sal_Int32 nId ) +{ + OUStringBuffer sBuf; + if( sStreamDir ) + sBuf.appendAscii( sStreamDir ); + sBuf.appendAscii( sStream ); + if( nId ) + sBuf.append( nId ); + if( strstr(sStream, "vml") ) + sBuf.append( ".vml" ); + else + sBuf.append( ".xml" ); + return sBuf.makeStringAndClear(); +} + +OString XclXmlUtils::ToOString( const Color& rColor ) +{ + char buf[9]; + sprintf( buf, "%.2X%.2X%.2X%.2X", rColor.GetAlpha(), rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() ); + buf[8] = '\0'; + return OString(buf); +} + +OStringBuffer& XclXmlUtils::ToOString( OStringBuffer& s, const ScAddress& rAddress ) +{ + rAddress.Format(s, ScRefFlags::VALID, nullptr, ScAddress::Details( FormulaGrammar::CONV_XL_A1)); + return s; +} + +OString XclXmlUtils::ToOString( const ScfUInt16Vec& rBuffer ) +{ + if(rBuffer.empty()) + return OString(); + + const sal_uInt16* pBuffer = rBuffer.data(); + return OString( + reinterpret_cast<sal_Unicode const *>(pBuffer), rBuffer.size(), + RTL_TEXTENCODING_UTF8); +} + +OString XclXmlUtils::ToOString( const ScDocument& rDoc, const ScRange& rRange, bool bFullAddressNotation ) +{ + OUString sRange(rRange.Format( rDoc, ScRefFlags::VALID, + ScAddress::Details( FormulaGrammar::CONV_XL_A1 ), + bFullAddressNotation ) ); + return sRange.toUtf8(); +} + +OString XclXmlUtils::ToOString( const ScDocument& rDoc, const ScRangeList& rRangeList ) +{ + OUString s; + rRangeList.Format(s, ScRefFlags::VALID, rDoc, FormulaGrammar::CONV_XL_OOX, ' '); + return s.toUtf8(); +} + +static ScAddress lcl_ToAddress( const XclAddress& rAddress ) +{ + return ScAddress( rAddress.mnCol, rAddress.mnRow, 0 ); +} + +OStringBuffer& XclXmlUtils::ToOString( OStringBuffer& s, const XclAddress& rAddress ) +{ + return ToOString( s, lcl_ToAddress( rAddress )); +} + +OString XclXmlUtils::ToOString( const XclExpString& s ) +{ + OSL_ENSURE( !s.IsRich(), "XclXmlUtils::ToOString(XclExpString): rich text string found!" ); + return ToOString( s.GetUnicodeBuffer() ); +} + +static ScRange lcl_ToRange( const XclRange& rRange ) +{ + ScRange aRange; + + aRange.aStart = lcl_ToAddress( rRange.maFirst ); + aRange.aEnd = lcl_ToAddress( rRange.maLast ); + + return aRange; +} + +OString XclXmlUtils::ToOString( const ScDocument& rDoc, const XclRangeList& rRanges ) +{ + ScRangeList aRanges; + for( const auto& rRange : rRanges ) + { + aRanges.push_back( lcl_ToRange( rRange ) ); + } + return ToOString( rDoc, aRanges ); +} + +OUString XclXmlUtils::ToOUString( const char* s ) +{ + return OUString( s, static_cast<sal_Int32>(strlen( s )), RTL_TEXTENCODING_ASCII_US ); +} + +OUString XclXmlUtils::ToOUString( const ScfUInt16Vec& rBuf, sal_Int32 nStart, sal_Int32 nLength ) +{ + if( nLength == -1 || ( nLength > (static_cast<sal_Int32>(rBuf.size()) - nStart) ) ) + nLength = (rBuf.size() - nStart); + + return nLength > 0 + ? OUString( + reinterpret_cast<sal_Unicode const *>(&rBuf[nStart]), nLength) + : OUString(); +} + +OUString XclXmlUtils::ToOUString( + sc::CompileFormulaContext& rCtx, const ScAddress& rAddress, const ScTokenArray* pTokenArray, + FormulaError nErrCode ) +{ + ScCompiler aCompiler( rCtx, rAddress, const_cast<ScTokenArray&>(*pTokenArray)); + + /* TODO: isn't this the same as passed in rCtx and thus superfluous? */ + aCompiler.SetGrammar(FormulaGrammar::GRAM_OOXML); + + sal_Int32 nLen = pTokenArray->GetLen(); + OUStringBuffer aBuffer( nLen ? (nLen * 5) : 8 ); + if (nLen) + aCompiler.CreateStringFromTokenArray( aBuffer ); + else + { + if (nErrCode != FormulaError::NONE) + aCompiler.AppendErrorConstant( aBuffer, nErrCode); + else + { + // No code SHOULD be an "error cell", assert caller thought of that + // and it really is. + assert(!"No code and no error."); + } + } + + return aBuffer.makeStringAndClear(); +} + +OUString XclXmlUtils::ToOUString( const XclExpString& s ) +{ + OSL_ENSURE( !s.IsRich(), "XclXmlUtils::ToOString(XclExpString): rich text string found!" ); + return ToOUString( s.GetUnicodeBuffer() ); +} + +static void lcl_WriteValue( const sax_fastparser::FSHelperPtr& rStream, sal_Int32 nElement, const char* pValue ) +{ + if( !pValue ) + return; + rStream->singleElement(nElement, XML_val, pValue); +} + +static const char* lcl_GetUnderlineStyle( FontLineStyle eUnderline, bool& bHaveUnderline ) +{ + bHaveUnderline = true; + switch( eUnderline ) + { + // OOXTODO: doubleAccounting, singleAccounting + // OOXTODO: what should be done with the other FontLineStyle values? + case LINESTYLE_SINGLE: return "single"; + case LINESTYLE_DOUBLE: return "double"; + case LINESTYLE_NONE: + default: bHaveUnderline = false; return "none"; + } +} + +static const char* lcl_ToVerticalAlignmentRun( SvxEscapement eEscapement, bool& bHaveAlignment ) +{ + bHaveAlignment = true; + switch( eEscapement ) + { + case SvxEscapement::Superscript: return "superscript"; + case SvxEscapement::Subscript: return "subscript"; + case SvxEscapement::Off: + default: bHaveAlignment = false; return "baseline"; + } +} + +sax_fastparser::FSHelperPtr XclXmlUtils::WriteFontData( sax_fastparser::FSHelperPtr pStream, const XclFontData& rFontData, sal_Int32 nFontId ) +{ + bool bHaveUnderline, bHaveVertAlign; + const char* pUnderline = lcl_GetUnderlineStyle( rFontData.GetScUnderline(), bHaveUnderline ); + const char* pVertAlign = lcl_ToVerticalAlignmentRun( rFontData.GetScEscapement(), bHaveVertAlign ); + + lcl_WriteValue( pStream, XML_b, rFontData.mnWeight > 400 ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_i, rFontData.mbItalic ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_strike, rFontData.mbStrikeout ? ToPsz( true ) : nullptr ); + // OOXTODO: lcl_WriteValue( rStream, XML_condense, ); // mac compatibility setting + // OOXTODO: lcl_WriteValue( rStream, XML_extend, ); // compatibility setting + lcl_WriteValue( pStream, XML_outline, rFontData.mbOutline ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_shadow, rFontData.mbShadow ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_u, bHaveUnderline ? pUnderline : nullptr ); + lcl_WriteValue( pStream, XML_vertAlign, bHaveVertAlign ? pVertAlign : nullptr ); + lcl_WriteValue( pStream, XML_sz, OString::number( rFontData.mnHeight / 20.0 ).getStr() ); // Twips->Pt + if( rFontData.maColor != Color( ColorAlpha, 0, 0xFF, 0xFF, 0xFF ) ) + pStream->singleElement( XML_color, + // OOXTODO: XML_auto, bool + // OOXTODO: XML_indexed, uint + XML_rgb, XclXmlUtils::ToOString(rFontData.maColor) + // OOXTODO: XML_theme, index into <clrScheme/> + // OOXTODO: XML_tint, double + ); + lcl_WriteValue( pStream, nFontId, rFontData.maName.toUtf8().getStr() ); + lcl_WriteValue( pStream, XML_family, OString::number( rFontData.mnFamily ).getStr() ); + lcl_WriteValue( pStream, XML_charset, rFontData.mnCharSet != 0 ? OString::number( rFontData.mnCharSet ).getStr() : nullptr ); + + return pStream; +} + +XclExpXmlStream::XclExpXmlStream( const uno::Reference< XComponentContext >& rCC, bool bExportVBA, bool bExportTemplate ) + : XmlFilterBase( rCC ), + mpRoot( nullptr ), + mbExportVBA(bExportVBA), + mbExportTemplate(bExportTemplate) +{ +} + +XclExpXmlStream::~XclExpXmlStream() +{ + assert(maStreams.empty() && "Forgotten PopStream()?"); +} + +sax_fastparser::FSHelperPtr& XclExpXmlStream::GetCurrentStream() +{ + OSL_ENSURE( !maStreams.empty(), "XclExpXmlStream::GetCurrentStream - no current stream" ); + return maStreams.top(); +} + +void XclExpXmlStream::PushStream( sax_fastparser::FSHelperPtr const & aStream ) +{ + maStreams.push( aStream ); +} + +void XclExpXmlStream::PopStream() +{ + OSL_ENSURE( !maStreams.empty(), "XclExpXmlStream::PopStream - stack is empty!" ); + maStreams.pop(); +} + +sax_fastparser::FSHelperPtr XclExpXmlStream::GetStreamForPath( const OUString& sPath ) +{ + if( maOpenedStreamMap.find( sPath ) == maOpenedStreamMap.end() ) + return sax_fastparser::FSHelperPtr(); + return maOpenedStreamMap[ sPath ].second; +} + +void XclExpXmlStream::WriteAttribute(sal_Int32 nAttr, std::u16string_view sVal) +{ + GetCurrentStream()->write(" ")->writeId(nAttr)->write("=\"")->writeEscaped(sVal)->write("\""); +} + +sax_fastparser::FSHelperPtr XclExpXmlStream::CreateOutputStream ( + const OUString& sFullStream, + std::u16string_view sRelativeStream, + const uno::Reference< XOutputStream >& xParentRelation, + const char* sContentType, + std::u16string_view sRelationshipType, + OUString* pRelationshipId ) +{ + OUString sRelationshipId; + if (xParentRelation.is()) + sRelationshipId = addRelation( xParentRelation, OUString(sRelationshipType), sRelativeStream ); + else + sRelationshipId = addRelation( OUString(sRelationshipType), sRelativeStream ); + + if( pRelationshipId ) + *pRelationshipId = sRelationshipId; + + sax_fastparser::FSHelperPtr p = openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) ); + + maOpenedStreamMap[ sFullStream ] = std::make_pair( sRelationshipId, p ); + + return p; +} + +bool XclExpXmlStream::importDocument() noexcept +{ + return false; +} + +oox::vml::Drawing* XclExpXmlStream::getVmlDrawing() +{ + return nullptr; +} + +const oox::drawingml::Theme* XclExpXmlStream::getCurrentTheme() const +{ + return nullptr; +} + +oox::drawingml::table::TableStyleListPtr XclExpXmlStream::getTableStyles() +{ + return oox::drawingml::table::TableStyleListPtr(); +} + +oox::drawingml::chart::ChartConverter* XclExpXmlStream::getChartConverter() +{ + // DO NOT CALL + return nullptr; +} + +ScDocShell* XclExpXmlStream::getDocShell() +{ + uno::Reference< XInterface > xModel( getModel(), UNO_QUERY ); + + ScModelObj *pObj = dynamic_cast < ScModelObj* >( xModel.get() ); + + if ( pObj ) + return static_cast < ScDocShell* >( pObj->GetEmbeddedObject() ); + + return nullptr; +} + +bool XclExpXmlStream::exportDocument() +{ + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + ScRefreshTimerProtector aProt(rDoc.GetRefreshTimerControlAddress()); + + const bool bValidateTabNames = officecfg::Office::Calc::Filter::Export::MS_Excel::TruncateLongSheetNames::get(); + std::vector<OUString> aOriginalTabNames; + if (bValidateTabNames) + { + validateTabNames(aOriginalTabNames); + } + + uno::Reference<task::XStatusIndicator> xStatusIndicator = getStatusIndicator(); + + if (xStatusIndicator.is()) + xStatusIndicator->start(ScResId(STR_SAVE_DOC), 100); + + // NOTE: Don't use SotStorage or SvStream any more, and never call + // SfxMedium::GetOutStream() anywhere in the xlsx export filter code! + // Instead, write via XOutputStream instance. + tools::SvRef<SotStorage> rStorage = static_cast<SotStorage*>(nullptr); + drawingml::DrawingML::ResetMlCounters(); + drawingml::DrawingML::PushExportGraphics(); + + XclExpRootData aData( + EXC_BIFF8, *pShell->GetMedium (), rStorage, rDoc, + msfilter::util::getBestTextEncodingFromLocale( + Application::GetSettings().GetLanguageTag().getLocale())); + aData.meOutput = EXC_OUTPUT_XML_2007; + aData.maXclMaxPos.Set( EXC_MAXCOL_XML_2007, EXC_MAXROW_XML_2007, EXC_MAXTAB_XML_2007 ); + aData.maMaxPos.SetCol( ::std::min( aData.maScMaxPos.Col(), aData.maXclMaxPos.Col() ) ); + aData.maMaxPos.SetRow( ::std::min( aData.maScMaxPos.Row(), aData.maXclMaxPos.Row() ) ); + aData.maMaxPos.SetTab( ::std::min( aData.maScMaxPos.Tab(), aData.maXclMaxPos.Tab() ) ); + aData.mpCompileFormulaCxt = std::make_shared<sc::CompileFormulaContext>(rDoc); + // set target path to get correct relative links to target document, not source + INetURLObject aPath(getFileUrl()); + aData.maBasePath = OUString("file:///" + aPath.GetPath() + "\\").replace('\\', '/') + // fix for Linux + .replaceFirst("file:////", "file:///"); + + XclExpRoot aRoot( aData ); + + mpRoot = &aRoot; + aRoot.GetOldRoot().pER = &aRoot; + aRoot.GetOldRoot().eDateiTyp = Biff8; + // Get the viewsettings before processing + if (ScViewData* pViewData = ScDocShell::GetViewData()) + pViewData->WriteExtOptions( mpRoot->GetExtDocOptions() ); + + OUString const workbook = "xl/workbook.xml"; + const char* pWorkbookContentType = nullptr; + if (mbExportVBA) + { + if (mbExportTemplate) + { + pWorkbookContentType = "application/vnd.ms-excel.template.macroEnabled.main+xml"; + } + else + { + pWorkbookContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"; + } + } + else + { + if (mbExportTemplate) + { + pWorkbookContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"; + } + else + { + pWorkbookContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; + } + } + + PushStream( CreateOutputStream( workbook, workbook, + uno::Reference <XOutputStream>(), + pWorkbookContentType, + oox::getRelationship(Relationship::OFFICEDOCUMENT) ) ); + + if (mbExportVBA) + { + VbaExport aExport(getModel()); + if (aExport.containsVBAProject()) + { + SvMemoryStream aVbaStream(4096, 4096); + tools::SvRef<SotStorage> pVBAStorage(new SotStorage(aVbaStream)); + aExport.exportVBA( pVBAStorage.get() ); + aVbaStream.Seek(0); + css::uno::Reference<css::io::XInputStream> xVBAStream( + new utl::OInputStreamWrapper(aVbaStream)); + css::uno::Reference<css::io::XOutputStream> xVBAOutput = + openFragmentStream("xl/vbaProject.bin", "application/vnd.ms-office.vbaProject"); + comphelper::OStorageHelper::CopyInputToOutput(xVBAStream, xVBAOutput); + + addRelation(GetCurrentStream()->getOutputStream(), oox::getRelationship(Relationship::VBAPROJECT), u"vbaProject.bin"); + } + } + + // destruct at the end of the block + { + ExcDocument aDocRoot( aRoot ); + if (xStatusIndicator.is()) + xStatusIndicator->setValue(10); + aDocRoot.ReadDoc(); + if (xStatusIndicator.is()) + xStatusIndicator->setValue(40); + aDocRoot.WriteXml( *this ); + rDoc.GetExternalRefManager()->disableSkipUnusedFileIds(); + } + + PopStream(); + // Free all FSHelperPtr, to flush data before committing storage + maOpenedStreamMap.clear(); + + commitStorage(); + + if (bValidateTabNames) + { + restoreTabNames(aOriginalTabNames); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + mpRoot = nullptr; + + drawingml::DrawingML::PopExportGraphics(); + + return true; +} + +::oox::ole::VbaProject* XclExpXmlStream::implCreateVbaProject() const +{ + return new ::oox::xls::ExcelVbaProject( getComponentContext(), uno::Reference< XSpreadsheetDocument >( getModel(), UNO_QUERY ) ); +} + +OUString XclExpXmlStream::getImplementationName() +{ + return "TODO"; +} + +void XclExpXmlStream::validateTabNames(std::vector<OUString>& aOriginalTabNames) +{ + const int MAX_TAB_NAME_LENGTH = 31; + + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + + // get original names + aOriginalTabNames.resize(rDoc.GetTableCount()); + for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++) + { + rDoc.GetName(nTab, aOriginalTabNames[nTab]); + } + + // new tab names + std::vector<OUString> aNewTabNames; + aNewTabNames.reserve(rDoc.GetTableCount()); + + // check and rename + for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++) + { + const OUString& rOriginalName = aOriginalTabNames[nTab]; + if (rOriginalName.getLength() > MAX_TAB_NAME_LENGTH) + { + OUString aNewName; + + // let's try just truncate "<first 31 chars>" + if (aNewName.isEmpty()) + { + aNewName = rOriginalName.copy(0, MAX_TAB_NAME_LENGTH); + if (aNewTabNames.end() != std::find(aNewTabNames.begin(), aNewTabNames.end(), aNewName) || + aOriginalTabNames.end() != std::find(aOriginalTabNames.begin(), aOriginalTabNames.end(), aNewName)) + { + // was found => let's use another tab name + aNewName.clear(); + } + } + + // let's try "<first N chars>-XXX" template + for (int digits=1; digits<10 && aNewName.isEmpty(); digits++) + { + const int rangeStart = pow(10, digits - 1); + const int rangeEnd = pow(10, digits); + + for (int i=rangeStart; i<rangeEnd && aNewName.isEmpty(); i++) + { + aNewName = OUString::Concat(rOriginalName.subView(0, MAX_TAB_NAME_LENGTH - 1 - digits)) + "-" + OUString::number(i); + if (aNewTabNames.end() != std::find(aNewTabNames.begin(), aNewTabNames.end(), aNewName) || + aOriginalTabNames.end() != std::find(aOriginalTabNames.begin(), aOriginalTabNames.end(), aNewName)) + { + // was found => let's use another tab name + aNewName.clear(); + } + } + } + + if (!aNewName.isEmpty()) + { + // new name was created => rename + renameTab(nTab, aNewName); + aNewTabNames.push_back(aNewName); + } + else + { + // default: do not rename + aNewTabNames.push_back(rOriginalName); + } + } + else + { + // default: do not rename + aNewTabNames.push_back(rOriginalName); + } + } +} + +void XclExpXmlStream::restoreTabNames(const std::vector<OUString>& aOriginalTabNames) +{ + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + + for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++) + { + const OUString& rOriginalName = aOriginalTabNames[nTab]; + + OUString rModifiedName; + rDoc.GetName(nTab, rModifiedName); + + if (rOriginalName != rModifiedName) + { + renameTab(nTab, rOriginalName); + } + } +} + +void XclExpXmlStream::renameTab(SCTAB aTab, OUString aNewName) +{ + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + + bool bAutoCalcShellDisabled = rDoc.IsAutoCalcShellDisabled(); + bool bIdleEnabled = rDoc.IsIdleEnabled(); + + rDoc.SetAutoCalcShellDisabled( true ); + rDoc.EnableIdle(false); + + if (rDoc.RenameTab(aTab, aNewName)) + { + SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScTablesChanged)); + } + + rDoc.SetAutoCalcShellDisabled( bAutoCalcShellDisabled ); + rDoc.EnableIdle(bIdleEnabled); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xestring.cxx b/sc/source/filter/excel/xestring.cxx new file mode 100644 index 000000000..295f37709 --- /dev/null +++ b/sc/source/filter/excel/xestring.cxx @@ -0,0 +1,565 @@ +/* -*- 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 <algorithm> +#include <cassert> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <tools/solar.h> +#include <xlstyle.hxx> +#include <xestyle.hxx> +#include <xestream.hxx> +#include <xestring.hxx> +#include <oox/token/tokens.hxx> + +using namespace ::oox; + +namespace { + +// compare vectors + +/** Compares two vectors. + @return A negative value, if rLeft<rRight; or a positive value, if rLeft>rRight; + or 0, if rLeft==rRight. */ +template< typename Type > +int lclCompareVectors( const ::std::vector< Type >& rLeft, const ::std::vector< Type >& rRight ) +{ + int nResult = 0; + + // 1st: compare all elements of the vectors + auto [aItL, aItR] = std::mismatch(rLeft.begin(), rLeft.end(), rRight.begin(), rRight.end()); + if ((aItL != rLeft.end()) && (aItR != rRight.end())) + nResult = static_cast< int >( *aItL ) - static_cast< int >( *aItR ); + else + // 2nd: compare the vector sizes. Shorter vector is less + nResult = static_cast< int >( rLeft.size() ) - static_cast< int >( rRight.size() ); + + return nResult; +} + +// hashing helpers + +/** Base class for value hashers. + @descr These function objects are used to hash any value to a sal_uInt32 value. */ +template< typename Type > +struct XclHasher {}; + +template< typename Type > +struct XclDirectHasher : public XclHasher< Type > +{ + sal_uInt32 operator()( Type nVal ) const { return nVal; } +}; + +struct XclFormatRunHasher : public XclHasher< const XclFormatRun& > +{ + sal_uInt32 operator()( const XclFormatRun& rRun ) const + { return (rRun.mnChar << 8) ^ rRun.mnFontIdx; } +}; + +/** Calculates a hash value from a vector. + @descr Uses the passed hasher function object to calculate hash values from + all vector elements. */ +template< typename Type, typename ValueHasher > +sal_uInt16 lclHashVector( const ::std::vector< Type >& rVec, const ValueHasher& rHasher ) +{ + sal_uInt32 nHash = rVec.size(); + for( const auto& rItem : rVec ) + nHash = (nHash * 31) + rHasher( rItem ); + return static_cast< sal_uInt16 >( nHash ^ (nHash >> 16) ); +} + +/** Calculates a hash value from a vector. Uses XclDirectHasher to hash the vector elements. */ +template< typename Type > +sal_uInt16 lclHashVector( const ::std::vector< Type >& rVec ) +{ + return lclHashVector( rVec, XclDirectHasher< Type >() ); +} + +} // namespace + +// constructors --------------------------------------------------------------- + +XclExpString::XclExpString( XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Init( 0, nFlags, nMaxLen, true ); +} + +XclExpString::XclExpString( const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Assign( rString, nFlags, nMaxLen ); +} + +// assign --------------------------------------------------------------------- + +void XclExpString::Assign( const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Build( rString.getStr(), rString.getLength(), nFlags, nMaxLen ); +} + +void XclExpString::Assign( sal_Unicode cChar ) +{ + Build( &cChar, 1, XclStrFlags::NONE, EXC_STR_MAXLEN ); +} + +void XclExpString::AssignByte( + std::u16string_view rString, rtl_TextEncoding eTextEnc, XclStrFlags nFlags, + sal_uInt16 nMaxLen ) +{ + // length may differ from length of rString + OString aByteStr(OUStringToOString(rString, eTextEnc)); + Build(aByteStr.getStr(), aByteStr.getLength(), nFlags, nMaxLen); +} + +// append --------------------------------------------------------------------- + +void XclExpString::Append( std::u16string_view rString ) +{ + BuildAppend( rString ); +} + +void XclExpString::AppendByte( std::u16string_view rString, rtl_TextEncoding eTextEnc ) +{ + if (!rString.empty()) + { + // length may differ from length of rString + OString aByteStr(OUStringToOString(rString, eTextEnc)); + BuildAppend(aByteStr); + } +} + +void XclExpString::AppendByte( sal_Unicode cChar, rtl_TextEncoding eTextEnc ) +{ + if( !cChar ) + { + char cByteChar = 0; + BuildAppend( std::string_view(&cByteChar, 1) ); + } + else + { + OString aByteStr( &cChar, 1, eTextEnc ); // length may be >1 + BuildAppend( aByteStr ); + } +} + +// formatting runs ------------------------------------------------------------ + +void XclExpString::AppendFormat( sal_uInt16 nChar, sal_uInt16 nFontIdx, bool bDropDuplicate ) +{ + OSL_ENSURE( maFormats.empty() || (maFormats.back().mnChar < nChar), "XclExpString::AppendFormat - invalid char index" ); + size_t nMaxSize = static_cast< size_t >( mbIsBiff8 ? EXC_STR_MAXLEN : EXC_STR_MAXLEN_8BIT ); + if( maFormats.empty() || ((maFormats.size() < nMaxSize) && (!bDropDuplicate || (maFormats.back().mnFontIdx != nFontIdx))) ) + maFormats.emplace_back( nChar, nFontIdx ); +} + +void XclExpString::AppendTrailingFormat( sal_uInt16 nFontIdx ) +{ + AppendFormat( mnLen, nFontIdx, false ); +} + +void XclExpString::LimitFormatCount( sal_uInt16 nMaxCount ) +{ + if( maFormats.size() > nMaxCount ) + maFormats.erase( maFormats.begin() + nMaxCount, maFormats.end() ); +} + +sal_uInt16 XclExpString::GetLeadingFont() +{ + sal_uInt16 nFontIdx = EXC_FONT_NOTFOUND; + if( !maFormats.empty() && (maFormats.front().mnChar == 0) ) + { + nFontIdx = maFormats.front().mnFontIdx; + } + return nFontIdx; +} + +sal_uInt16 XclExpString::RemoveLeadingFont() +{ + sal_uInt16 nFontIdx = GetLeadingFont(); + if( nFontIdx != EXC_FONT_NOTFOUND ) + { + maFormats.erase( maFormats.begin() ); + } + return nFontIdx; +} + +bool XclExpString::IsEqual( const XclExpString& rCmp ) const +{ + return + (mnLen == rCmp.mnLen) && + (mbIsBiff8 == rCmp.mbIsBiff8) && + (mbIsUnicode == rCmp.mbIsUnicode) && + (mbWrapped == rCmp.mbWrapped) && + ( + ( mbIsBiff8 && (maUniBuffer == rCmp.maUniBuffer)) || + (!mbIsBiff8 && (maCharBuffer == rCmp.maCharBuffer)) + ) && + (maFormats == rCmp.maFormats); +} + +bool XclExpString::IsLessThan( const XclExpString& rCmp ) const +{ + int nResult = mbIsBiff8 ? + lclCompareVectors( maUniBuffer, rCmp.maUniBuffer ) : + lclCompareVectors( maCharBuffer, rCmp.maCharBuffer ); + return (nResult != 0) ? (nResult < 0) : (maFormats < rCmp.maFormats); +} + +// get data ------------------------------------------------------------------- + +sal_uInt16 XclExpString::GetFormatsCount() const +{ + return static_cast< sal_uInt16 >( maFormats.size() ); +} + +sal_uInt8 XclExpString::GetFlagField() const +{ + return (mbIsUnicode ? EXC_STRF_16BIT : 0) | (IsWriteFormats() ? EXC_STRF_RICH : 0); +} + +sal_uInt16 XclExpString::GetHeaderSize() const +{ + return + (mb8BitLen ? 1 : 2) + // length field + (IsWriteFlags() ? 1 : 0) + // flag field + (IsWriteFormats() ? 2 : 0); // richtext formatting count +} + +std::size_t XclExpString::GetBufferSize() const +{ + return static_cast<std::size_t>(mnLen) * (mbIsUnicode ? 2 : 1); +} + +std::size_t XclExpString::GetSize() const +{ + return + GetHeaderSize() + // header + GetBufferSize() + // character buffer + (IsWriteFormats() ? (4 * GetFormatsCount()) : 0); // richtext formatting +} + +sal_uInt16 XclExpString::GetChar( sal_uInt16 nCharIdx ) const +{ + OSL_ENSURE( nCharIdx < Len(), "XclExpString::GetChar - invalid character index" ); + return static_cast< sal_uInt16 >( mbIsBiff8 ? maUniBuffer[ nCharIdx ] : maCharBuffer[ nCharIdx ] ); +} + +sal_uInt16 XclExpString::GetHash() const +{ + return + (mbIsBiff8 ? lclHashVector( maUniBuffer ) : lclHashVector( maCharBuffer )) ^ + lclHashVector( maFormats, XclFormatRunHasher() ); +} + +// streaming ------------------------------------------------------------------ + +void XclExpString::WriteLenField( XclExpStream& rStrm ) const +{ + if( mb8BitLen ) + rStrm << static_cast< sal_uInt8 >( mnLen ); + else + rStrm << mnLen; +} + +void XclExpString::WriteFlagField( XclExpStream& rStrm ) const +{ + if( mbIsBiff8 ) + { + PrepareWrite( rStrm, 1 ); + rStrm << GetFlagField(); + rStrm.SetSliceSize( 0 ); + } +} + +void XclExpString::WriteHeader( XclExpStream& rStrm ) const +{ + OSL_ENSURE( !mb8BitLen || (mnLen < 256), "XclExpString::WriteHeader - string too long" ); + PrepareWrite( rStrm, GetHeaderSize() ); + // length + WriteLenField( rStrm ); + // flag field + if( IsWriteFlags() ) + rStrm << GetFlagField(); + // format run count + if( IsWriteFormats() ) + rStrm << GetFormatsCount(); + rStrm.SetSliceSize( 0 ); +} + +void XclExpString::WriteBuffer( XclExpStream& rStrm ) const +{ + if( mbIsBiff8 ) + rStrm.WriteUnicodeBuffer( maUniBuffer, GetFlagField() ); + else + rStrm.WriteCharBuffer( maCharBuffer ); +} + +void XclExpString::WriteFormats( XclExpStream& rStrm, bool bWriteSize ) const +{ + if( !IsRich() ) + return; + + if( mbIsBiff8 ) + { + if( bWriteSize ) + rStrm << GetFormatsCount(); + rStrm.SetSliceSize( 4 ); + for( const auto& rFormat : maFormats ) + rStrm << rFormat.mnChar << rFormat.mnFontIdx; + } + else + { + if( bWriteSize ) + rStrm << static_cast< sal_uInt8 >( GetFormatsCount() ); + rStrm.SetSliceSize( 2 ); + for( const auto& rFormat : maFormats ) + rStrm << static_cast< sal_uInt8 >( rFormat.mnChar ) << static_cast< sal_uInt8 >( rFormat.mnFontIdx ); + } + rStrm.SetSliceSize( 0 ); +} + +void XclExpString::Write( XclExpStream& rStrm ) const +{ + if (!mbSkipHeader) + WriteHeader( rStrm ); + WriteBuffer( rStrm ); + if( IsWriteFormats() ) // only in BIFF8 included in string + WriteFormats( rStrm ); +} + +void XclExpString::WriteHeaderToMem( sal_uInt8* pnMem ) const +{ + assert(pnMem); + OSL_ENSURE( !mb8BitLen || (mnLen < 256), "XclExpString::WriteHeaderToMem - string too long" ); + OSL_ENSURE( !IsWriteFormats(), "XclExpString::WriteHeaderToMem - formatted strings not supported" ); + // length + if( mb8BitLen ) + { + *pnMem = static_cast< sal_uInt8 >( mnLen ); + ++pnMem; + } + else + { + ShortToSVBT16( mnLen, pnMem ); + pnMem += 2; + } + // flag field + if( IsWriteFlags() ) + *pnMem = GetFlagField(); +} + +void XclExpString::WriteBufferToMem( sal_uInt8* pnMem ) const +{ + assert(pnMem); + if( IsEmpty() ) + return; + + if( mbIsBiff8 ) + { + for( const sal_uInt16 nChar : maUniBuffer ) + { + *pnMem = static_cast< sal_uInt8 >( nChar ); + ++pnMem; + if( mbIsUnicode ) + { + *pnMem = static_cast< sal_uInt8 >( nChar >> 8 ); + ++pnMem; + } + } + } + else + memcpy( pnMem, maCharBuffer.data(), mnLen ); +} + +void XclExpString::WriteToMem( sal_uInt8* pnMem ) const +{ + WriteHeaderToMem( pnMem ); + WriteBufferToMem( pnMem + GetHeaderSize() ); +} + +static sal_uInt16 lcl_WriteRun( XclExpXmlStream& rStrm, const ScfUInt16Vec& rBuffer, sal_uInt16 nStart, sal_Int32 nLength, const XclExpFont* pFont ) +{ + if( nLength == 0 ) + return nStart; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(XML_r); + if( pFont ) + { + const XclFontData& rFontData = pFont->GetFontData(); + rWorksheet->startElement(XML_rPr); + XclXmlUtils::WriteFontData( rWorksheet, rFontData, XML_rFont ); + rWorksheet->endElement( XML_rPr ); + } + rWorksheet->startElement(XML_t, FSNS(XML_xml, XML_space), "preserve"); + rWorksheet->writeEscaped( XclXmlUtils::ToOUString( rBuffer, nStart, nLength ) ); + rWorksheet->endElement( XML_t ); + rWorksheet->endElement( XML_r ); + return nStart + nLength; +} + +void XclExpString::WriteXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr rWorksheet = rStrm.GetCurrentStream(); + + if( !IsWriteFormats() ) + { + rWorksheet->startElement(XML_t, FSNS(XML_xml, XML_space), "preserve"); + rWorksheet->writeEscaped( XclXmlUtils::ToOUString( *this ) ); + rWorksheet->endElement( XML_t ); + } + else + { + XclExpFontBuffer& rFonts = rStrm.GetRoot().GetFontBuffer(); + + sal_uInt16 nStart = 0; + const XclExpFont* pFont = nullptr; + for ( const auto& rFormat : maFormats ) + { + nStart = lcl_WriteRun( rStrm, GetUnicodeBuffer(), + nStart, rFormat.mnChar-nStart, pFont ); + pFont = rFonts.GetFont( rFormat.mnFontIdx ); + } + lcl_WriteRun( rStrm, GetUnicodeBuffer(), + nStart, GetUnicodeBuffer().size() - nStart, pFont ); + } +} + +bool XclExpString::IsWriteFlags() const +{ + return mbIsBiff8 && (!IsEmpty() || !mbSmartFlags); +} + +bool XclExpString::IsWriteFormats() const +{ + return mbIsBiff8 && !mbSkipFormats && IsRich(); +} + +void XclExpString::SetStrLen( sal_Int32 nNewLen ) +{ + sal_uInt16 nAllowedLen = (mb8BitLen && (mnMaxLen > 255)) ? 255 : mnMaxLen; + mnLen = limit_cast< sal_uInt16 >( nNewLen, 0, nAllowedLen ); +} + +void XclExpString::CharsToBuffer( const sal_Unicode* pcSource, sal_Int32 nBegin, sal_Int32 nLen ) +{ + OSL_ENSURE( maUniBuffer.size() >= o3tl::make_unsigned( nBegin + nLen ), + "XclExpString::CharsToBuffer - char buffer invalid" ); + ScfUInt16Vec::iterator aBeg = maUniBuffer.begin() + nBegin; + ScfUInt16Vec::iterator aEnd = aBeg + nLen; + const sal_Unicode* pcSrcChar = pcSource; + for( ScfUInt16Vec::iterator aIt = aBeg; aIt != aEnd; ++aIt, ++pcSrcChar ) + { + *aIt = static_cast< sal_uInt16 >( *pcSrcChar ); + if( *aIt & 0xFF00 ) + mbIsUnicode = true; + } + if( !mbWrapped ) + mbWrapped = ::std::find( aBeg, aEnd, EXC_LF ) != aEnd; +} + +void XclExpString::CharsToBuffer( const char* pcSource, sal_Int32 nBegin, sal_Int32 nLen ) +{ + OSL_ENSURE( maCharBuffer.size() >= o3tl::make_unsigned( nBegin + nLen ), + "XclExpString::CharsToBuffer - char buffer invalid" ); + ScfUInt8Vec::iterator aBeg = maCharBuffer.begin() + nBegin; + ScfUInt8Vec::iterator aEnd = aBeg + nLen; + const char* pcSrcChar = pcSource; + for( ScfUInt8Vec::iterator aIt = aBeg; aIt != aEnd; ++aIt, ++pcSrcChar ) + *aIt = static_cast< sal_uInt8 >( *pcSrcChar ); + mbIsUnicode = false; + if( !mbWrapped ) + mbWrapped = ::std::find( aBeg, aEnd, EXC_LF_C ) != aEnd; +} + +void XclExpString::Init( sal_Int32 nCurrLen, XclStrFlags nFlags, sal_uInt16 nMaxLen, bool bBiff8 ) +{ + mbIsBiff8 = bBiff8; + mbIsUnicode = bBiff8 && ( nFlags & XclStrFlags::ForceUnicode ); + mb8BitLen = bool( nFlags & XclStrFlags::EightBitLength ); + mbSmartFlags = bBiff8 && ( nFlags & XclStrFlags::SmartFlags ); + mbSkipFormats = bool( nFlags & XclStrFlags::SeparateFormats ); + mbWrapped = false; + mbSkipHeader = bool( nFlags & XclStrFlags::NoHeader ); + mnMaxLen = nMaxLen; + SetStrLen( nCurrLen ); + + maFormats.clear(); + if( mbIsBiff8 ) + { + maCharBuffer.clear(); + maUniBuffer.resize( mnLen ); + } + else + { + maUniBuffer.clear(); + maCharBuffer.resize( mnLen ); + } +} + +void XclExpString::Build( const sal_Unicode* pcSource, sal_Int32 nCurrLen, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Init( nCurrLen, nFlags, nMaxLen, true ); + CharsToBuffer( pcSource, 0, mnLen ); +} + +void XclExpString::Build( const char* pcSource, sal_Int32 nCurrLen, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Init( nCurrLen, nFlags, nMaxLen, false ); + CharsToBuffer( pcSource, 0, mnLen ); +} + +void XclExpString::InitAppend( sal_Int32 nAddLen ) +{ + SetStrLen( static_cast< sal_Int32 >( mnLen ) + nAddLen ); + if( mbIsBiff8 ) + maUniBuffer.resize( mnLen ); + else + maCharBuffer.resize( mnLen ); +} + +void XclExpString::BuildAppend( std::u16string_view rSource ) +{ + OSL_ENSURE( mbIsBiff8, "XclExpString::BuildAppend - must not be called at byte strings" ); + if( mbIsBiff8 ) + { + sal_uInt16 nOldLen = mnLen; + InitAppend( rSource.size() ); + CharsToBuffer( rSource.data(), nOldLen, mnLen - nOldLen ); + } +} + +void XclExpString::BuildAppend( std::string_view rSource ) +{ + OSL_ENSURE( !mbIsBiff8, "XclExpString::BuildAppend - must not be called at unicode strings" ); + if( !mbIsBiff8 ) + { + sal_uInt16 nOldLen = mnLen; + InitAppend( rSource.size() ); + CharsToBuffer( rSource.data(), nOldLen, mnLen - nOldLen ); + } +} + +void XclExpString::PrepareWrite( XclExpStream& rStrm, sal_uInt16 nBytes ) const +{ + rStrm.SetSliceSize( nBytes + (mbIsUnicode ? 2 : 1) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xestyle.cxx b/sc/source/filter/excel/xestyle.cxx new file mode 100644 index 000000000..1e9c426a3 --- /dev/null +++ b/sc/source/filter/excel/xestyle.cxx @@ -0,0 +1,3306 @@ +/* -*- 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 <memory> +#include <xestyle.hxx> + +#include <algorithm> +#include <iterator> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <comphelper/processfactory.hxx> +#include <rtl/tencinfo.h> +#include <vcl/font.hxx> +#include <svl/languageoptions.hxx> +#include <scitems.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/langitem.hxx> +#include <document.hxx> +#include <stlpool.hxx> +#include <stlsheet.hxx> +#include <patattr.hxx> +#include <attrib.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <xestring.hxx> +#include <xltools.hxx> +#include <conditio.hxx> +#include <dbdata.hxx> +#include <filterentries.hxx> + +#include <o3tl/safeint.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/tokens.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/relationship.hxx> +#include <svl/numformat.hxx> + +using namespace ::com::sun::star; +using namespace oox; + +// PALETTE record - color information ========================================= + +namespace { + +sal_uInt32 lclGetWeighting( XclExpColorType eType ) +{ + switch( eType ) + { + case EXC_COLOR_CHARTLINE: return 1; + case EXC_COLOR_CELLBORDER: + case EXC_COLOR_CHARTAREA: return 2; + case EXC_COLOR_CELLTEXT: + case EXC_COLOR_CHARTTEXT: + case EXC_COLOR_CTRLTEXT: return 10; + case EXC_COLOR_TABBG: + case EXC_COLOR_CELLAREA: return 20; + case EXC_COLOR_GRID: return 50; + default: OSL_FAIL( "lclGetWeighting - unknown color type" ); + } + return 1; +} + +sal_Int32 lclGetColorDistance( const Color& rColor1, const Color& rColor2 ) +{ + sal_Int32 nDist = rColor1.GetRed() - rColor2.GetRed(); + nDist *= nDist * 77; + sal_Int32 nDummy = rColor1.GetGreen() - rColor2.GetGreen(); + nDist += nDummy * nDummy * 151; + nDummy = rColor1.GetBlue() - rColor2.GetBlue(); + nDist += nDummy * nDummy * 28; + return nDist; +} + +sal_uInt8 lclGetMergedColorComp( sal_uInt8 nComp1, sal_uInt32 nWeight1, sal_uInt8 nComp2, sal_uInt32 nWeight2 ) +{ + sal_uInt8 nComp1Dist = ::std::min< sal_uInt8 >( nComp1, 0xFF - nComp1 ); + sal_uInt8 nComp2Dist = ::std::min< sal_uInt8 >( nComp2, 0xFF - nComp2 ); + if( nComp1Dist != nComp2Dist ) + { + /* #i36945# One of the passed RGB components is nearer at the limits (0x00 or 0xFF). + Increase its weighting to prevent fading of the colors during reduction. */ + const sal_uInt8& rnCompNearer = (nComp1Dist < nComp2Dist) ? nComp1 : nComp2; + sal_uInt32& rnWeight = (nComp1Dist < nComp2Dist) ? nWeight1 : nWeight2; + rnWeight *= ((rnCompNearer - 0x80L) * (rnCompNearer - 0x7FL) / 0x1000L + 1); + } + sal_uInt32 nWSum = nWeight1 + nWeight2; + return static_cast< sal_uInt8 >( (nComp1 * nWeight1 + nComp2 * nWeight2 + nWSum / 2) / nWSum ); +} + +void lclSetMixedColor( Color& rDest, const Color& rSrc1, const Color& rSrc2 ) +{ + rDest.SetRed( static_cast< sal_uInt8 >( (static_cast< sal_uInt16 >( rSrc1.GetRed() ) + rSrc2.GetRed()) / 2 ) ); + rDest.SetGreen( static_cast< sal_uInt8 >( (static_cast< sal_uInt16 >( rSrc1.GetGreen() ) + rSrc2.GetGreen()) / 2 ) ); + rDest.SetBlue( static_cast< sal_uInt8 >( (static_cast< sal_uInt16 >( rSrc1.GetBlue() ) + rSrc2.GetBlue()) / 2 ) ); +} + +} // namespace + +// additional classes for color reduction ------------------------------------- + +namespace { + +/** Represents an entry in a color list. + + The color stores a weighting value, which increases the more the color is + used in the document. Heavy-weighted colors will change less than others on + color reduction. + */ +class XclListColor +{ +private: + Color maColor; /// The color value of this palette entry. + sal_uInt32 mnColorId; /// Unique color ID for color reduction. + sal_uInt32 mnWeight; /// Weighting for color reduction. + bool mbBaseColor; /// true = Handle as base color, (don't remove/merge). + +public: + explicit XclListColor( const Color& rColor, sal_uInt32 nColorId ); + + /** Returns the RGB color value of the color. */ + const Color& GetColor() const { return maColor; } + /** Returns the unique ID of the color. */ + sal_uInt32 GetColorId() const { return mnColorId; } + /** Returns the current weighting of the color. */ + sal_uInt32 GetWeighting() const { return mnWeight; } + /** Returns true, if this color is a base color, i.e. it will not be removed or merged. */ + bool IsBaseColor() const { return mbBaseColor; } + + /** Adds the passed weighting to this color. */ + void AddWeighting( sal_uInt32 nWeight ) { mnWeight += nWeight; } + /** Merges this color with rColor, regarding weighting settings. */ + void Merge( const XclListColor& rColor ); +}; + +XclListColor::XclListColor( const Color& rColor, sal_uInt32 nColorId ) : + maColor( rColor ), + mnColorId( nColorId ), + mnWeight( 0 ) +{ + mbBaseColor = + ((rColor.GetRed() == 0x00) || (rColor.GetRed() == 0xFF)) && + ((rColor.GetGreen() == 0x00) || (rColor.GetGreen() == 0xFF)) && + ((rColor.GetBlue() == 0x00) || (rColor.GetBlue() == 0xFF)); +} + +void XclListColor::Merge( const XclListColor& rColor ) +{ + sal_uInt32 nWeight2 = rColor.GetWeighting(); + // do not change RGB value of base colors + if( !mbBaseColor ) + { + maColor.SetRed( lclGetMergedColorComp( maColor.GetRed(), mnWeight, rColor.maColor.GetRed(), nWeight2 ) ); + maColor.SetGreen( lclGetMergedColorComp( maColor.GetGreen(), mnWeight, rColor.maColor.GetGreen(), nWeight2 ) ); + maColor.SetBlue( lclGetMergedColorComp( maColor.GetBlue(), mnWeight, rColor.maColor.GetBlue(), nWeight2 ) ); + } + AddWeighting( nWeight2 ); +} + +/** Data for each inserted original color, represented by a color ID. */ +struct XclColorIdData +{ + Color maColor; /// The original inserted color. + sal_uInt32 mnIndex; /// Maps current color ID to color list or export color vector. + /** Sets the contents of this struct. */ + void Set( const Color& rColor, sal_uInt32 nIndex ) { maColor = rColor; mnIndex = nIndex; } +}; + +/** A color that will be written to the Excel file. */ +struct XclPaletteColor +{ + Color maColor; /// Resulting color to export. + bool mbUsed; /// true = Entry is used in the document. + + explicit XclPaletteColor( const Color& rColor ) : maColor( rColor ), mbUsed( false ) {} + void SetColor( const Color& rColor ) { maColor = rColor; mbUsed = true; } +}; + +/** Maps a color list index to a palette index. + @descr Used to remap the color ID data vector from list indexes to palette indexes. */ +struct XclRemap +{ + sal_uInt32 mnPalIndex; /// Index to palette. + bool mbProcessed; /// true = List color already processed. + + explicit XclRemap() : mnPalIndex( 0 ), mbProcessed( false ) {} + void SetIndex( sal_uInt32 nPalIndex ) + { mnPalIndex = nPalIndex; mbProcessed = true; } +}; + +/** Stores the nearest palette color index of a list color. */ +struct XclNearest +{ + sal_uInt32 mnPalIndex; /// Index to nearest palette color. + sal_Int32 mnDist; /// Distance to palette color. + + explicit XclNearest() : mnPalIndex( 0 ), mnDist( 0 ) {} +}; + +} // namespace + +class XclExpPaletteImpl +{ +public: + explicit XclExpPaletteImpl( const XclDefaultPalette& rDefPal ); + + /** Inserts the color into the list and updates weighting. + @param nAutoDefault The Excel palette index for automatic color. + @return A unique ID for this color. */ + sal_uInt32 InsertColor( const Color& rColor, XclExpColorType eType, sal_uInt16 nAutoDefault = 0 ); + /** Returns the color ID representing a fixed Excel palette index (i.e. for auto colors). */ + static sal_uInt32 GetColorIdFromIndex( sal_uInt16 nIndex ); + + /** Reduces the color list to the maximum count of the current BIFF version. */ + void Finalize(); + + /** Returns the Excel palette index of the color with passed color ID. */ + sal_uInt16 GetColorIndex( sal_uInt32 nColorId ) const; + + /** Returns a foreground and background color for the two passed color IDs. + @descr If rnXclPattern contains a solid pattern, this function tries to find + the two best fitting colors and a mix pattern (25%, 50% or 75%) for nForeColorId. + This will result in a better approximation to the passed foreground color. */ + void GetMixedColors( + sal_uInt16& rnXclForeIx, sal_uInt16& rnXclBackIx, sal_uInt8& rnXclPattern, + sal_uInt32 nForeColorId, sal_uInt32 nBackColorId ) const; + + /** Returns the RGB color for a (non-zero-based) Excel palette entry. + @return The color from current or default palette or COL_AUTO, if nothing else found. */ + Color GetColor( sal_uInt16 nXclIndex ) const; + + /** Returns true, if all colors of the palette are equal to default palette colors. */ + bool IsDefaultPalette() const; + /** Writes the color list (contents of the palette record) to the passed stream. */ + void WriteBody( XclExpStream& rStrm ); + void SaveXml( XclExpXmlStream& rStrm ); + +private: + /** Returns the Excel index of a 0-based color index. */ + static sal_uInt16 GetXclIndex( sal_uInt32 nIndex ) + { return static_cast< sal_uInt16 >( nIndex + EXC_COLOR_USEROFFSET ); } + + /** Returns the original inserted color represented by the color ID nColorId. */ + const Color& GetOriginalColor( sal_uInt32 nColorId ) const; + + /** Searches for rColor, returns the ordered insertion index for rColor in rnIndex. */ + XclListColor* SearchListEntry( const Color& rColor, sal_uInt32& rnIndex ); + /** Creates and inserts a new color list entry at the specified list position. */ + XclListColor* CreateListEntry( const Color& rColor, sal_uInt32 nIndex ); + + /** Raw and fast reduction of the palette. */ + void RawReducePalette( sal_uInt32 nPass ); + /** Reduction of one color using advanced color merging based on color weighting. */ + void ReduceLeastUsedColor(); + + /** Finds the least used color and returns its current list index. */ + sal_uInt32 GetLeastUsedListColor() const; + /** Returns the list index of the color nearest to rColor. + @param nIgnore List index of a color which will be ignored. + @return The list index of the found color. */ + sal_uInt32 GetNearestListColor( const Color& rColor, sal_uInt32 nIgnore ) const; + /** Returns the list index of the color nearest to the color with list index nIndex. */ + sal_uInt32 GetNearestListColor( sal_uInt32 nIndex ) const; + + /** Returns in rnIndex the palette index of the color nearest to rColor. + Searches for default colors only (colors never replaced). + @return The distance from passed color to found color. */ + sal_Int32 GetNearestPaletteColor( + sal_uInt32& rnIndex, + const Color& rColor ) const; + /** Returns in rnFirst and rnSecond the palette indexes of the two colors nearest to rColor. + @return The minimum distance from passed color to found colors. */ + sal_Int32 GetNearPaletteColors( + sal_uInt32& rnFirst, sal_uInt32& rnSecond, + const Color& rColor ) const; + +private: + typedef std::vector< std::unique_ptr<XclListColor> > XclListColorList; + typedef std::shared_ptr< XclListColorList > XclListColorListRef; + + const XclDefaultPalette& mrDefPal; /// The default palette for the current BIFF version. + XclListColorListRef mxColorList; /// Working color list. + std::vector< XclColorIdData > + maColorIdDataVec; /// Data of all CIDs. + std::vector< XclPaletteColor > + maPalette; /// Contains resulting colors to export. + sal_uInt32 mnLastIdx; /// Last insertion index for search opt. +}; + +const sal_uInt32 EXC_PAL_INDEXBASE = 0xFFFF0000; +const sal_uInt32 EXC_PAL_MAXRAWSIZE = 1024; + +XclExpPaletteImpl::XclExpPaletteImpl( const XclDefaultPalette& rDefPal ) : + mrDefPal( rDefPal ), + mxColorList( std::make_shared<XclListColorList>() ), + mnLastIdx( 0 ) +{ + // initialize maPalette with default colors + sal_uInt16 nCount = static_cast< sal_uInt16 >( mrDefPal.GetColorCount() ); + maPalette.reserve( nCount ); + for( sal_uInt16 nIdx = 0; nIdx < nCount; ++nIdx ) + maPalette.emplace_back( mrDefPal.GetDefColor( GetXclIndex( nIdx ) ) ); + + InsertColor( COL_BLACK, EXC_COLOR_CELLTEXT ); +} + +sal_uInt32 XclExpPaletteImpl::InsertColor( const Color& rColor, XclExpColorType eType, sal_uInt16 nAutoDefault ) +{ + if( rColor == COL_AUTO ) + return GetColorIdFromIndex( nAutoDefault ); + + sal_uInt32 nFoundIdx = 0; + XclListColor* pEntry = SearchListEntry( rColor, nFoundIdx ); + if( !pEntry || (pEntry->GetColor() != rColor) ) + pEntry = CreateListEntry( rColor, nFoundIdx ); + pEntry->AddWeighting( lclGetWeighting( eType ) ); + + return pEntry->GetColorId(); +} + +sal_uInt32 XclExpPaletteImpl::GetColorIdFromIndex( sal_uInt16 nIndex ) +{ + return EXC_PAL_INDEXBASE | nIndex; +} + +void XclExpPaletteImpl::Finalize() +{ +// --- build initial color ID data vector (maColorIdDataVec) --- + + sal_uInt32 nCount = mxColorList->size(); + maColorIdDataVec.resize( nCount ); + for( sal_uInt32 nIdx = 0; nIdx < nCount; ++nIdx ) + { + const XclListColor& listColor = *mxColorList->at( nIdx ); + maColorIdDataVec[ listColor.GetColorId() ].Set( listColor.GetColor(), nIdx ); + } + +// --- loop as long as current color count does not fit into palette of current BIFF --- + + // phase 1: raw reduction (performance reasons, #i36945#) + sal_uInt32 nPass = 0; + while( mxColorList->size() > EXC_PAL_MAXRAWSIZE ) + RawReducePalette( nPass++ ); + + // phase 2: precise reduction using advanced color merging based on color weighting + while( mxColorList->size() > mrDefPal.GetColorCount() ) + ReduceLeastUsedColor(); + +// --- use default palette and replace colors with nearest used colors --- + + nCount = mxColorList->size(); + std::vector< XclRemap > aRemapVec( nCount ); + std::vector< XclNearest > aNearestVec( nCount ); + + // in each run: search the best fitting color and replace a default color with it + for( sal_uInt32 nRun = 0; nRun < nCount; ++nRun ) + { + sal_uInt32 nIndex; + // find nearest unused default color for each unprocessed list color + for( nIndex = 0; nIndex < nCount; ++nIndex ) + aNearestVec[ nIndex ].mnDist = aRemapVec[ nIndex ].mbProcessed ? SAL_MAX_INT32 : + GetNearestPaletteColor( aNearestVec[ nIndex ].mnPalIndex, mxColorList->at( nIndex )->GetColor() ); + // find the list color which is nearest to a default color + sal_uInt32 nFound = 0; + for( nIndex = 1; nIndex < nCount; ++nIndex ) + if( aNearestVec[ nIndex ].mnDist < aNearestVec[ nFound ].mnDist ) + nFound = nIndex; + // replace default color with list color + sal_uInt32 nNearest = aNearestVec[ nFound ].mnPalIndex; + OSL_ENSURE( nNearest < maPalette.size(), "XclExpPaletteImpl::Finalize - algorithm error" ); + maPalette[ nNearest ].SetColor( mxColorList->at( nFound )->GetColor() ); + aRemapVec[ nFound ].SetIndex( nNearest ); + } + + // remap color ID data map (maColorIdDataVec) from list indexes to palette indexes + for( auto& rColorIdData : maColorIdDataVec ) + rColorIdData.mnIndex = aRemapVec[ rColorIdData.mnIndex ].mnPalIndex; +} + +sal_uInt16 XclExpPaletteImpl::GetColorIndex( sal_uInt32 nColorId ) const +{ + sal_uInt16 nRet = 0; + if( nColorId >= EXC_PAL_INDEXBASE ) + nRet = static_cast< sal_uInt16 >( nColorId & ~EXC_PAL_INDEXBASE ); + else if( nColorId < maColorIdDataVec.size() ) + nRet = GetXclIndex( maColorIdDataVec[ nColorId ].mnIndex ); + return nRet; +} + +void XclExpPaletteImpl::GetMixedColors( + sal_uInt16& rnXclForeIx, sal_uInt16& rnXclBackIx, sal_uInt8& rnXclPattern, + sal_uInt32 nForeColorId, sal_uInt32 nBackColorId ) const +{ + rnXclForeIx = GetColorIndex( nForeColorId ); + rnXclBackIx = GetColorIndex( nBackColorId ); + if( (rnXclPattern != EXC_PATT_SOLID) || (nForeColorId >= maColorIdDataVec.size()) ) + return; + + // now we have solid pattern, and a defined foreground (background doesn't care for solid pattern) + + sal_uInt32 nIndex1, nIndex2; + Color aForeColor( GetOriginalColor( nForeColorId ) ); + sal_Int32 nFirstDist = GetNearPaletteColors( nIndex1, nIndex2, aForeColor ); + if( (nIndex1 >= maPalette.size()) || (nIndex2 >= maPalette.size()) ) + return; + + Color aColorArr[ 5 ]; + aColorArr[ 0 ] = maPalette[ nIndex1 ].maColor; + aColorArr[ 4 ] = maPalette[ nIndex2 ].maColor; + lclSetMixedColor( aColorArr[ 2 ], aColorArr[ 0 ], aColorArr[ 4 ] ); + lclSetMixedColor( aColorArr[ 1 ], aColorArr[ 0 ], aColorArr[ 2 ] ); + lclSetMixedColor( aColorArr[ 3 ], aColorArr[ 2 ], aColorArr[ 4 ] ); + + sal_Int32 nMinDist = nFirstDist; + sal_uInt32 nMinIndex = 0; + for( sal_uInt32 nCnt = 1; nCnt < 4; ++nCnt ) + { + sal_Int32 nDist = lclGetColorDistance( aForeColor, aColorArr[ nCnt ] ); + if( nDist < nMinDist ) + { + nMinDist = nDist; + nMinIndex = nCnt; + } + } + rnXclForeIx = GetXclIndex( nIndex1 ); + rnXclBackIx = GetXclIndex( nIndex2 ); + if( nMinDist < nFirstDist ) + { + switch( nMinIndex ) + { + case 1: rnXclPattern = EXC_PATT_75_PERC; break; + case 2: rnXclPattern = EXC_PATT_50_PERC; break; + case 3: rnXclPattern = EXC_PATT_25_PERC; break; + } + } +} + +Color XclExpPaletteImpl::GetColor( sal_uInt16 nXclIndex ) const +{ + if( nXclIndex >= EXC_COLOR_USEROFFSET ) + { + sal_uInt32 nIdx = nXclIndex - EXC_COLOR_USEROFFSET; + if( nIdx < maPalette.size() ) + return maPalette[ nIdx ].maColor; + } + return mrDefPal.GetDefColor( nXclIndex ); +} + +bool XclExpPaletteImpl::IsDefaultPalette() const +{ + bool bDefault = true; + for( sal_uInt32 nIdx = 0, nSize = static_cast< sal_uInt32 >( maPalette.size() ); bDefault && (nIdx < nSize); ++nIdx ) + bDefault = maPalette[ nIdx ].maColor == mrDefPal.GetDefColor( GetXclIndex( nIdx ) ); + return bDefault; +} + +void XclExpPaletteImpl::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt16 >( maPalette.size() ); + for( const auto& rColor : maPalette ) + rStrm << rColor.maColor; +} + +void XclExpPaletteImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maPalette.empty() ) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_colors); + rStyleSheet->startElement(XML_indexedColors); + for( const auto& rColor : maPalette ) + rStyleSheet->singleElement(XML_rgbColor, XML_rgb, XclXmlUtils::ToOString(rColor.maColor)); + rStyleSheet->endElement( XML_indexedColors ); + rStyleSheet->endElement( XML_colors ); +} + +const Color& XclExpPaletteImpl::GetOriginalColor( sal_uInt32 nColorId ) const +{ + if( nColorId < maColorIdDataVec.size() ) + return maColorIdDataVec[ nColorId ].maColor; + return maPalette[ 0 ].maColor; +} + +XclListColor* XclExpPaletteImpl::SearchListEntry( const Color& rColor, sal_uInt32& rnIndex ) +{ + rnIndex = 0; + + if (mxColorList->empty()) + return nullptr; + + XclListColor* pEntry = nullptr; + + // search optimization for equal-colored objects occurring repeatedly + if (mnLastIdx < mxColorList->size()) + { + pEntry = (*mxColorList)[mnLastIdx].get(); + if( pEntry->GetColor() == rColor ) + { + rnIndex = mnLastIdx; + return pEntry; + } + } + + // binary search for color + sal_uInt32 nBegIdx = 0; + sal_uInt32 nEndIdx = mxColorList->size(); + bool bFound = false; + while( !bFound && (nBegIdx < nEndIdx) ) + { + rnIndex = (nBegIdx + nEndIdx) / 2; + pEntry = (*mxColorList)[rnIndex].get(); + bFound = pEntry->GetColor() == rColor; + if( !bFound ) + { + if( pEntry->GetColor() < rColor ) + nBegIdx = rnIndex + 1; + else + nEndIdx = rnIndex; + } + } + + // not found - use end of range as new insertion position + if( !bFound ) + rnIndex = nEndIdx; + + mnLastIdx = rnIndex; + return pEntry; +} + +XclListColor* XclExpPaletteImpl::CreateListEntry( const Color& rColor, sal_uInt32 nIndex ) +{ + XclListColor* pEntry = new XclListColor( rColor, mxColorList->size() ); + mxColorList->insert(mxColorList->begin() + nIndex, std::unique_ptr<XclListColor>(pEntry)); + return pEntry; +} + +void XclExpPaletteImpl::RawReducePalette( sal_uInt32 nPass ) +{ + /* Fast palette reduction - in each call of this function one RGB component + of each color is reduced to a lower number of distinct values. + Pass 0: Blue is reduced to 128 distinct values. + Pass 1: Red is reduced to 128 distinct values. + Pass 2: Green is reduced to 128 distinct values. + Pass 3: Blue is reduced to 64 distinct values. + Pass 4: Red is reduced to 64 distinct values. + Pass 5: Green is reduced to 64 distinct values. + And so on... + */ + + XclListColorListRef xOldList = mxColorList; + mxColorList = std::make_shared<XclListColorList>(); + + // maps old list indexes to new list indexes, used to update maColorIdDataVec + ScfUInt32Vec aListIndexMap; + aListIndexMap.reserve( xOldList->size() ); + + // preparations + sal_uInt8 nR, nG, nB; + sal_uInt8& rnComp = ((nPass % 3 == 0) ? nB : ((nPass % 3 == 1) ? nR : nG)); + nPass /= 3; + OSL_ENSURE( nPass < 7, "XclExpPaletteImpl::RawReducePalette - reduction not terminated" ); + + static const sal_uInt8 spnFactor2[] = { 0x81, 0x82, 0x84, 0x88, 0x92, 0xAA, 0xFF }; + sal_uInt8 nFactor1 = static_cast< sal_uInt8 >( 0x02 << nPass ); + sal_uInt8 nFactor2 = spnFactor2[ nPass ]; + sal_uInt8 nFactor3 = static_cast< sal_uInt8 >( 0x40 >> nPass ); + + // process each color in the old color list + for(const std::unique_ptr<XclListColor> & pOldColor : *xOldList) + { + // get the old list entry + const XclListColor* pOldEntry = pOldColor.get(); + nR = pOldEntry->GetColor().GetRed(); + nG = pOldEntry->GetColor().GetGreen(); + nB = pOldEntry->GetColor().GetBlue(); + + /* Calculate the new RGB component (rnComp points to one of nR, nG, nB). + Using integer arithmetic with its rounding errors, the results of + this calculation are always exactly in the range 0x00 to 0xFF + (simply cutting the lower bits would darken the colors slightly). */ + sal_uInt32 nNewComp = rnComp; + nNewComp /= nFactor1; + nNewComp *= nFactor2; + nNewComp /= nFactor3; + rnComp = static_cast< sal_uInt8 >( nNewComp ); + Color aNewColor( nR, nG, nB ); + + // find or insert the new color + sal_uInt32 nFoundIdx = 0; + XclListColor* pNewEntry = SearchListEntry( aNewColor, nFoundIdx ); + if( !pNewEntry || (pNewEntry->GetColor() != aNewColor) ) + pNewEntry = CreateListEntry( aNewColor, nFoundIdx ); + pNewEntry->AddWeighting( pOldEntry->GetWeighting() ); + aListIndexMap.push_back( nFoundIdx ); + } + + // update color ID data map (maps color IDs to color list indexes), replace old by new list indexes + for( auto& rColorIdData : maColorIdDataVec ) + rColorIdData.mnIndex = aListIndexMap[ rColorIdData.mnIndex ]; +} + +void XclExpPaletteImpl::ReduceLeastUsedColor() +{ + // find a list color to remove + sal_uInt32 nRemove = GetLeastUsedListColor(); + // find its nearest neighbor + sal_uInt32 nKeep = GetNearestListColor( nRemove ); + + // merge both colors to one color, remove one color from list + XclListColor* pKeepEntry = mxColorList->at(nKeep).get(); + XclListColor* pRemoveEntry = mxColorList->at(nRemove).get(); + if( !(pKeepEntry && pRemoveEntry) ) + return; + + // merge both colors (if pKeepEntry is a base color, it will not change) + pKeepEntry->Merge( *pRemoveEntry ); + // remove the less used color, adjust nKeep index if kept color follows removed color + XclListColorList::iterator itr = mxColorList->begin(); + ::std::advance(itr, nRemove); + mxColorList->erase(itr); + if( nKeep > nRemove ) --nKeep; + + // recalculate color ID data map (maps color IDs to color list indexes) + for( auto& rColorIdData : maColorIdDataVec ) + { + if( rColorIdData.mnIndex > nRemove ) + --rColorIdData.mnIndex; + else if( rColorIdData.mnIndex == nRemove ) + rColorIdData.mnIndex = nKeep; + } +} + +sal_uInt32 XclExpPaletteImpl::GetLeastUsedListColor() const +{ + sal_uInt32 nFound = 0; + sal_uInt32 nMinW = SAL_MAX_UINT32; + + for( sal_uInt32 nIdx = 0, nCount = mxColorList->size(); nIdx < nCount; ++nIdx ) + { + XclListColor& rEntry = *mxColorList->at( nIdx ); + // ignore the base colors + if( !rEntry.IsBaseColor() && (rEntry.GetWeighting() < nMinW) ) + { + nFound = nIdx; + nMinW = rEntry.GetWeighting(); + } + } + return nFound; +} + +sal_uInt32 XclExpPaletteImpl::GetNearestListColor( const Color& rColor, sal_uInt32 nIgnore ) const +{ + sal_uInt32 nFound = 0; + sal_Int32 nMinD = SAL_MAX_INT32; + + for( sal_uInt32 nIdx = 0, nCount = mxColorList->size(); nIdx < nCount; ++nIdx ) + { + if( nIdx != nIgnore ) + { + if( XclListColor* pEntry = mxColorList->at(nIdx).get() ) + { + sal_Int32 nDist = lclGetColorDistance( rColor, pEntry->GetColor() ); + if( nDist < nMinD ) + { + nFound = nIdx; + nMinD = nDist; + } + } + } + } + return nFound; +} + +sal_uInt32 XclExpPaletteImpl::GetNearestListColor( sal_uInt32 nIndex ) const +{ + if (nIndex >= mxColorList->size()) + return 0; + XclListColor* pEntry = mxColorList->at(nIndex).get(); + return GetNearestListColor( pEntry->GetColor(), nIndex ); +} + +sal_Int32 XclExpPaletteImpl::GetNearestPaletteColor( + sal_uInt32& rnIndex, const Color& rColor ) const +{ + rnIndex = 0; + sal_Int32 nDist = SAL_MAX_INT32; + + sal_uInt32 nPaletteIndex = 0; + for( const auto& rPaletteColor : maPalette ) + { + if( !rPaletteColor.mbUsed ) + { + sal_Int32 nCurrDist = lclGetColorDistance( rColor, rPaletteColor.maColor ); + if( nCurrDist < nDist ) + { + rnIndex = nPaletteIndex; + nDist = nCurrDist; + } + } + ++nPaletteIndex; + } + return nDist; +} + +sal_Int32 XclExpPaletteImpl::GetNearPaletteColors( + sal_uInt32& rnFirst, sal_uInt32& rnSecond, const Color& rColor ) const +{ + rnFirst = rnSecond = 0; + sal_Int32 nDist1 = SAL_MAX_INT32; + sal_Int32 nDist2 = SAL_MAX_INT32; + + sal_uInt32 nPaletteIndex = 0; + for( const auto& rPaletteColor : maPalette ) + { + sal_Int32 nCurrDist = lclGetColorDistance( rColor, rPaletteColor.maColor ); + if( nCurrDist < nDist1 ) + { + rnSecond = rnFirst; + nDist2 = nDist1; + rnFirst = nPaletteIndex; + nDist1 = nCurrDist; + } + else if( nCurrDist < nDist2 ) + { + rnSecond = nPaletteIndex; + nDist2 = nCurrDist; + } + ++nPaletteIndex; + } + return nDist1; +} + +XclExpPalette::XclExpPalette( const XclExpRoot& rRoot ) : + XclDefaultPalette( rRoot ), + XclExpRecord( EXC_ID_PALETTE ) +{ + mxImpl = std::make_shared<XclExpPaletteImpl>( *this ); + SetRecSize( GetColorCount() * 4 + 2 ); +} + +XclExpPalette::~XclExpPalette() +{ +} + +sal_uInt32 XclExpPalette::InsertColor( const Color& rColor, XclExpColorType eType, sal_uInt16 nAutoDefault ) +{ + return mxImpl->InsertColor( rColor, eType, nAutoDefault ); +} + +sal_uInt32 XclExpPalette::GetColorIdFromIndex( sal_uInt16 nIndex ) +{ + return XclExpPaletteImpl::GetColorIdFromIndex( nIndex ); +} + +void XclExpPalette::Finalize() +{ + mxImpl->Finalize(); +} + +sal_uInt16 XclExpPalette::GetColorIndex( sal_uInt32 nColorId ) const +{ + return mxImpl->GetColorIndex( nColorId ); +} + +void XclExpPalette::GetMixedColors( + sal_uInt16& rnXclForeIx, sal_uInt16& rnXclBackIx, sal_uInt8& rnXclPattern, + sal_uInt32 nForeColorId, sal_uInt32 nBackColorId ) const +{ + return mxImpl->GetMixedColors( rnXclForeIx, rnXclBackIx, rnXclPattern, nForeColorId, nBackColorId ); +} + +Color XclExpPalette::GetColor( sal_uInt16 nXclIndex ) const +{ + return mxImpl->GetColor( nXclIndex ); +} + +void XclExpPalette::Save( XclExpStream& rStrm ) +{ + if( !mxImpl->IsDefaultPalette() ) + XclExpRecord::Save( rStrm ); +} + +void XclExpPalette::SaveXml( XclExpXmlStream& rStrm ) +{ + if( !mxImpl->IsDefaultPalette() ) + mxImpl->SaveXml( rStrm ); +} + +void XclExpPalette::WriteBody( XclExpStream& rStrm ) +{ + mxImpl->WriteBody( rStrm ); +} + +// FONT record - font information ============================================= + +namespace { + +typedef ::std::pair< sal_uInt16, sal_Int16 > WhichAndScript; + +sal_Int16 lclCheckFontItems( const SfxItemSet& rItemSet, + const WhichAndScript& rWAS1, const WhichAndScript& rWAS2, const WhichAndScript& rWAS3 ) +{ + if( ScfTools::CheckItem( rItemSet, rWAS1.first, false ) ) return rWAS1.second; + if( ScfTools::CheckItem( rItemSet, rWAS2.first, false ) ) return rWAS2.second; + if( ScfTools::CheckItem( rItemSet, rWAS3.first, false ) ) return rWAS3.second; + return 0; +}; + +} // namespace + +sal_Int16 XclExpFontHelper::GetFirstUsedScript( const XclExpRoot& rRoot, const SfxItemSet& rItemSet ) +{ + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + + /* #i17050# #i107170# We need to determine which font items are set in the + item set, and which script type we should prefer according to the + current language settings. */ + + static const WhichAndScript WAS_LATIN( ATTR_FONT, css::i18n::ScriptType::LATIN ); + static const WhichAndScript WAS_ASIAN( ATTR_CJK_FONT, css::i18n::ScriptType::ASIAN ); + static const WhichAndScript WAS_CMPLX( ATTR_CTL_FONT, css::i18n::ScriptType::COMPLEX ); + + /* do not let a font from a parent style override an explicit + cell font. */ + + sal_Int16 nDefScript = rRoot.GetDefApiScript(); + sal_Int16 nScript = 0; + const SfxItemSet* pCurrSet = &rItemSet; + + while( (nScript == 0) && pCurrSet ) + { + switch( nDefScript ) + { + case ApiScriptType::LATIN: + nScript = lclCheckFontItems( *pCurrSet, WAS_LATIN, WAS_CMPLX, WAS_ASIAN ); + break; + case ApiScriptType::ASIAN: + nScript = lclCheckFontItems( *pCurrSet, WAS_ASIAN, WAS_CMPLX, WAS_LATIN ); + break; + case ApiScriptType::COMPLEX: + nScript = lclCheckFontItems( *pCurrSet, WAS_CMPLX, WAS_ASIAN, WAS_LATIN ); + break; + default: + OSL_FAIL( "XclExpFontHelper::GetFirstUsedScript - unknown script type" ); + nScript = ApiScriptType::LATIN; + }; + pCurrSet = pCurrSet->GetParent(); + } + + if (nScript == 0) + nScript = nDefScript; + + if (nScript == 0) + { + OSL_FAIL( "XclExpFontHelper::GetFirstUsedScript - unknown script type" ); + nScript = ApiScriptType::LATIN; + } + + return nScript; +} + +vcl::Font XclExpFontHelper::GetFontFromItemSet( const XclExpRoot& rRoot, const SfxItemSet& rItemSet, sal_Int16 nScript ) +{ + // if WEAK is passed, guess script type from existing items in the item set + if( nScript == css::i18n::ScriptType::WEAK ) + nScript = GetFirstUsedScript( rRoot, rItemSet ); + + // convert to core script type constants + SvtScriptType nScScript = SvtLanguageOptions::FromI18NToSvtScriptType(nScript); + + // fill the font object + vcl::Font aFont; + ScPatternAttr::GetFont( aFont, rItemSet, SC_AUTOCOL_RAW, nullptr, nullptr, nullptr, nScScript ); + return aFont; +} + +ScDxfFont XclExpFontHelper::GetDxfFontFromItemSet(const XclExpRoot& rRoot, const SfxItemSet& rItemSet) +{ + sal_Int16 nScript = GetFirstUsedScript(rRoot, rItemSet); + + // convert to core script type constants + SvtScriptType nScScript = SvtLanguageOptions::FromI18NToSvtScriptType(nScript); + return ScPatternAttr::GetDxfFont(rItemSet, nScScript); +} + +bool XclExpFontHelper::CheckItems( const XclExpRoot& rRoot, const SfxItemSet& rItemSet, sal_Int16 nScript, bool bDeep ) +{ + static const sal_uInt16 pnCommonIds[] = { + ATTR_FONT_UNDERLINE, ATTR_FONT_CROSSEDOUT, ATTR_FONT_CONTOUR, + ATTR_FONT_SHADOWED, ATTR_FONT_COLOR, ATTR_FONT_LANGUAGE, 0 }; + static const sal_uInt16 pnLatinIds[] = { + ATTR_FONT, ATTR_FONT_HEIGHT, ATTR_FONT_WEIGHT, ATTR_FONT_POSTURE, 0 }; + static const sal_uInt16 pnAsianIds[] = { + ATTR_CJK_FONT, ATTR_CJK_FONT_HEIGHT, ATTR_CJK_FONT_WEIGHT, ATTR_CJK_FONT_POSTURE, 0 }; + static const sal_uInt16 pnComplexIds[] = { + ATTR_CTL_FONT, ATTR_CTL_FONT_HEIGHT, ATTR_CTL_FONT_WEIGHT, ATTR_CTL_FONT_POSTURE, 0 }; + + bool bUsed = ScfTools::CheckItems( rItemSet, pnCommonIds, bDeep ); + if( !bUsed ) + { + namespace ApiScriptType = css::i18n::ScriptType; + // if WEAK is passed, guess script type from existing items in the item set + if( nScript == ApiScriptType::WEAK ) + nScript = GetFirstUsedScript( rRoot, rItemSet ); + // check the correct items + switch( nScript ) + { + case ApiScriptType::LATIN: bUsed = ScfTools::CheckItems( rItemSet, pnLatinIds, bDeep ); break; + case ApiScriptType::ASIAN: bUsed = ScfTools::CheckItems( rItemSet, pnAsianIds, bDeep ); break; + case ApiScriptType::COMPLEX: bUsed = ScfTools::CheckItems( rItemSet, pnComplexIds, bDeep ); break; + default: OSL_FAIL( "XclExpFontHelper::CheckItems - unknown script type" ); + } + } + return bUsed; +} + +namespace { + +sal_uInt32 lclCalcHash( const XclFontData& rFontData ) +{ + sal_uInt32 nHash = rFontData.maName.getLength(); + nHash += sal_uInt32(rFontData.maColor) * 2; + nHash += rFontData.mnWeight * 3; + nHash += rFontData.mnCharSet * 5; + nHash += rFontData.mnFamily * 7; + nHash += rFontData.mnHeight * 11; + nHash += rFontData.mnUnderline * 13; + nHash += rFontData.mnEscapem * 17; + if( rFontData.mbItalic ) nHash += 19; + if( rFontData.mbStrikeout ) nHash += 23; + if( rFontData.mbOutline ) nHash += 29; + if( rFontData.mbShadow ) nHash += 31; + return nHash; +} + +} // namespace + +XclExpFont::XclExpFont( const XclExpRoot& rRoot, + const XclFontData& rFontData, XclExpColorType eColorType ) : + XclExpRecord( EXC_ID2_FONT, 14 ), + XclExpRoot( rRoot ), + maData( rFontData ) +{ + // insert font color into palette + mnColorId = rRoot.GetPalette().InsertColor( rFontData.maColor, eColorType, EXC_COLOR_FONTAUTO ); + // hash value for faster comparison + mnHash = lclCalcHash( maData ); + // record size + sal_Int32 nStrLen = maData.maName.getLength(); + SetRecSize( ((GetBiff() == EXC_BIFF8) ? (nStrLen * 2 + 1) : nStrLen) + 15 ); +} + +bool XclExpFont::Equals( const XclFontData& rFontData, sal_uInt32 nHash ) const +{ + return (mnHash == nHash) && (maData == rFontData); +} + +void XclExpFont::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_font); + XclXmlUtils::WriteFontData( rStyleSheet, maData, XML_name ); + // OOXTODO: XML_scheme; //scheme/@val values: "major", "minor", "none" + rStyleSheet->endElement( XML_font ); +} + +// private -------------------------------------------------------------------- + +void XclExpFont::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nAttr = EXC_FONTATTR_NONE; + ::set_flag( nAttr, EXC_FONTATTR_ITALIC, maData.mbItalic ); + if( maData.mnUnderline > 0 ) + ::set_flag( nAttr, EXC_FONTATTR_UNDERLINE, true ); + ::set_flag( nAttr, EXC_FONTATTR_STRIKEOUT, maData.mbStrikeout ); + ::set_flag( nAttr, EXC_FONTATTR_OUTLINE, maData.mbOutline ); + ::set_flag( nAttr, EXC_FONTATTR_SHADOW, maData.mbShadow ); + + OSL_ENSURE( maData.maName.getLength() < 256, "XclExpFont::WriteBody - font name too long" ); + XclExpString aFontName; + if( GetBiff() <= EXC_BIFF5 ) + aFontName.AssignByte( maData.maName, GetTextEncoding(), XclStrFlags::EightBitLength ); + else + aFontName.Assign( maData.maName, XclStrFlags::ForceUnicode | XclStrFlags::EightBitLength ); + + rStrm << maData.mnHeight + << nAttr + << GetPalette().GetColorIndex( mnColorId ) + << maData.mnWeight + << maData.mnEscapem + << maData.mnUnderline + << maData.mnFamily + << maData.mnCharSet + << sal_uInt8( 0 ) + << aFontName; +} + +XclExpDxfFont::XclExpDxfFont(const XclExpRoot& rRoot, + const SfxItemSet& rItemSet): + XclExpRoot(rRoot) +{ + maDxfData = XclExpFontHelper::GetDxfFontFromItemSet(rRoot, rItemSet); +} + +namespace { + +const char* getUnderlineOOXValue(FontLineStyle eUnderline) +{ + switch (eUnderline) + { + case LINESTYLE_NONE: + case LINESTYLE_DONTKNOW: + return "none"; + case LINESTYLE_DOUBLE: + case LINESTYLE_DOUBLEWAVE: + return "double"; + default: + return "single"; + } +} + +const char* getFontFamilyOOXValue(FontFamily eValue) +{ + switch (eValue) + { + case FAMILY_DONTKNOW: + return "0"; + case FAMILY_SWISS: + case FAMILY_SYSTEM: + return "2"; + case FAMILY_ROMAN: + return "1"; + case FAMILY_SCRIPT: + return "4"; + case FAMILY_MODERN: + return "3"; + case FAMILY_DECORATIVE: + return "5"; + default: + return "0"; + } +} + +} + +void XclExpDxfFont::SaveXml(XclExpXmlStream& rStrm) +{ + if (maDxfData.isEmpty()) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_font); + + if (maDxfData.pFontAttr) + { + OUString aFontName = (*maDxfData.pFontAttr)->GetFamilyName(); + + aFontName = XclTools::GetXclFontName(aFontName); + if (!aFontName.isEmpty()) + { + rStyleSheet->singleElement(XML_name, XML_val, aFontName); + } + + rtl_TextEncoding eTextEnc = (*maDxfData.pFontAttr)->GetCharSet(); + sal_uInt8 nExcelCharSet = rtl_getBestWindowsCharsetFromTextEncoding(eTextEnc); + if (nExcelCharSet) + { + rStyleSheet->singleElement(XML_charset, XML_val, OString::number(nExcelCharSet)); + } + + FontFamily eFamily = (*maDxfData.pFontAttr)->GetFamily(); + const char* pVal = getFontFamilyOOXValue(eFamily); + if (pVal) + { + rStyleSheet->singleElement(XML_family, XML_val, pVal); + } + } + + if (maDxfData.eWeight) + { + rStyleSheet->singleElement(XML_b, + XML_val, ToPsz10(*maDxfData.eWeight != WEIGHT_NORMAL)); + } + + if (maDxfData.eItalic) + { + bool bItalic = (*maDxfData.eItalic == ITALIC_OBLIQUE) || (*maDxfData.eItalic == ITALIC_NORMAL); + rStyleSheet->singleElement(XML_i, XML_val, ToPsz10(bItalic)); + } + + if (maDxfData.eStrike) + { + bool bStrikeout = + (*maDxfData.eStrike == STRIKEOUT_SINGLE) || (*maDxfData.eStrike == STRIKEOUT_DOUBLE) || + (*maDxfData.eStrike == STRIKEOUT_BOLD) || (*maDxfData.eStrike == STRIKEOUT_SLASH) || + (*maDxfData.eStrike == STRIKEOUT_X); + + rStyleSheet->singleElement(XML_strike, XML_val, ToPsz10(bStrikeout)); + } + + if (maDxfData.bOutline) + { + rStyleSheet->singleElement(XML_outline, XML_val, ToPsz10(*maDxfData.bOutline)); + } + + if (maDxfData.bShadow) + { + rStyleSheet->singleElement(XML_shadow, XML_val, ToPsz10(*maDxfData.bShadow)); + } + + if (maDxfData.aColor) + { + rStyleSheet->singleElement(XML_color, + XML_rgb, XclXmlUtils::ToOString(*maDxfData.aColor)); + } + + if (maDxfData.nFontHeight) + { + rStyleSheet->singleElement(XML_sz, + XML_val, OString::number(*maDxfData.nFontHeight/20)); + } + + if (maDxfData.eUnder) + { + const char* pVal = getUnderlineOOXValue(*maDxfData.eUnder); + rStyleSheet->singleElement(XML_u, XML_val, pVal); + } + + rStyleSheet->endElement(XML_font); +} + +XclExpBlindFont::XclExpBlindFont( const XclExpRoot& rRoot ) : + XclExpFont( rRoot, XclFontData(), EXC_COLOR_CELLTEXT ) +{ +} + +bool XclExpBlindFont::Equals( const XclFontData& /*rFontData*/, sal_uInt32 /*nHash*/ ) const +{ + return false; +} + +void XclExpBlindFont::Save( XclExpStream& /*rStrm*/ ) +{ + // do nothing +} + +XclExpFontBuffer::XclExpFontBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnXclMaxSize( 0 ) +{ + switch( GetBiff() ) + { + case EXC_BIFF4: mnXclMaxSize = EXC_FONT_MAXCOUNT4; break; + case EXC_BIFF5: mnXclMaxSize = EXC_FONT_MAXCOUNT5; break; + case EXC_BIFF8: mnXclMaxSize = EXC_FONT_MAXCOUNT8; break; + default: DBG_ERROR_BIFF(); + } + InitDefaultFonts(); +} + +const XclExpFont* XclExpFontBuffer::GetFont( sal_uInt16 nXclFont ) const +{ + return maFontList.GetRecord( nXclFont ); +} + +const XclFontData& XclExpFontBuffer::GetAppFontData() const +{ + return maFontList.GetRecord( EXC_FONT_APP )->GetFontData(); // exists always +} + +sal_uInt16 XclExpFontBuffer::Insert( + const XclFontData& rFontData, XclExpColorType eColorType, bool bAppFont ) +{ + if( bAppFont ) + { + XclExpFontRef xFont = new XclExpFont( GetRoot(), rFontData, eColorType ); + maFontList.ReplaceRecord( xFont, EXC_FONT_APP ); + // set width of '0' character for column width export + SetCharWidth( xFont->GetFontData() ); + return EXC_FONT_APP; + } + + size_t nPos = Find( rFontData ); + if( nPos == EXC_FONTLIST_NOTFOUND ) + { + // not found in buffer - create new font + size_t nSize = maFontList.GetSize(); + if( nSize < mnXclMaxSize ) + { + // possible to insert + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), rFontData, eColorType ) ); + nPos = nSize; // old size is last position now + } + else + { + // buffer is full - ignore new font, use default font + nPos = EXC_FONT_APP; + } + } + return static_cast< sal_uInt16 >( nPos ); +} + +sal_uInt16 XclExpFontBuffer::Insert( + const SvxFont& rFont, XclExpColorType eColorType ) +{ + return Insert( XclFontData( rFont ), eColorType ); +} + +sal_uInt16 XclExpFontBuffer::Insert( const SfxItemSet& rItemSet, + sal_Int16 nScript, XclExpColorType eColorType, bool bAppFont ) +{ + // #i17050# script type now provided by caller + vcl::Font aFont = XclExpFontHelper::GetFontFromItemSet( GetRoot(), rItemSet, nScript ); + return Insert( XclFontData( aFont ), eColorType, bAppFont ); +} + +void XclExpFontBuffer::Save( XclExpStream& rStrm ) +{ + maFontList.Save( rStrm ); +} + +void XclExpFontBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maFontList.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_fonts, XML_count, OString::number(maFontList.GetSize())); + + maFontList.SaveXml( rStrm ); + + rStyleSheet->endElement( XML_fonts ); +} + +// private -------------------------------------------------------------------- + +void XclExpFontBuffer::InitDefaultFonts() +{ + XclFontData aFontData; + aFontData.maName = "Arial"; + aFontData.SetScFamily( FAMILY_DONTKNOW ); + aFontData.SetFontEncoding( ScfTools::GetSystemTextEncoding() ); + aFontData.SetScHeight( 200 ); // 200 twips = 10 pt + aFontData.SetScWeight( WEIGHT_NORMAL ); + + switch( GetBiff() ) + { + case EXC_BIFF5: + { + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + aFontData.SetScWeight( WEIGHT_BOLD ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + aFontData.SetScWeight( WEIGHT_NORMAL ); + aFontData.SetScPosture( ITALIC_NORMAL ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + aFontData.SetScWeight( WEIGHT_BOLD ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + // the blind font with index 4 + maFontList.AppendNewRecord( new XclExpBlindFont( GetRoot() ) ); + // already add the first user defined font (Excel does it too) + aFontData.SetScWeight( WEIGHT_NORMAL ); + aFontData.SetScPosture( ITALIC_NONE ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + } + break; + case EXC_BIFF8: + { + XclExpFontRef xFont = new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ); + maFontList.AppendRecord( xFont ); + maFontList.AppendRecord( xFont ); + maFontList.AppendRecord( xFont ); + maFontList.AppendRecord( xFont ); + if( GetOutput() == EXC_OUTPUT_BINARY ) + // the blind font with index 4 + maFontList.AppendNewRecord( new XclExpBlindFont( GetRoot() ) ); + } + break; + default: + DBG_ERROR_BIFF(); + } +} + +size_t XclExpFontBuffer::Find( const XclFontData& rFontData ) +{ + sal_uInt32 nHash = lclCalcHash( rFontData ); + for( size_t nPos = 0, nSize = maFontList.GetSize(); nPos < nSize; ++nPos ) + if( maFontList.GetRecord( nPos )->Equals( rFontData, nHash ) ) + return nPos; + return EXC_FONTLIST_NOTFOUND; +} + +// FORMAT record - number formats ============================================= + +namespace { + +/** Predicate for search algorithm. */ +struct XclExpNumFmtPred +{ + sal_uInt32 mnScNumFmt; + explicit XclExpNumFmtPred( sal_uInt32 nScNumFmt ) : mnScNumFmt( nScNumFmt ) {} + bool operator()( const XclExpNumFmt& rFormat ) const + { return rFormat.mnScNumFmt == mnScNumFmt; } +}; + +} + +void XclExpNumFmt::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->singleElement( XML_numFmt, + XML_numFmtId, OString::number(mnXclNumFmt), + XML_formatCode, maNumFmtString ); +} + +XclExpNumFmtBuffer::XclExpNumFmtBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxFormatter( new SvNumberFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) ), + mpKeywordTable( new NfKeywordTable ), + mnStdFmt( GetFormatter().GetStandardIndex( ScGlobal::eLnge ) ) +{ + switch( GetBiff() ) + { + case EXC_BIFF5: mnXclOffset = EXC_FORMAT_OFFSET5; break; + case EXC_BIFF8: mnXclOffset = EXC_FORMAT_OFFSET8; break; + default: mnXclOffset = 0; DBG_ERROR_BIFF(); + } + + mxFormatter->FillKeywordTableForExcel( *mpKeywordTable ); +} + +XclExpNumFmtBuffer::~XclExpNumFmtBuffer() +{ +} + +sal_uInt16 XclExpNumFmtBuffer::Insert( sal_uInt32 nScNumFmt ) +{ + XclExpNumFmtVec::const_iterator aIt = + ::std::find_if( maFormatMap.begin(), maFormatMap.end(), XclExpNumFmtPred( nScNumFmt ) ); + if( aIt != maFormatMap.end() ) + return aIt->mnXclNumFmt; + + size_t nSize = maFormatMap.size(); + if( nSize < o3tl::make_unsigned( 0xFFFF - mnXclOffset ) ) + { + sal_uInt16 nXclNumFmt = static_cast< sal_uInt16 >( nSize + mnXclOffset ); + maFormatMap.emplace_back( nScNumFmt, nXclNumFmt, GetFormatCode( nScNumFmt ) ); + return nXclNumFmt; + } + + return 0; +} + +void XclExpNumFmtBuffer::Save( XclExpStream& rStrm ) +{ + for( const auto& rEntry : maFormatMap ) + WriteFormatRecord( rStrm, rEntry ); +} + +void XclExpNumFmtBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maFormatMap.empty() ) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_numFmts, XML_count, OString::number(maFormatMap.size())); + for( auto& rEntry : maFormatMap ) + { + rEntry.SaveXml( rStrm ); + } + rStyleSheet->endElement( XML_numFmts ); +} + +void XclExpNumFmtBuffer::WriteFormatRecord( XclExpStream& rStrm, sal_uInt16 nXclNumFmt, const OUString& rFormatStr ) +{ + XclExpString aExpStr; + if( GetBiff() <= EXC_BIFF5 ) + aExpStr.AssignByte( rFormatStr, GetTextEncoding(), XclStrFlags::EightBitLength ); + else + aExpStr.Assign( rFormatStr ); + + rStrm.StartRecord( EXC_ID4_FORMAT, 2 + aExpStr.GetSize() ); + rStrm << nXclNumFmt << aExpStr; + rStrm.EndRecord(); +} + +void XclExpNumFmtBuffer::WriteFormatRecord( XclExpStream& rStrm, const XclExpNumFmt& rFormat ) +{ + WriteFormatRecord( rStrm, rFormat.mnXclNumFmt, GetFormatCode( rFormat.mnScNumFmt ) ); +} + +namespace { + +OUString GetNumberFormatCode(const XclRoot& rRoot, const sal_uInt32 nScNumFmt, SvNumberFormatter* pFormatter, const NfKeywordTable* pKeywordTable) +{ + return rRoot.GetFormatter().GetFormatStringForExcel( nScNumFmt, *pKeywordTable, *pFormatter); +} + +} + +OUString XclExpNumFmtBuffer::GetFormatCode( sal_uInt32 nScNumFmt ) +{ + return GetNumberFormatCode( *this, nScNumFmt, mxFormatter.get(), mpKeywordTable.get() ); +} + +// XF, STYLE record - Cell formatting ========================================= + +bool XclExpCellProt::FillFromItemSet( const SfxItemSet& rItemSet, bool bStyle ) +{ + const ScProtectionAttr& rProtItem = rItemSet.Get( ATTR_PROTECTION ); + mbLocked = rProtItem.GetProtection(); + mbHidden = rProtItem.GetHideFormula() || rProtItem.GetHideCell(); + return ScfTools::CheckItem( rItemSet, ATTR_PROTECTION, bStyle ); +} + +void XclExpCellProt::FillToXF3( sal_uInt16& rnProt ) const +{ + ::set_flag( rnProt, EXC_XF_LOCKED, mbLocked ); + ::set_flag( rnProt, EXC_XF_HIDDEN, mbHidden ); +} + +void XclExpCellProt::SaveXml( XclExpXmlStream& rStrm ) const +{ + rStrm.GetCurrentStream()->singleElement( XML_protection, + XML_locked, ToPsz( mbLocked ), + XML_hidden, ToPsz( mbHidden ) ); +} + +bool XclExpCellAlign::FillFromItemSet(const XclRoot& rRoot, const SfxItemSet& rItemSet, + bool bForceLineBreak, XclBiff eBiff, bool bStyle) +{ + bool bUsed = false; + SvxCellHorJustify eHorAlign = rItemSet.Get( ATTR_HOR_JUSTIFY ).GetValue(); + SvxCellVerJustify eVerAlign = rItemSet.Get( ATTR_VER_JUSTIFY ).GetValue(); + + switch( eBiff ) + { + case EXC_BIFF8: // attributes new in BIFF8 + { + // text indent + tools::Long nTmpIndent = rItemSet.Get( ATTR_INDENT ).GetValue(); // already in twips + tools::Long nSpaceWidth = rRoot.GetSpaceWidth(); + sal_Int32 nIndent = static_cast<double>(nTmpIndent) / (3.0 * nSpaceWidth) + 0.5; + mnIndent = limit_cast< sal_uInt8 >( nIndent, 0, 15 ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_INDENT, bStyle ); + + // shrink to fit + mbShrink = rItemSet.Get( ATTR_SHRINKTOFIT ).GetValue(); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_SHRINKTOFIT, bStyle ); + + // CTL text direction + SetScFrameDir( rItemSet.Get( ATTR_WRITINGDIR ).GetValue() ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_WRITINGDIR, bStyle ); + + [[fallthrough]]; + } + + case EXC_BIFF5: // attributes new in BIFF5 + case EXC_BIFF4: // attributes new in BIFF4 + { + // vertical alignment + SetScVerAlign( eVerAlign ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_VER_JUSTIFY, bStyle ); + + // stacked/rotation + bool bStacked = rItemSet.Get( ATTR_STACKED ).GetValue(); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_STACKED, bStyle ); + if( bStacked ) + { + mnRotation = EXC_ROT_STACKED; + } + else + { + // rotation + Degree100 nScRot = rItemSet.Get( ATTR_ROTATE_VALUE ).GetValue(); + mnRotation = XclTools::GetXclRotation( nScRot ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_ROTATE_VALUE, bStyle ); + } + mnOrient = XclTools::GetXclOrientFromRot( mnRotation ); + + [[fallthrough]]; + } + + case EXC_BIFF3: // attributes new in BIFF3 + { + // text wrap + mbLineBreak = bForceLineBreak || rItemSet.Get( ATTR_LINEBREAK ).GetValue(); + bUsed |= bForceLineBreak || ScfTools::CheckItem( rItemSet, ATTR_LINEBREAK, bStyle ); + + [[fallthrough]]; + } + + case EXC_BIFF2: // attributes new in BIFF2 + { + // horizontal alignment + SetScHorAlign( eHorAlign ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_HOR_JUSTIFY, bStyle ); + } + + break; + default: DBG_ERROR_BIFF(); + } + + if (eBiff == EXC_BIFF8) + { + // Adjust for distributed alignments. + if (eHorAlign == SvxCellHorJustify::Block) + { + SvxCellJustifyMethod eHorJustMethod = + rItemSet.GetItem<SvxJustifyMethodItem>(ATTR_HOR_JUSTIFY_METHOD)->GetValue(); + if (eHorJustMethod == SvxCellJustifyMethod::Distribute) + mnHorAlign = EXC_XF_HOR_DISTRIB; + } + + if (eVerAlign == SvxCellVerJustify::Block) + { + SvxCellJustifyMethod eVerJustMethod = + rItemSet.GetItem<SvxJustifyMethodItem>(ATTR_VER_JUSTIFY_METHOD)->GetValue(); + if (eVerJustMethod == SvxCellJustifyMethod::Distribute) + mnVerAlign = EXC_XF_VER_DISTRIB; + } + } + + return bUsed; +} + +void XclExpCellAlign::FillToXF5( sal_uInt16& rnAlign ) const +{ + ::insert_value( rnAlign, mnHorAlign, 0, 3 ); + ::set_flag( rnAlign, EXC_XF_LINEBREAK, mbLineBreak ); + ::insert_value( rnAlign, mnVerAlign, 4, 3 ); + ::insert_value( rnAlign, mnOrient, 8, 2 ); +} + +void XclExpCellAlign::FillToXF8( sal_uInt16& rnAlign, sal_uInt16& rnMiscAttrib ) const +{ + ::insert_value( rnAlign, mnHorAlign, 0, 3 ); + ::set_flag( rnAlign, EXC_XF_LINEBREAK, mbLineBreak ); + ::insert_value( rnAlign, mnVerAlign, 4, 3 ); + ::insert_value( rnAlign, mnRotation, 8, 8 ); + ::insert_value( rnMiscAttrib, mnIndent, 0, 4 ); + ::set_flag( rnMiscAttrib, EXC_XF8_SHRINK, mbShrink ); + ::insert_value( rnMiscAttrib, mnTextDir, 6, 2 ); +} + +static const char* ToHorizontalAlignment( sal_uInt8 nHorAlign ) +{ + switch( nHorAlign ) + { + case EXC_XF_HOR_GENERAL: return "general"; + case EXC_XF_HOR_LEFT: return "left"; + case EXC_XF_HOR_CENTER: return "center"; + case EXC_XF_HOR_RIGHT: return "right"; + case EXC_XF_HOR_FILL: return "fill"; + case EXC_XF_HOR_JUSTIFY: return "justify"; + case EXC_XF_HOR_CENTER_AS: return "centerContinuous"; + case EXC_XF_HOR_DISTRIB: return "distributed"; + } + return "*unknown*"; +} + +static const char* ToVerticalAlignment( sal_uInt8 nVerAlign ) +{ + switch( nVerAlign ) + { + case EXC_XF_VER_TOP: return "top"; + case EXC_XF_VER_CENTER: return "center"; + case EXC_XF_VER_BOTTOM: return "bottom"; + case EXC_XF_VER_JUSTIFY: return "justify"; + case EXC_XF_VER_DISTRIB: return "distributed"; + } + return "*unknown*"; +} + +void XclExpCellAlign::SaveXml( XclExpXmlStream& rStrm ) const +{ + rStrm.GetCurrentStream()->singleElement( XML_alignment, + XML_horizontal, ToHorizontalAlignment( mnHorAlign ), + XML_vertical, ToVerticalAlignment( mnVerAlign ), + XML_textRotation, OString::number(mnRotation), + XML_wrapText, ToPsz( mbLineBreak ), + XML_indent, OString::number(mnIndent), + // OOXTODO: XML_relativeIndent, mnIndent? + // OOXTODO: XML_justifyLastLine, + XML_shrinkToFit, ToPsz( mbShrink ), + XML_readingOrder, sax_fastparser::UseIf(OString::number(mnTextDir), mnTextDir != EXC_XF_TEXTDIR_CONTEXT) ); +} + +namespace { + +void lclGetBorderLine( + sal_uInt8& rnXclLine, sal_uInt32& rnColorId, + const ::editeng::SvxBorderLine* pLine, XclExpPalette& rPalette, XclBiff eBiff ) +{ + // Document: sc/qa/unit/data/README.cellborders + + enum CalcLineIndex{Idx_None, Idx_Solid, Idx_Dotted, Idx_Dashed, Idx_FineDashed, Idx_DashDot, Idx_DashDotDot, Idx_DoubleThin, Idx_Last}; + enum ExcelWidthIndex{Width_Hair, Width_Thin, Width_Medium, Width_Thick, Width_Last}; + static sal_uInt8 Map_LineLO_toMS[Idx_Last][Width_Last] = + { + // 0,05 - 0,74 0,75 - 1,49 1,50 - 2,49 2,50 - 9,00 Width Range [pt] + // EXC_BORDER_HAIR EXC_BORDER_THIN EXC_BORDER_MEDIUM EXC_BORDER_THICK MS Width + {EXC_LINE_NONE , EXC_LINE_NONE , EXC_LINE_NONE , EXC_LINE_NONE }, // 0 BorderLineStyle::NONE + {EXC_LINE_HAIR , EXC_LINE_THIN , EXC_LINE_MEDIUM , EXC_LINE_THICK }, // 1 BorderLineStyle::SOLID + {EXC_LINE_DOTTED , EXC_LINE_DOTTED , EXC_LINE_MEDIUM_SLANT_DASHDOT, EXC_LINE_MEDIUM_SLANT_DASHDOT}, // 2 BorderLineStyle::DOTTED + {EXC_LINE_DOTTED , EXC_LINE_DASHED , EXC_LINE_MEDIUM_DASHED , EXC_LINE_MEDIUM_DASHED }, // 3 BorderLineStyle::DASHED + {EXC_LINE_DASHED , EXC_LINE_DASHED , EXC_LINE_MEDIUM_SLANT_DASHDOT, EXC_LINE_MEDIUM_SLANT_DASHDOT}, // 4 BorderLineStyle::FINE_DASHED + {EXC_LINE_DASHED , EXC_LINE_THIN_DASHDOT , EXC_LINE_MEDIUM_DASHDOT , EXC_LINE_MEDIUM_DASHDOT }, // 5 BorderLineStyle::DASH_DOT + {EXC_LINE_DASHED , EXC_LINE_THIN_DASHDOTDOT , EXC_LINE_MEDIUM_DASHDOTDOT , EXC_LINE_MEDIUM_DASHDOTDOT }, // 6 BorderLineStyle::DASH_DOT_DOT + {EXC_LINE_DOUBLE , EXC_LINE_DOUBLE , EXC_LINE_DOUBLE , EXC_LINE_DOUBLE } // 7 BorderLineStyle::DOUBLE_THIN + }; // Line Name + + rnXclLine = EXC_LINE_NONE; + if( pLine ) + { + sal_uInt16 nOuterWidth = pLine->GetOutWidth(); + ExcelWidthIndex nOuterWidthIndx; + CalcLineIndex nStyleIndex; + + switch (pLine->GetBorderLineStyle()) + { + case SvxBorderLineStyle::NONE: + nStyleIndex = Idx_None; + break; + case SvxBorderLineStyle::SOLID: + nStyleIndex = Idx_Solid; + break; + case SvxBorderLineStyle::DOTTED: + nStyleIndex = Idx_Dotted; + break; + case SvxBorderLineStyle::DASHED: + nStyleIndex = Idx_Dashed; + break; + case SvxBorderLineStyle::FINE_DASHED: + nStyleIndex = Idx_FineDashed; + break; + case SvxBorderLineStyle::DASH_DOT: + nStyleIndex = Idx_DashDot; + break; + case SvxBorderLineStyle::DASH_DOT_DOT: + nStyleIndex = Idx_DashDotDot; + break; + case SvxBorderLineStyle::DOUBLE_THIN: + // the "nOuterWidth" is not right for this line type + // but at the moment width it not important for that + // the right function is nOuterWidth = (sal_uInt16) pLine->GetWidth(); + nStyleIndex = Idx_DoubleThin; + break; + default: + nStyleIndex = Idx_Solid; + } + + if( nOuterWidth >= EXC_BORDER_THICK ) + nOuterWidthIndx = Width_Thick; + else if( nOuterWidth >= EXC_BORDER_MEDIUM ) + nOuterWidthIndx = Width_Medium; + else if( nOuterWidth >= EXC_BORDER_THIN ) + nOuterWidthIndx = Width_Thin; + else if ( nOuterWidth >= EXC_BORDER_HAIR ) + nOuterWidthIndx = Width_Hair; + else + nOuterWidthIndx = Width_Thin; + + rnXclLine = Map_LineLO_toMS[nStyleIndex][nOuterWidthIndx]; + } + + if( (eBiff == EXC_BIFF2) && (rnXclLine != EXC_LINE_NONE) ) + rnXclLine = EXC_LINE_THIN; + + rnColorId = (pLine && (rnXclLine != EXC_LINE_NONE)) ? + rPalette.InsertColor( pLine->GetColor(), EXC_COLOR_CELLBORDER ) : + XclExpPalette::GetColorIdFromIndex( 0 ); +} + +} // namespace + +XclExpCellBorder::XclExpCellBorder() : + mnLeftColorId( XclExpPalette::GetColorIdFromIndex( mnLeftColor ) ), + mnRightColorId( XclExpPalette::GetColorIdFromIndex( mnRightColor ) ), + mnTopColorId( XclExpPalette::GetColorIdFromIndex( mnTopColor ) ), + mnBottomColorId( XclExpPalette::GetColorIdFromIndex( mnBottomColor ) ), + mnDiagColorId( XclExpPalette::GetColorIdFromIndex( mnDiagColor ) ) +{ +} + +bool XclExpCellBorder::FillFromItemSet( + const SfxItemSet& rItemSet, XclExpPalette& rPalette, XclBiff eBiff, bool bStyle ) +{ + bool bUsed = false; + + switch( eBiff ) + { + case EXC_BIFF8: // attributes new in BIFF8 + { + const SvxLineItem& rTLBRItem = rItemSet.Get( ATTR_BORDER_TLBR ); + sal_uInt8 nTLBRLine; + sal_uInt32 nTLBRColorId; + lclGetBorderLine( nTLBRLine, nTLBRColorId, rTLBRItem.GetLine(), rPalette, eBiff ); + mbDiagTLtoBR = (nTLBRLine != EXC_LINE_NONE); + + const SvxLineItem& rBLTRItem = rItemSet.Get( ATTR_BORDER_BLTR ); + sal_uInt8 nBLTRLine; + sal_uInt32 nBLTRColorId; + lclGetBorderLine( nBLTRLine, nBLTRColorId, rBLTRItem.GetLine(), rPalette, eBiff ); + mbDiagBLtoTR = (nBLTRLine != EXC_LINE_NONE); + + if( ::ScHasPriority( rTLBRItem.GetLine(), rBLTRItem.GetLine() ) ) + { + mnDiagLine = nTLBRLine; + mnDiagColorId = nTLBRColorId; + } + else + { + mnDiagLine = nBLTRLine; + mnDiagColorId = nBLTRColorId; + } + + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_BORDER_TLBR, bStyle ) || + ScfTools::CheckItem( rItemSet, ATTR_BORDER_BLTR, bStyle ); + + [[fallthrough]]; + } + + case EXC_BIFF5: + case EXC_BIFF4: + case EXC_BIFF3: + case EXC_BIFF2: + { + const SvxBoxItem& rBoxItem = rItemSet.Get( ATTR_BORDER ); + lclGetBorderLine( mnLeftLine, mnLeftColorId, rBoxItem.GetLeft(), rPalette, eBiff ); + lclGetBorderLine( mnRightLine, mnRightColorId, rBoxItem.GetRight(), rPalette, eBiff ); + lclGetBorderLine( mnTopLine, mnTopColorId, rBoxItem.GetTop(), rPalette, eBiff ); + lclGetBorderLine( mnBottomLine, mnBottomColorId, rBoxItem.GetBottom(), rPalette, eBiff ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_BORDER, bStyle ); + } + + break; + default: DBG_ERROR_BIFF(); + } + + return bUsed; +} + +void XclExpCellBorder::SetFinalColors( const XclExpPalette& rPalette ) +{ + mnLeftColor = rPalette.GetColorIndex( mnLeftColorId ); + mnRightColor = rPalette.GetColorIndex( mnRightColorId ); + mnTopColor = rPalette.GetColorIndex( mnTopColorId ); + mnBottomColor = rPalette.GetColorIndex( mnBottomColorId ); + mnDiagColor = rPalette.GetColorIndex( mnDiagColorId ); +} + +void XclExpCellBorder::FillToXF5( sal_uInt32& rnBorder, sal_uInt32& rnArea ) const +{ + ::insert_value( rnBorder, mnTopLine, 0, 3 ); + ::insert_value( rnBorder, mnLeftLine, 3, 3 ); + ::insert_value( rnArea, mnBottomLine, 22, 3 ); + ::insert_value( rnBorder, mnRightLine, 6, 3 ); + ::insert_value( rnBorder, mnTopColor, 9, 7 ); + ::insert_value( rnBorder, mnLeftColor, 16, 7 ); + ::insert_value( rnArea, mnBottomColor, 25, 7 ); + ::insert_value( rnBorder, mnRightColor, 23, 7 ); +} + +void XclExpCellBorder::FillToXF8( sal_uInt32& rnBorder1, sal_uInt32& rnBorder2 ) const +{ + ::insert_value( rnBorder1, mnLeftLine, 0, 4 ); + ::insert_value( rnBorder1, mnRightLine, 4, 4 ); + ::insert_value( rnBorder1, mnTopLine, 8, 4 ); + ::insert_value( rnBorder1, mnBottomLine, 12, 4 ); + ::insert_value( rnBorder1, mnLeftColor, 16, 7 ); + ::insert_value( rnBorder1, mnRightColor, 23, 7 ); + ::insert_value( rnBorder2, mnTopColor, 0, 7 ); + ::insert_value( rnBorder2, mnBottomColor, 7, 7 ); + ::insert_value( rnBorder2, mnDiagColor, 14, 7 ); + ::insert_value( rnBorder2, mnDiagLine, 21, 4 ); + ::set_flag( rnBorder1, EXC_XF_DIAGONAL_TL_TO_BR, mbDiagTLtoBR ); + ::set_flag( rnBorder1, EXC_XF_DIAGONAL_BL_TO_TR, mbDiagBLtoTR ); +} + +void XclExpCellBorder::FillToCF8( sal_uInt16& rnLine, sal_uInt32& rnColor ) const +{ + ::insert_value( rnLine, mnLeftLine, 0, 4 ); + ::insert_value( rnLine, mnRightLine, 4, 4 ); + ::insert_value( rnLine, mnTopLine, 8, 4 ); + ::insert_value( rnLine, mnBottomLine, 12, 4 ); + ::insert_value( rnColor, mnLeftColor, 0, 7 ); + ::insert_value( rnColor, mnRightColor, 7, 7 ); + ::insert_value( rnColor, mnTopColor, 16, 7 ); + ::insert_value( rnColor, mnBottomColor, 23, 7 ); +} + +static const char* ToLineStyle( sal_uInt8 nLineStyle ) +{ + switch( nLineStyle ) + { + case EXC_LINE_NONE: return "none"; + case EXC_LINE_THIN: return "thin"; + case EXC_LINE_MEDIUM: return "medium"; + case EXC_LINE_THICK: return "thick"; + case EXC_LINE_DOUBLE: return "double"; + case EXC_LINE_HAIR: return "hair"; + case EXC_LINE_DOTTED: return "dotted"; + case EXC_LINE_DASHED: return "dashed"; + case EXC_LINE_MEDIUM_DASHED: return "mediumDashed"; + case EXC_LINE_THIN_DASHDOT: return "dashDot"; + case EXC_LINE_THIN_DASHDOTDOT: return "dashDotDot"; + case EXC_LINE_MEDIUM_DASHDOT: return "mediumDashDot"; + case EXC_LINE_MEDIUM_DASHDOTDOT: return "mediumDashDotDot"; + case EXC_LINE_MEDIUM_SLANT_DASHDOT: return "slantDashDot"; + } + return "*unknown*"; +} + +static void lcl_WriteBorder( XclExpXmlStream& rStrm, sal_Int32 nElement, sal_uInt8 nLineStyle, const Color& rColor ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + if( nLineStyle == EXC_LINE_NONE ) + rStyleSheet->singleElement(nElement); + else if( rColor == Color( 0, 0, 0 ) ) + rStyleSheet->singleElement(nElement, XML_style, ToLineStyle(nLineStyle)); + else + { + rStyleSheet->startElement(nElement, XML_style, ToLineStyle(nLineStyle)); + rStyleSheet->singleElement(XML_color, XML_rgb, XclXmlUtils::ToOString(rColor)); + rStyleSheet->endElement( nElement ); + } +} + +void XclExpCellBorder::SaveXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + + XclExpPalette& rPalette = rStrm.GetRoot().GetPalette(); + + rStyleSheet->startElement( XML_border, + XML_diagonalUp, ToPsz( mbDiagBLtoTR ), + XML_diagonalDown, ToPsz( mbDiagTLtoBR ) + // OOXTODO: XML_outline + ); + lcl_WriteBorder( rStrm, XML_left, mnLeftLine, rPalette.GetColor( mnLeftColor ) ); + lcl_WriteBorder( rStrm, XML_right, mnRightLine, rPalette.GetColor( mnRightColor ) ); + lcl_WriteBorder( rStrm, XML_top, mnTopLine, rPalette.GetColor( mnTopColor ) ); + lcl_WriteBorder( rStrm, XML_bottom, mnBottomLine, rPalette.GetColor( mnBottomColor ) ); + lcl_WriteBorder( rStrm, XML_diagonal, mnDiagLine, rPalette.GetColor( mnDiagColor ) ); + // OOXTODO: XML_vertical, XML_horizontal + rStyleSheet->endElement( XML_border ); +} + +XclExpCellArea::XclExpCellArea() : + mnForeColorId( XclExpPalette::GetColorIdFromIndex( mnForeColor ) ), + mnBackColorId( XclExpPalette::GetColorIdFromIndex( mnBackColor ) ), + maForeColor(0), + maBackColor(0) +{ +} + +XclExpCellArea::XclExpCellArea(Color aForeColor, Color aBackColor) + : XclCellArea(EXC_PATT_SOLID) + , mnForeColorId(0) + , mnBackColorId(0) + , maForeColor(aForeColor) + , maBackColor(aBackColor) +{ +} + +bool XclExpCellArea::FillFromItemSet( const SfxItemSet& rItemSet, XclExpPalette& rPalette, bool bStyle ) +{ + const SvxBrushItem& rBrushItem = rItemSet.Get( ATTR_BACKGROUND ); + if( rBrushItem.GetColor().IsTransparent() ) + { + mnPattern = EXC_PATT_NONE; + mnForeColorId = XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ); + mnBackColorId = XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWBACK ); + } + else + { + mnPattern = EXC_PATT_SOLID; + mnForeColorId = rPalette.InsertColor( rBrushItem.GetColor(), EXC_COLOR_CELLAREA ); + mnBackColorId = XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ); + } + return ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, bStyle ); +} + +void XclExpCellArea::SetFinalColors( const XclExpPalette& rPalette ) +{ + rPalette.GetMixedColors( mnForeColor, mnBackColor, mnPattern, mnForeColorId, mnBackColorId ); +} + +void XclExpCellArea::FillToXF5( sal_uInt32& rnArea ) const +{ + ::insert_value( rnArea, mnPattern, 16, 6 ); + ::insert_value( rnArea, mnForeColor, 0, 7 ); + ::insert_value( rnArea, mnBackColor, 7, 7 ); +} + +void XclExpCellArea::FillToXF8( sal_uInt32& rnBorder2, sal_uInt16& rnArea ) const +{ + ::insert_value( rnBorder2, mnPattern, 26, 6 ); + ::insert_value( rnArea, mnForeColor, 0, 7 ); + ::insert_value( rnArea, mnBackColor, 7, 7 ); +} + +void XclExpCellArea::FillToCF8( sal_uInt16& rnPattern, sal_uInt16& rnColor ) const +{ + XclCellArea aTmp( *this ); + if( !aTmp.IsTransparent() && (aTmp.mnBackColor == EXC_COLOR_WINDOWTEXT) ) + aTmp.mnBackColor = 0; + if( aTmp.mnPattern == EXC_PATT_SOLID ) + ::std::swap( aTmp.mnForeColor, aTmp.mnBackColor ); + ::insert_value( rnColor, aTmp.mnForeColor, 0, 7 ); + ::insert_value( rnColor, aTmp.mnBackColor, 7, 7 ); + ::insert_value( rnPattern, aTmp.mnPattern, 10, 6 ); +} + +static const char* ToPatternType( sal_uInt8 nPattern ) +{ + switch( nPattern ) + { + case EXC_PATT_NONE: return "none"; + case EXC_PATT_SOLID: return "solid"; + case EXC_PATT_50_PERC: return "mediumGray"; + case EXC_PATT_75_PERC: return "darkGray"; + case EXC_PATT_25_PERC: return "lightGray"; + case EXC_PATT_12_5_PERC: return "gray125"; + case EXC_PATT_6_25_PERC: return "gray0625"; + } + return "*unknown*"; +} + +void XclExpCellArea::SaveXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_fill); + + // OOXTODO: XML_gradientFill + + XclExpPalette& rPalette = rStrm.GetRoot().GetPalette(); + + if (mnPattern == EXC_PATT_NONE + || (mnForeColor == 0 && mnBackColor == 0 && maForeColor == 0 && maBackColor == 0)) + { + rStyleSheet->singleElement(XML_patternFill, XML_patternType, ToPatternType(mnPattern)); + } + else + { + rStyleSheet->startElement(XML_patternFill, XML_patternType, ToPatternType(mnPattern)); + if (maForeColor != 0 || maBackColor != 0) + { + if (maForeColor != 0) + { + rStyleSheet->singleElement(XML_fgColor, XML_rgb, + XclXmlUtils::ToOString(maForeColor)); + } + + if (maBackColor != 0) + { + rStyleSheet->singleElement(XML_bgColor, XML_rgb, + XclXmlUtils::ToOString(maBackColor)); + } + } + else + { + if (mnForeColor != 0) + { + rStyleSheet->singleElement(XML_fgColor, XML_rgb, + XclXmlUtils::ToOString(rPalette.GetColor(mnForeColor))); + } + if (mnBackColor != 0) + { + rStyleSheet->singleElement(XML_bgColor, XML_rgb, + XclXmlUtils::ToOString(rPalette.GetColor(mnBackColor))); + } + } + + rStyleSheet->endElement( XML_patternFill ); + } + + rStyleSheet->endElement( XML_fill ); +} + +bool XclExpColor::FillFromItemSet( const SfxItemSet& rItemSet ) +{ + if( !ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, true ) ) + return false; + + const SvxBrushItem& rBrushItem = rItemSet.Get( ATTR_BACKGROUND ); + maColor = rBrushItem.GetColor(); + + return true; +} + +void XclExpColor::SaveXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_fill); + rStyleSheet->startElement(XML_patternFill); + rStyleSheet->singleElement(XML_bgColor, XML_rgb, XclXmlUtils::ToOString(maColor)); + + rStyleSheet->endElement( XML_patternFill ); + rStyleSheet->endElement( XML_fill ); +} + +XclExpXFId::XclExpXFId() : + mnXFId( XclExpXFBuffer::GetDefCellXFId() ), + mnXFIndex( EXC_XF_DEFAULTCELL ) +{ +} + +void XclExpXFId::ConvertXFIndex( const XclExpRoot& rRoot ) +{ + mnXFIndex = rRoot.GetXFBuffer().GetXFIndex( mnXFId ); +} + +XclExpXF::XclExpXF( + const XclExpRoot& rRoot, const ScPatternAttr& rPattern, sal_Int16 nScript, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) : + XclXFBase( true ), + XclExpRoot( rRoot ) +{ + mnParentXFId = GetXFBuffer().InsertStyle( rPattern.GetStyleSheet() ); + Init( rPattern.GetItemSet(), nScript, nForceScNumFmt, nForceXclFont, bForceLineBreak, false ); +} + +XclExpXF::XclExpXF( const XclExpRoot& rRoot, const SfxStyleSheetBase& rStyleSheet ) : + XclXFBase( false ), + XclExpRoot( rRoot ), + mnParentXFId( XclExpXFBuffer::GetXFIdFromIndex( EXC_XF_STYLEPARENT ) ) +{ + bool bDefStyle = (rStyleSheet.GetName() == ScResId( STR_STYLENAME_STANDARD )); + sal_Int16 nScript = bDefStyle ? GetDefApiScript() : css::i18n::ScriptType::WEAK; + Init( const_cast< SfxStyleSheetBase& >( rStyleSheet ).GetItemSet(), nScript, + NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND, false, bDefStyle ); +} + +XclExpXF::XclExpXF( const XclExpRoot& rRoot, bool bCellXF ) : + XclXFBase( bCellXF ), + XclExpRoot( rRoot ), + mnParentXFId( XclExpXFBuffer::GetXFIdFromIndex( EXC_XF_STYLEPARENT ) ) +{ + InitDefault(); +} + +bool XclExpXF::Equals( const ScPatternAttr& rPattern, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) const +{ + return IsCellXF() && (mpItemSet == &rPattern.GetItemSet()) && + (!bForceLineBreak || maAlignment.mbLineBreak) && + ((nForceScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND) || (mnScNumFmt == nForceScNumFmt)) && + ((nForceXclFont == EXC_FONT_NOTFOUND) || (mnXclFont == nForceXclFont)); +} + +bool XclExpXF::Equals( const SfxStyleSheetBase& rStyleSheet ) const +{ + return IsStyleXF() && (mpItemSet == &const_cast< SfxStyleSheetBase& >( rStyleSheet ).GetItemSet()); +} + +void XclExpXF::SetFinalColors() +{ + maBorder.SetFinalColors( GetPalette() ); + maArea.SetFinalColors( GetPalette() ); +} + +bool XclExpXF::Equals( const XclExpXF& rCmpXF ) const +{ + return XclXFBase::Equals( rCmpXF ) && + (maProtection == rCmpXF.maProtection) && (maAlignment == rCmpXF.maAlignment) && + (maBorder == rCmpXF.maBorder) && (maArea == rCmpXF.maArea) && + (mnXclFont == rCmpXF.mnXclFont) && (mnXclNumFmt == rCmpXF.mnXclNumFmt) && + (mnParentXFId == rCmpXF.mnParentXFId); +} + +void XclExpXF::InitDefault() +{ + SetRecHeader( EXC_ID5_XF, (GetBiff() == EXC_BIFF8) ? 20 : 16 ); + mpItemSet = nullptr; + mnScNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND; + mnXclFont = mnXclNumFmt = 0; + SetXmlIds(0, 0); +} + +void XclExpXF::Init( const SfxItemSet& rItemSet, sal_Int16 nScript, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak, bool bDefStyle ) +{ + InitDefault(); + mpItemSet = &rItemSet; + + // cell protection + mbProtUsed = maProtection.FillFromItemSet( rItemSet, IsStyleXF() ); + + // font + if( nForceXclFont == EXC_FONT_NOTFOUND ) + { + mnXclFont = GetFontBuffer().Insert( rItemSet, nScript, EXC_COLOR_CELLTEXT, bDefStyle ); + mbFontUsed = XclExpFontHelper::CheckItems( GetRoot(), rItemSet, nScript, IsStyleXF() ); + } + else + { + mnXclFont = nForceXclFont; + mbFontUsed = true; + } + + // number format + if (nForceScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND) + mnXclNumFmt = nForceScNumFmt; + else + { + // Built-in formats of dedicated languages may be attributed using the + // system language (or even other?) format with a language attribute, + // obtain the "real" format key. + mnScNumFmt = rItemSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + LanguageType nLang = rItemSet.Get( ATTR_LANGUAGE_FORMAT).GetLanguage(); + if (mnScNumFmt >= SV_COUNTRY_LANGUAGE_OFFSET || nLang != LANGUAGE_SYSTEM) + mnScNumFmt = GetFormatter().GetFormatForLanguageIfBuiltIn( mnScNumFmt, nLang); + } + mnXclNumFmt = GetNumFmtBuffer().Insert( mnScNumFmt ); + mbFmtUsed = ScfTools::CheckItem( rItemSet, ATTR_VALUE_FORMAT, IsStyleXF() ); + + // alignment + mbAlignUsed = maAlignment.FillFromItemSet(*this, rItemSet, bForceLineBreak, GetBiff(), IsStyleXF()); + + // cell border + mbBorderUsed = maBorder.FillFromItemSet( rItemSet, GetPalette(), GetBiff(), IsStyleXF() ); + + // background area + mbAreaUsed = maArea.FillFromItemSet( rItemSet, GetPalette(), IsStyleXF() ); + + // set all b***Used flags to true in "Default"/"Normal" style + if( bDefStyle ) + SetAllUsedFlags( true ); +} + +sal_uInt8 XclExpXF::GetUsedFlags() const +{ + sal_uInt8 nUsedFlags = 0; + /* In cell XFs a set bit means a used attribute, in style XFs a cleared bit. + "mbCellXF == mb***Used" evaluates to correct value in cell and style XFs. */ + ::set_flag( nUsedFlags, EXC_XF_DIFF_PROT, mbCellXF == mbProtUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_FONT, mbCellXF == mbFontUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_VALFMT, mbCellXF == mbFmtUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_ALIGN, mbCellXF == mbAlignUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_BORDER, mbCellXF == mbBorderUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_AREA, mbCellXF == mbAreaUsed ); + return nUsedFlags; +} + +void XclExpXF::WriteBody5( XclExpStream& rStrm ) +{ + sal_uInt16 nTypeProt = 0, nAlign = 0; + sal_uInt32 nArea = 0, nBorder = 0; + + ::set_flag( nTypeProt, EXC_XF_STYLE, IsStyleXF() ); + ::insert_value( nTypeProt, mnParent, 4, 12 ); + ::insert_value( nAlign, GetUsedFlags(), 10, 6 ); + + maProtection.FillToXF3( nTypeProt ); + maAlignment.FillToXF5( nAlign ); + maBorder.FillToXF5( nBorder, nArea ); + maArea.FillToXF5( nArea ); + + rStrm << mnXclFont << mnXclNumFmt << nTypeProt << nAlign << nArea << nBorder; +} + +void XclExpXF::WriteBody8( XclExpStream& rStrm ) +{ + sal_uInt16 nTypeProt = 0, nAlign = 0, nMiscAttrib = 0, nArea = 0; + sal_uInt32 nBorder1 = 0, nBorder2 = 0; + + ::set_flag( nTypeProt, EXC_XF_STYLE, IsStyleXF() ); + ::insert_value( nTypeProt, mnParent, 4, 12 ); + ::insert_value( nMiscAttrib, GetUsedFlags(), 10, 6 ); + + maProtection.FillToXF3( nTypeProt ); + maAlignment.FillToXF8( nAlign, nMiscAttrib ); + maBorder.FillToXF8( nBorder1, nBorder2 ); + maArea.FillToXF8( nBorder2, nArea ); + + rStrm << mnXclFont << mnXclNumFmt << nTypeProt << nAlign << nMiscAttrib << nBorder1 << nBorder2 << nArea; +} + +void XclExpXF::WriteBody( XclExpStream& rStrm ) +{ + XclExpXFId aParentId( mnParentXFId ); + aParentId.ConvertXFIndex( GetRoot() ); + mnParent = aParentId.mnXFIndex; + switch( GetBiff() ) + { + case EXC_BIFF5: WriteBody5( rStrm ); break; + case EXC_BIFF8: WriteBody8( rStrm ); break; + default: DBG_ERROR_BIFF(); + } +} + +void XclExpXF::SetXmlIds( sal_uInt32 nBorderId, sal_uInt32 nFillId ) +{ + mnBorderId = nBorderId; + mnFillId = nFillId; +} + +void XclExpXF::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + + sal_Int32 nXfId = 0; + const XclExpXF* pStyleXF = nullptr; + if( IsCellXF() ) + { + sal_uInt16 nXFIndex = rStrm.GetRoot().GetXFBuffer().GetXFIndex( mnParentXFId ); + nXfId = rStrm.GetRoot().GetXFBuffer().GetXmlStyleIndex( nXFIndex ); + pStyleXF = rStrm.GetRoot().GetXFBuffer().GetXFById( mnParentXFId ); + } + + rStyleSheet->startElement( XML_xf, + XML_numFmtId, OString::number(mnXclNumFmt), + XML_fontId, OString::number(mnXclFont), + XML_fillId, OString::number(mnFillId), + XML_borderId, OString::number(mnBorderId), + XML_xfId, sax_fastparser::UseIf(OString::number(nXfId), !IsStyleXF()), + // OOXTODO: XML_quotePrefix, + // OOXTODO: XML_pivotButton, + // OOXTODO: XML_applyNumberFormat, ; + XML_applyFont, ToPsz( mbFontUsed ), + // OOXTODO: XML_applyFill, + XML_applyBorder, ToPsz( mbBorderUsed ), + XML_applyAlignment, ToPsz( mbAlignUsed ), + XML_applyProtection, ToPsz( mbProtUsed ) ); + if( mbAlignUsed ) + maAlignment.SaveXml( rStrm ); + else if ( pStyleXF ) + pStyleXF->GetAlignmentData().SaveXml( rStrm ); + if( mbProtUsed ) + maProtection.SaveXml( rStrm ); + else if ( pStyleXF ) + pStyleXF->GetProtectionData().SaveXml( rStrm ); + + // OOXTODO: XML_extLst + rStyleSheet->endElement( XML_xf ); +} + +XclExpDefaultXF::XclExpDefaultXF( const XclExpRoot& rRoot, bool bCellXF ) : + XclExpXF( rRoot, bCellXF ) +{ +} + +void XclExpDefaultXF::SetFont( sal_uInt16 nXclFont ) +{ + mnXclFont = nXclFont; + mbFontUsed = true; +} + +void XclExpDefaultXF::SetNumFmt( sal_uInt16 nXclNumFmt ) +{ + mnXclNumFmt = nXclNumFmt; + mbFmtUsed = true; +} + +XclExpStyle::XclExpStyle( sal_uInt32 nXFId, const OUString& rStyleName ) : + XclExpRecord( EXC_ID_STYLE, 4 ), + maName( rStyleName ), + maXFId( nXFId ), + mnStyleId( EXC_STYLE_USERDEF ), + mnLevel( EXC_STYLE_NOLEVEL ) +{ + OSL_ENSURE( !maName.isEmpty(), "XclExpStyle::XclExpStyle - empty style name" ); +#if OSL_DEBUG_LEVEL > 0 + sal_uInt8 nStyleId, nLevel; // do not use members for debug tests + OSL_ENSURE( !XclTools::GetBuiltInStyleId( nStyleId, nLevel, maName ), + "XclExpStyle::XclExpStyle - this is a built-in style" ); +#endif +} + +XclExpStyle::XclExpStyle( sal_uInt32 nXFId, sal_uInt8 nStyleId, sal_uInt8 nLevel ) : + XclExpRecord( EXC_ID_STYLE, 4 ), + maXFId( nXFId ), + mnStyleId( nStyleId ), + mnLevel( nLevel ) +{ +} + +void XclExpStyle::WriteBody( XclExpStream& rStrm ) +{ + maXFId.ConvertXFIndex( rStrm.GetRoot() ); + ::set_flag( maXFId.mnXFIndex, EXC_STYLE_BUILTIN, IsBuiltIn() ); + rStrm << maXFId.mnXFIndex; + + if( IsBuiltIn() ) + { + rStrm << mnStyleId << mnLevel; + } + else + { + XclExpString aNameEx; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + aNameEx.Assign( maName ); + else + aNameEx.AssignByte( maName, rStrm.GetRoot().GetTextEncoding(), XclStrFlags::EightBitLength ); + rStrm << aNameEx; + } +} + +static const char* lcl_StyleNameFromId( sal_Int32 nStyleId ) +{ + switch( nStyleId ) + { + case 0: return "Normal"; + case 3: return "Comma"; + case 4: return "Currency"; + case 5: return "Percent"; + case 6: return "Comma [0]"; + case 7: return "Currency [0]"; + } + return "*unknown*"; +} + +void XclExpStyle::SaveXml( XclExpXmlStream& rStrm ) +{ + constexpr sal_Int32 CELL_STYLE_MAX_BUILTIN_ID = 54; + OString sName; + OString sBuiltinId; + const char* pBuiltinId = nullptr; + if( IsBuiltIn() ) + { + sName = OString( lcl_StyleNameFromId( mnStyleId ) ); + sBuiltinId = OString::number( std::min( static_cast<sal_Int32>( CELL_STYLE_MAX_BUILTIN_ID - 1 ), static_cast <sal_Int32>( mnStyleId ) ) ); + pBuiltinId = sBuiltinId.getStr(); + } + else + sName = maName.toUtf8(); + + // get the index in sortedlist associated with the mnXId + sal_Int32 nXFId = rStrm.GetRoot().GetXFBuffer().GetXFIndex( maXFId.mnXFId ); + // get the style index associated with index into sortedlist + nXFId = rStrm.GetRoot().GetXFBuffer().GetXmlStyleIndex( nXFId ); + rStrm.GetCurrentStream()->singleElement( XML_cellStyle, + XML_name, sName, + XML_xfId, OString::number(nXFId), +// builtinId of 54 or above is invalid according to OpenXML SDK validator. + XML_builtinId, pBuiltinId + // OOXTODO: XML_iLevel, + // OOXTODO: XML_hidden, + // XML_customBuiltin, ToPsz( ! IsBuiltIn() ) + ); + // OOXTODO: XML_extLst +} + +namespace { + +const sal_uInt32 EXC_XFLIST_INDEXBASE = 0xFFFE0000; +/** Maximum count of XF records to store in the XF list (performance). */ +const sal_uInt32 EXC_XFLIST_HARDLIMIT = 256 * 1024; + +bool lclIsBuiltInStyle( const OUString& rStyleName ) +{ + return + XclTools::IsBuiltInStyleName( rStyleName ) || + XclTools::IsCondFormatStyleName( rStyleName ); +} + +} // namespace + +XclExpXFBuffer::XclExpBuiltInInfo::XclExpBuiltInInfo() : + mnStyleId( EXC_STYLE_USERDEF ), + mnLevel( EXC_STYLE_NOLEVEL ), + mbPredefined( true ), + mbHasStyleRec( false ) +{ +} + +namespace { + +/** Predicate for search algorithm. */ +struct XclExpBorderPred +{ + const XclExpCellBorder& + mrBorder; + explicit XclExpBorderPred( const XclExpCellBorder& rBorder ) : mrBorder( rBorder ) {} + bool operator()( const XclExpCellBorder& rBorder ) const; +}; + +} + +bool XclExpBorderPred::operator()( const XclExpCellBorder& rBorder ) const +{ + return + mrBorder.mnLeftColor == rBorder.mnLeftColor && + mrBorder.mnRightColor == rBorder.mnRightColor && + mrBorder.mnTopColor == rBorder.mnTopColor && + mrBorder.mnBottomColor == rBorder.mnBottomColor && + mrBorder.mnDiagColor == rBorder.mnDiagColor && + mrBorder.mnLeftLine == rBorder.mnLeftLine && + mrBorder.mnRightLine == rBorder.mnRightLine && + mrBorder.mnTopLine == rBorder.mnTopLine && + mrBorder.mnBottomLine == rBorder.mnBottomLine && + mrBorder.mnDiagLine == rBorder.mnDiagLine && + mrBorder.mbDiagTLtoBR == rBorder.mbDiagTLtoBR && + mrBorder.mbDiagBLtoTR == rBorder.mbDiagBLtoTR && + mrBorder.mnLeftColorId == rBorder.mnLeftColorId && + mrBorder.mnRightColorId == rBorder.mnRightColorId && + mrBorder.mnTopColorId == rBorder.mnTopColorId && + mrBorder.mnBottomColorId == rBorder.mnBottomColorId && + mrBorder.mnDiagColorId == rBorder.mnDiagColorId; +} + +namespace { + +struct XclExpFillPred +{ + const XclExpCellArea& + mrFill; + explicit XclExpFillPred( const XclExpCellArea& rFill ) : mrFill( rFill ) {} + bool operator()( const XclExpCellArea& rFill ) const; +}; + +} + +bool XclExpFillPred::operator()( const XclExpCellArea& rFill ) const +{ + return + mrFill.mnForeColor == rFill.mnForeColor && + mrFill.mnBackColor == rFill.mnBackColor && + mrFill.mnPattern == rFill.mnPattern && + mrFill.mnForeColorId == rFill.mnForeColorId && + mrFill.mnBackColorId == rFill.mnBackColorId; +} + +XclExpXFBuffer::XclExpXFBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpXFBuffer::Initialize() +{ + InsertDefaultRecords(); + InsertUserStyles(); +} + +sal_uInt32 XclExpXFBuffer::Insert( const ScPatternAttr* pPattern, sal_Int16 nScript ) +{ + return InsertCellXF( pPattern, nScript, NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND, false ); +} + +sal_uInt32 XclExpXFBuffer::InsertWithFont( const ScPatternAttr* pPattern, sal_Int16 nScript, + sal_uInt16 nForceXclFont, bool bForceLineBreak ) +{ + return InsertCellXF( pPattern, nScript, NUMBERFORMAT_ENTRY_NOT_FOUND, nForceXclFont, bForceLineBreak ); +} + +sal_uInt32 XclExpXFBuffer::InsertWithNumFmt( const ScPatternAttr* pPattern, sal_Int16 nScript, sal_uInt32 nForceScNumFmt, bool bForceLineBreak ) +{ + return InsertCellXF( pPattern, nScript, nForceScNumFmt, EXC_FONT_NOTFOUND, bForceLineBreak ); +} + +sal_uInt32 XclExpXFBuffer::InsertStyle( const SfxStyleSheetBase* pStyleSheet ) +{ + return pStyleSheet ? InsertStyleXF( *pStyleSheet ) : GetXFIdFromIndex( EXC_XF_DEFAULTSTYLE ); +} + +sal_uInt32 XclExpXFBuffer::GetXFIdFromIndex( sal_uInt16 nXFIndex ) +{ + return EXC_XFLIST_INDEXBASE | nXFIndex; +} + +sal_uInt32 XclExpXFBuffer::GetDefCellXFId() +{ + return GetXFIdFromIndex( EXC_XF_DEFAULTCELL ); +} + +const XclExpXF* XclExpXFBuffer::GetXFById( sal_uInt32 nXFId ) const +{ + return maXFList.GetRecord( nXFId ); +} + +void XclExpXFBuffer::Finalize() +{ + for( size_t nPos = 0, nSize = maXFList.GetSize(); nPos < nSize; ++nPos ) + maXFList.GetRecord( nPos )->SetFinalColors(); + + sal_uInt32 nTotalCount = static_cast< sal_uInt32 >( maXFList.GetSize() ); + sal_uInt32 nId; + maXFIndexVec.resize( nTotalCount, EXC_XF_DEFAULTCELL ); + maStyleIndexes.resize( nTotalCount, EXC_XF_DEFAULTCELL ); + maCellIndexes.resize( nTotalCount, EXC_XF_DEFAULTCELL ); + + XclExpBuiltInMap::const_iterator aBuiltInEnd = maBuiltInMap.end(); + /* nMaxBuiltInXFId used to decide faster whether an XF record is + user-defined. If the current XF ID is greater than this value, + maBuiltInMap doesn't need to be searched. */ + sal_uInt32 nMaxBuiltInXFId = maBuiltInMap.empty() ? 0 : maBuiltInMap.rbegin()->first; + + // *** map all built-in XF records (cell and style) *** ------------------- + + // do not change XF order -> std::map<> iterates elements in ascending order + for( const auto& rEntry : maBuiltInMap ) + AppendXFIndex( rEntry.first ); + + // *** insert all user-defined style XF records, without reduce *** ------- + + sal_uInt32 nStyleXFCount = 0; // counts up to EXC_XF_MAXSTYLECOUNT limit + + for( nId = 0; nId < nTotalCount; ++nId ) + { + XclExpXFRef xXF = maXFList.GetRecord( nId ); + if( xXF->IsStyleXF() && ((nId > nMaxBuiltInXFId) || (maBuiltInMap.find( nId ) == aBuiltInEnd)) ) + { + if( nStyleXFCount < EXC_XF_MAXSTYLECOUNT ) + { + // maximum count of styles not reached + AppendXFIndex( nId ); + ++nStyleXFCount; + } + else + { + /* Maximum count of styles reached - do not append more + pointers to XFs; use default style XF instead; do not break + the loop to initialize all maXFIndexVec elements. */ + maXFIndexVec[ nId ] = EXC_XF_DEFAULTSTYLE; + } + } + } + + // *** insert all cell XF records *** ------------------------------------- + + // start position to search for equal inserted XF records + size_t nSearchStart = maSortedXFList.GetSize(); + + // break the loop if XF limit reached - maXFIndexVec is already initialized with default index + XclExpXFRef xDefCellXF = maXFList.GetRecord( EXC_XF_DEFAULTCELL ); + for( nId = 0; (nId < nTotalCount) && (maSortedXFList.GetSize() < EXC_XF_MAXCOUNT); ++nId ) + { + XclExpXFRef xXF = maXFList.GetRecord( nId ); + if( xXF->IsCellXF() && ((nId > nMaxBuiltInXFId) || (maBuiltInMap.find( nId ) == aBuiltInEnd)) ) + { + // try to find an XF record equal to *xXF, which is already inserted + sal_uInt16 nFoundIndex = EXC_XF_NOTFOUND; + + // first try if it is equal to the default cell XF + if( xDefCellXF->Equals( *xXF ) ) + { + nFoundIndex = EXC_XF_DEFAULTCELL; + } + else for( size_t nSearchPos = nSearchStart, nSearchEnd = maSortedXFList.GetSize(); + (nSearchPos < nSearchEnd) && (nFoundIndex == EXC_XF_NOTFOUND); ++nSearchPos ) + { + if( maSortedXFList.GetRecord( nSearchPos )->Equals( *xXF ) ) + nFoundIndex = static_cast< sal_uInt16 >( nSearchPos ); + } + + if( nFoundIndex != EXC_XF_NOTFOUND ) + // equal XF already in the list, use its resulting XF index + maXFIndexVec[ nId ] = nFoundIndex; + else + AppendXFIndex( nId ); + } + } + + sal_uInt16 nXmlStyleIndex = 0; + sal_uInt16 nXmlCellIndex = 0; + + size_t nXFCount = maSortedXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpXFList::RecordRefType xXF = maSortedXFList.GetRecord( i ); + if( xXF->IsStyleXF() ) + maStyleIndexes[ i ] = nXmlStyleIndex++; + else + maCellIndexes[ i ] = nXmlCellIndex++; + } +} + +sal_uInt16 XclExpXFBuffer::GetXFIndex( sal_uInt32 nXFId ) const +{ + sal_uInt16 nXFIndex = EXC_XF_DEFAULTSTYLE; + if( nXFId >= EXC_XFLIST_INDEXBASE ) + nXFIndex = static_cast< sal_uInt16 >( nXFId & ~EXC_XFLIST_INDEXBASE ); + else if( nXFId < maXFIndexVec.size() ) + nXFIndex = maXFIndexVec[ nXFId ]; + return nXFIndex; +} + +sal_Int32 XclExpXFBuffer::GetXmlStyleIndex( sal_uInt32 nXFIndex ) const +{ + OSL_ENSURE( nXFIndex < maStyleIndexes.size(), "XclExpXFBuffer::GetXmlStyleIndex - invalid index!" ); + if( nXFIndex >= maStyleIndexes.size() ) + return 0; // should be caught/debugged via above assert; return "valid" index. + return maStyleIndexes[ nXFIndex ]; +} + +sal_Int32 XclExpXFBuffer::GetXmlCellIndex( sal_uInt32 nXFIndex ) const +{ + OSL_ENSURE( nXFIndex < maCellIndexes.size(), "XclExpXFBuffer::GetXmlStyleIndex - invalid index!" ); + if( nXFIndex >= maCellIndexes.size() ) + return 0; // should be caught/debugged via above assert; return "valid" index. + return maCellIndexes[ nXFIndex ]; +} + +void XclExpXFBuffer::Save( XclExpStream& rStrm ) +{ + // save all XF records contained in the maSortedXFList vector (sorted by XF index) + maSortedXFList.Save( rStrm ); + // save all STYLE records + maStyleList.Save( rStrm ); +} + +static void lcl_GetCellCounts( const XclExpRecordList< XclExpXF >& rXFList, sal_Int32& rCells, sal_Int32& rStyles ) +{ + rCells = 0; + rStyles = 0; + size_t nXFCount = rXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpRecordList< XclExpXF >::RecordRefType xXF = rXFList.GetRecord( i ); + if( xXF->IsCellXF() ) + ++rCells; + else if( xXF->IsStyleXF() ) + ++rStyles; + } +} + +void XclExpXFBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + + rStyleSheet->startElement(XML_fills, XML_count, OString::number(maFills.size())); + for( const auto& rFill : maFills ) + { + rFill.SaveXml( rStrm ); + } + rStyleSheet->endElement( XML_fills ); + + rStyleSheet->startElement(XML_borders, XML_count, OString::number(maBorders.size())); + for( const auto& rBorder : maBorders ) + { + rBorder.SaveXml( rStrm ); + } + rStyleSheet->endElement( XML_borders ); + + // save all XF records contained in the maSortedXFList vector (sorted by XF index) + sal_Int32 nCells, nStyles; + lcl_GetCellCounts( maSortedXFList, nCells, nStyles ); + + if( nStyles > 0 ) + { + rStyleSheet->startElement(XML_cellStyleXfs, XML_count, OString::number(nStyles)); + size_t nXFCount = maSortedXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpXFList::RecordRefType xXF = maSortedXFList.GetRecord( i ); + if( ! xXF->IsStyleXF() ) + continue; + SaveXFXml( rStrm, *xXF ); + } + rStyleSheet->endElement( XML_cellStyleXfs ); + } + + if( nCells > 0 ) + { + rStyleSheet->startElement(XML_cellXfs, XML_count, OString::number(nCells)); + size_t nXFCount = maSortedXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpXFList::RecordRefType xXF = maSortedXFList.GetRecord( i ); + if( ! xXF->IsCellXF() ) + continue; + SaveXFXml( rStrm, *xXF ); + } + rStyleSheet->endElement( XML_cellXfs ); + } + + // save all STYLE records + rStyleSheet->startElement(XML_cellStyles, XML_count, OString::number(maStyleList.GetSize())); + maStyleList.SaveXml( rStrm ); + rStyleSheet->endElement( XML_cellStyles ); +} + +void XclExpXFBuffer::SaveXFXml( XclExpXmlStream& rStrm, XclExpXF& rXF ) +{ + XclExpBorderList::iterator aBorderPos = + std::find_if( maBorders.begin(), maBorders.end(), XclExpBorderPred( rXF.GetBorderData() ) ); + OSL_ENSURE( aBorderPos != maBorders.end(), "XclExpXFBuffer::SaveXml - Invalid @borderId!" ); + XclExpFillList::iterator aFillPos = + std::find_if( maFills.begin(), maFills.end(), XclExpFillPred( rXF.GetAreaData() ) ); + OSL_ENSURE( aFillPos != maFills.end(), "XclExpXFBuffer::SaveXml - Invalid @fillId!" ); + + sal_Int32 nBorderId = 0, nFillId = 0; + if( aBorderPos != maBorders.end() ) + nBorderId = std::distance( maBorders.begin(), aBorderPos ); + if( aFillPos != maFills.end() ) + nFillId = std::distance( maFills.begin(), aFillPos ); + + rXF.SetXmlIds( nBorderId, nFillId ); + rXF.SaveXml( rStrm ); +} + +sal_uInt32 XclExpXFBuffer::FindXF( const ScPatternAttr& rPattern, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) const +{ + if (nForceScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND && nForceXclFont == EXC_FONT_NOTFOUND) + { + FindKey key1 { /*mbCellXF*/true, &rPattern.GetItemSet(), nForceScNumFmt, 0 }; + FindKey key2 { /*mbCellXF*/true, &rPattern.GetItemSet(), nForceScNumFmt, EXC_FONT_NOTFOUND }; + auto it1 = maXFFindMap.lower_bound(key1); + if (it1 != maXFFindMap.end()) + { + auto it2 = maXFFindMap.upper_bound(key2); + for (auto it = it1; it != it2; ++it) + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ) ) + return nPos; + } + } + else if (nForceScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND || nForceXclFont == EXC_FONT_NOTFOUND) + { + FindKey key1 { /*mbCellXF*/true, &rPattern.GetItemSet(), 0, 0 }; + FindKey key2 { /*mbCellXF*/true, &rPattern.GetItemSet(), NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND }; + auto it1 = maXFFindMap.lower_bound(key1); + if (it1 != maXFFindMap.end()) + { + auto it2 = maXFFindMap.upper_bound(key2); + for (auto it = it1; it != it2; ++it) + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ) ) + return nPos; + } + } + else + { + FindKey key { /*mbCellXF*/true, &rPattern.GetItemSet(), nForceScNumFmt, nForceXclFont }; + auto it = maXFFindMap.find(key); + if (it == maXFFindMap.end()) + return EXC_XFID_NOTFOUND; + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ) ) + return nPos; + } + return EXC_XFID_NOTFOUND; +} + +sal_uInt32 XclExpXFBuffer::FindXF( const SfxStyleSheetBase& rStyleSheet ) const +{ + const SfxItemSet* pItemSet = &const_cast< SfxStyleSheetBase& >( rStyleSheet ).GetItemSet(); + FindKey key1 { /*mbCellXF*/false, pItemSet, 0, 0 }; + FindKey key2 { /*mbCellXF*/false, pItemSet, NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND }; + auto it1 = maXFFindMap.lower_bound(key1); + auto it2 = maXFFindMap.upper_bound(key2); + for (auto it = it1; it != it2; ++it) + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rStyleSheet ) ) + return nPos; + return EXC_XFID_NOTFOUND; +} + +sal_uInt32 XclExpXFBuffer::FindBuiltInXF( sal_uInt8 nStyleId, sal_uInt8 nLevel ) const +{ + auto aIt = std::find_if(maBuiltInMap.begin(), maBuiltInMap.end(), + [&nStyleId, nLevel](const XclExpBuiltInMap::value_type& rEntry) { + return (rEntry.second.mnStyleId == nStyleId) && (rEntry.second.mnLevel == nLevel); + }); + if (aIt != maBuiltInMap.end()) + return aIt->first; + return EXC_XFID_NOTFOUND; +} + +XclExpXFBuffer::FindKey XclExpXFBuffer::ToFindKey(XclExpXF const & rRec) +{ + return { rRec.IsCellXF(), rRec.GetItemSet(), rRec.GetScNumFmt(), rRec.GetXclFont() }; +} + +sal_uInt32 XclExpXFBuffer::InsertCellXF( const ScPatternAttr* pPattern, sal_Int16 nScript, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) +{ + const ScPatternAttr* pDefPattern = GetDoc().GetDefPattern(); + if( !pPattern ) + pPattern = pDefPattern; + + // special handling for default cell formatting + if( (pPattern == pDefPattern) && !bForceLineBreak && + (nForceScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND) && + (nForceXclFont == EXC_FONT_NOTFOUND) ) + { + // Is it the first try to insert the default cell format? + bool& rbPredefined = maBuiltInMap[ EXC_XF_DEFAULTCELL ].mbPredefined; + if( rbPredefined ) + { + // remove old entry in find-map + auto & rPositions = maXFFindMap[ToFindKey(*maXFList.GetRecord(EXC_XF_DEFAULTCELL))]; + auto it = std::find(rPositions.begin(), rPositions.end(), EXC_XF_DEFAULTCELL); + rPositions.erase(it); + // replace default cell pattern + XclExpXFRef xNewXF = new XclExpXF( GetRoot(), *pPattern, nScript ); + maXFList.ReplaceRecord( xNewXF, EXC_XF_DEFAULTCELL ); + // and add new entry in find-map + maXFFindMap[ToFindKey(*xNewXF)].push_back(EXC_XF_DEFAULTCELL); + rbPredefined = false; + } + return GetDefCellXFId(); + } + + sal_uInt32 nXFId = FindXF( *pPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ); + if( nXFId == EXC_XFID_NOTFOUND ) + { + // not found - insert new cell XF + if( maXFList.GetSize() < EXC_XFLIST_HARDLIMIT ) + { + auto pNewExp = new XclExpXF( + GetRoot(), *pPattern, nScript, nForceScNumFmt, nForceXclFont, bForceLineBreak ); + maXFList.AppendNewRecord( pNewExp ); + // do not set nXFId before the AppendNewRecord() call - it may insert 2 XFs (style+cell) + nXFId = static_cast< sal_uInt32 >( maXFList.GetSize() - 1 ); + maXFFindMap[ToFindKey(*pNewExp)].push_back(nXFId); + } + else + { + // list full - fall back to default cell XF + nXFId = GetDefCellXFId(); + } + } + return nXFId; +} + +sal_uInt32 XclExpXFBuffer::InsertStyleXF( const SfxStyleSheetBase& rStyleSheet ) +{ + // *** try, if it is a built-in style - create new XF or replace existing predefined XF *** + + sal_uInt8 nStyleId, nLevel; + if( XclTools::GetBuiltInStyleId( nStyleId, nLevel, rStyleSheet.GetName() ) ) + { + // try to find the built-in XF record (if already created in InsertDefaultRecords()) + sal_uInt32 nXFId = FindBuiltInXF( nStyleId, nLevel ); + if( nXFId == EXC_XFID_NOTFOUND ) + { + // built-in style XF not yet created - do it now + XclExpXFRef xXF = new XclExpXF( GetRoot(), rStyleSheet ); + nXFId = AppendBuiltInXFWithStyle( xXF, nStyleId, nLevel ); + // this new XF record is not predefined + maBuiltInMap[ nXFId ].mbPredefined = false; + } + else + { + OSL_ENSURE( maXFList.HasRecord( nXFId ), "XclExpXFBuffer::InsertStyleXF - built-in XF not found" ); + // XF record still predefined? -> Replace with real XF + bool& rbPredefined = maBuiltInMap[ nXFId ].mbPredefined; + if( rbPredefined ) + { + // remove old entry in find-map + auto & rPositions = maXFFindMap[ToFindKey(*maXFList.GetRecord(nXFId))]; + auto it = std::find(rPositions.begin(), rPositions.end(), nXFId); + rPositions.erase(it); + // replace predefined built-in style (ReplaceRecord() deletes old record) + XclExpXFRef pNewExp = new XclExpXF( GetRoot(), rStyleSheet ); + maXFList.ReplaceRecord( pNewExp, nXFId ); + // and add new entry in find-map + maXFFindMap[ToFindKey(*pNewExp)].push_back(nXFId); + rbPredefined = false; + } + } + + // STYLE already inserted? (may be not, i.e. for RowLevel/ColLevel or Hyperlink styles) + bool& rbHasStyleRec = maBuiltInMap[ nXFId ].mbHasStyleRec; + if( !rbHasStyleRec ) + { + maStyleList.AppendNewRecord( new XclExpStyle( nXFId, nStyleId, nLevel ) ); + rbHasStyleRec = true; + } + + return nXFId; + } + + // *** try to find the XF record of a user-defined style *** + + sal_uInt32 nXFId = FindXF( rStyleSheet ); + if( nXFId == EXC_XFID_NOTFOUND ) + { + // not found - insert new style XF and STYLE + nXFId = static_cast< sal_uInt32 >( maXFList.GetSize() ); + if( nXFId < EXC_XFLIST_HARDLIMIT ) + { + auto pNewExp = new XclExpXF( GetRoot(), rStyleSheet ); + maXFList.AppendNewRecord( pNewExp ); + // create the STYLE record + if( !rStyleSheet.GetName().isEmpty() ) + maStyleList.AppendNewRecord( new XclExpStyle( nXFId, rStyleSheet.GetName() ) ); + maXFFindMap[ToFindKey(*pNewExp)].push_back(nXFId); + } + else + // list full - fall back to default style XF + nXFId = GetXFIdFromIndex( EXC_XF_DEFAULTSTYLE ); + } + return nXFId; +} + +void XclExpXFBuffer::InsertUserStyles() +{ + SfxStyleSheetIterator aStyleIter( GetDoc().GetStyleSheetPool(), SfxStyleFamily::Para ); + for( SfxStyleSheetBase* pStyleSheet = aStyleIter.First(); pStyleSheet; pStyleSheet = aStyleIter.Next() ) + if( pStyleSheet->IsUserDefined() && !lclIsBuiltInStyle( pStyleSheet->GetName() ) ) + InsertStyleXF( *pStyleSheet ); +} + +sal_uInt32 XclExpXFBuffer::AppendBuiltInXF( XclExpXFRef const & xXF, sal_uInt8 nStyleId, sal_uInt8 nLevel ) +{ + sal_uInt32 nXFId = static_cast< sal_uInt32 >( maXFList.GetSize() ); + maXFList.AppendRecord( xXF ); + maXFFindMap[ToFindKey(*xXF)].push_back(nXFId); + XclExpBuiltInInfo& rInfo = maBuiltInMap[ nXFId ]; + rInfo.mnStyleId = nStyleId; + rInfo.mnLevel = nLevel; + rInfo.mbPredefined = true; + return nXFId; +} + +sal_uInt32 XclExpXFBuffer::AppendBuiltInXFWithStyle( XclExpXFRef const & xXF, sal_uInt8 nStyleId, sal_uInt8 nLevel ) +{ + sal_uInt32 nXFId = AppendBuiltInXF( xXF, nStyleId, nLevel ); + maStyleList.AppendNewRecord( new XclExpStyle( nXFId, nStyleId, nLevel ) ); + maBuiltInMap[ nXFId ].mbHasStyleRec = true; // mark existing STYLE record + return nXFId; +} + +static XclExpCellArea lcl_GetPatternFill_None() +{ + XclExpCellArea aFill; + aFill.mnPattern = EXC_PATT_NONE; + return aFill; +} + +static XclExpCellArea lcl_GetPatternFill_Gray125() +{ + XclExpCellArea aFill; + aFill.mnPattern = EXC_PATT_12_5_PERC; + aFill.mnForeColor = 0; + aFill.mnBackColor = 0; + return aFill; +} + +void XclExpXFBuffer::InsertDefaultRecords() +{ + maFills.push_back( lcl_GetPatternFill_None() ); + maFills.push_back( lcl_GetPatternFill_Gray125() ); + + // index 0: default style + if( SfxStyleSheetBase* pDefStyleSheet = GetStyleSheetPool().Find( ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Para ) ) + { + XclExpXFRef xDefStyle = new XclExpXF( GetRoot(), *pDefStyleSheet ); + sal_uInt32 nXFId = AppendBuiltInXFWithStyle( xDefStyle, EXC_STYLE_NORMAL ); + // mark this XF as not predefined, prevents overwriting + maBuiltInMap[ nXFId ].mbPredefined = false; + } + else + { + OSL_FAIL( "XclExpXFBuffer::InsertDefaultRecords - default style not found" ); + XclExpXFRef xDefStyle = new XclExpDefaultXF( GetRoot(), false ); + xDefStyle->SetAllUsedFlags( true ); + AppendBuiltInXFWithStyle( xDefStyle, EXC_STYLE_NORMAL ); + } + + // index 1-14: RowLevel and ColLevel styles (without STYLE records) + XclExpDefaultXF aLevelStyle( GetRoot(), false ); + // RowLevel_1, ColLevel_1 + aLevelStyle.SetFont( 1 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_ROWLEVEL, 0 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_COLLEVEL, 0 ); + // RowLevel_2, ColLevel_2 + aLevelStyle.SetFont( 2 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_ROWLEVEL, 1 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_COLLEVEL, 1 ); + // RowLevel_3, ColLevel_3 ... RowLevel_7, ColLevel_7 + aLevelStyle.SetFont( 0 ); + for( sal_uInt8 nLevel = 2; nLevel < EXC_STYLE_LEVELCOUNT; ++nLevel ) + { + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_ROWLEVEL, nLevel ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_COLLEVEL, nLevel ); + } + + // index 15: default hard cell format, placeholder to be able to add more built-in styles + maXFList.AppendNewRecord( new XclExpDefaultXF( GetRoot(), true ) ); + maXFFindMap[ToFindKey(*maXFList.GetRecord(maXFList.GetSize()-1))].push_back(maXFList.GetSize()-1); + maBuiltInMap[ EXC_XF_DEFAULTCELL ].mbPredefined = true; + + // index 16-20: other built-in styles + XclExpDefaultXF aFormatStyle( GetRoot(), false ); + aFormatStyle.SetFont( 1 ); + aFormatStyle.SetNumFmt( 43 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_COMMA ); + aFormatStyle.SetNumFmt( 41 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_COMMA_0 ); + aFormatStyle.SetNumFmt( 44 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_CURRENCY ); + aFormatStyle.SetNumFmt( 42 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_CURRENCY_0 ); + aFormatStyle.SetNumFmt( 9 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_PERCENT ); + + // other built-in style XF records (i.e. Hyperlink styles) are created on demand + + /* Insert the real default hard cell format -> 0 is document default pattern. + Do it here (and not already above) to really have all built-in styles. */ + Insert( nullptr, GetDefApiScript() ); +} + +void XclExpXFBuffer::AppendXFIndex( sal_uInt32 nXFId ) +{ + OSL_ENSURE( nXFId < maXFIndexVec.size(), "XclExpXFBuffer::AppendXFIndex - XF ID out of range" ); + maXFIndexVec[ nXFId ] = static_cast< sal_uInt16 >( maSortedXFList.GetSize() ); + XclExpXFRef xXF = maXFList.GetRecord( nXFId ); + AddBorderAndFill( *xXF ); + maSortedXFList.AppendRecord( xXF ); + OSL_ENSURE( maXFList.HasRecord( nXFId ), "XclExpXFBuffer::AppendXFIndex - XF not found" ); +} + +void XclExpXFBuffer::AddBorderAndFill( const XclExpXF& rXF ) +{ + if( std::none_of( maBorders.begin(), maBorders.end(), XclExpBorderPred( rXF.GetBorderData() ) ) ) + { + maBorders.push_back( rXF.GetBorderData() ); + } + + if( std::none_of( maFills.begin(), maFills.end(), XclExpFillPred( rXF.GetAreaData() ) ) ) + { + maFills.push_back( rXF.GetAreaData() ); + } +} + +XclExpDxfs::XclExpDxfs( const XclExpRoot& rRoot ) + : XclExpRoot( rRoot ), + mpKeywordTable( new NfKeywordTable ) +{ + // Special number formatter for conversion. + SvNumberFormatterPtr xFormatter(new SvNumberFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US )); + xFormatter->FillKeywordTableForExcel( *mpKeywordTable ); + + SCTAB nTables = rRoot.GetDoc().GetTableCount(); + sal_Int32 nDxfId = 0; + for(SCTAB nTab = 0; nTab < nTables; ++nTab) + { + // Color filters + std::vector<ScDBData*> pDBData = rRoot.GetDoc().GetDBCollection()->GetAllDBsFromTab(nTab); + for (auto& pData : pDBData) + { + ScRange aRange; + pData->GetArea(aRange); + for (auto nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); nCol++) + { + ScFilterEntries aFilterEntries; + rRoot.GetDoc().GetFilterEntriesArea(nCol, aRange.aStart.Row(), + aRange.aEnd.Row(), nTab, true, aFilterEntries); + + // Excel has all filter values stored as foreground colors + // Does not matter it is text color or cell background color + for (auto& rColor : aFilterEntries.getBackgroundColors()) + { + if (!maColorToDxfId.emplace(rColor, nDxfId).second) + continue; + + std::unique_ptr<XclExpCellArea> pExpCellArea(new XclExpCellArea(rColor, 0)); + maDxf.push_back(std::make_unique<XclExpDxf>(rRoot, std::move(pExpCellArea))); + nDxfId++; + } + for (auto& rColor : aFilterEntries.getTextColors()) + { + if (!maColorToDxfId.emplace(rColor, nDxfId).second) + continue; + + std::unique_ptr<XclExpCellArea> pExpCellArea(new XclExpCellArea(rColor, 0)); + maDxf.push_back(std::make_unique<XclExpDxf>(rRoot, std::move(pExpCellArea))); + nDxfId++; + } + } + } + + // Conditional formatting + ScConditionalFormatList* pList = rRoot.GetDoc().GetCondFormList(nTab); + if (pList) + { + for (const auto& rxItem : *pList) + { + size_t nEntryCount = rxItem->size(); + for (size_t nFormatEntry = 0; nFormatEntry < nEntryCount; ++nFormatEntry) + { + const ScFormatEntry* pFormatEntry = rxItem->GetEntry(nFormatEntry); + if (!pFormatEntry + || (pFormatEntry->GetType() != ScFormatEntry::Type::Condition + && pFormatEntry->GetType() != ScFormatEntry::Type::Date + && pFormatEntry->GetType() != ScFormatEntry::Type::ExtCondition)) + continue; + + OUString aStyleName; + if (pFormatEntry->GetType() == ScFormatEntry::Type::Condition + || pFormatEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + const ScCondFormatEntry* pEntry = static_cast<const ScCondFormatEntry*>(pFormatEntry); + aStyleName= pEntry->GetStyle(); + } + else + { + const ScCondDateFormatEntry* pEntry = static_cast<const ScCondDateFormatEntry*>(pFormatEntry); + aStyleName = pEntry->GetStyleName(); + } + + if (maStyleNameToDxfId.emplace(aStyleName, nDxfId).second) + { + SfxStyleSheetBase* pStyle = rRoot.GetDoc().GetStyleSheetPool()->Find(aStyleName, SfxStyleFamily::Para); + if(!pStyle) + continue; + + SfxItemSet& rSet = pStyle->GetItemSet(); + + std::unique_ptr<XclExpCellBorder> pBorder(new XclExpCellBorder); + if (!pBorder->FillFromItemSet( rSet, GetPalette(), GetBiff()) ) + { + pBorder.reset(); + } + + std::unique_ptr<XclExpCellAlign> pAlign(new XclExpCellAlign); + if (!pAlign->FillFromItemSet(rRoot, rSet, false, GetBiff())) + { + pAlign.reset(); + } + + std::unique_ptr<XclExpCellProt> pCellProt(new XclExpCellProt); + if (!pCellProt->FillFromItemSet( rSet )) + { + pCellProt.reset(); + } + + std::unique_ptr<XclExpColor> pColor(new XclExpColor); + if(!pColor->FillFromItemSet( rSet )) + { + pColor.reset(); + } + + std::unique_ptr<XclExpDxfFont> pFont(new XclExpDxfFont(rRoot, rSet)); + + std::unique_ptr<XclExpNumFmt> pNumFormat; + if( const SfxUInt32Item *pPoolItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT ) ) + { + sal_uInt32 nScNumFmt = pPoolItem->GetValue(); + sal_Int32 nXclNumFmt = GetRoot().GetNumFmtBuffer().Insert(nScNumFmt); + pNumFormat.reset(new XclExpNumFmt( nScNumFmt, nXclNumFmt, GetNumberFormatCode( *this, nScNumFmt, xFormatter.get(), mpKeywordTable.get() ))); + } + + maDxf.push_back(std::make_unique<XclExpDxf>( rRoot, std::move(pAlign), std::move(pBorder), + std::move(pFont), std::move(pNumFormat), std::move(pCellProt), std::move(pColor) )); + ++nDxfId; + } + + } + } + } + } +} + +sal_Int32 XclExpDxfs::GetDxfId( const OUString& rStyleName ) const +{ + std::map<OUString, sal_Int32>::const_iterator itr = maStyleNameToDxfId.find(rStyleName); + if(itr!= maStyleNameToDxfId.end()) + return itr->second; + return -1; +} + +sal_Int32 XclExpDxfs::GetDxfByColor(Color aColor) const +{ + std::map<Color, sal_Int32>::const_iterator itr = maColorToDxfId.find(aColor); + if (itr != maColorToDxfId.end()) + return itr->second; + return -1; +} + +void XclExpDxfs::AddColor(Color aColor) +{ + maColorToDxfId.emplace(aColor, maDxf.size()); + + std::unique_ptr<XclExpCellArea> pExpCellArea(new XclExpCellArea(aColor, 0)); + maDxf.push_back(std::make_unique<XclExpDxf>(GetRoot(), std::move(pExpCellArea))); +} + +void XclExpDxfs::SaveXml( XclExpXmlStream& rStrm ) +{ + if(maDxf.empty()) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_dxfs, XML_count, OString::number(maDxf.size())); + + for ( auto& rxDxf : maDxf ) + { + rxDxf->SaveXml( rStrm ); + } + + rStyleSheet->endElement( XML_dxfs ); +} + +XclExpDxf::XclExpDxf( const XclExpRoot& rRoot, std::unique_ptr<XclExpCellAlign> pAlign, std::unique_ptr<XclExpCellBorder> pBorder, + std::unique_ptr<XclExpDxfFont> pFont, std::unique_ptr<XclExpNumFmt> pNumberFmt, std::unique_ptr<XclExpCellProt> pProt, + std::unique_ptr<XclExpColor> pColor) + : XclExpRoot( rRoot ), + mpAlign(std::move(pAlign)), + mpBorder(std::move(pBorder)), + mpFont(std::move(pFont)), + mpNumberFmt(std::move(pNumberFmt)), + mpProt(std::move(pProt)), + mpColor(std::move(pColor)) +{ +} + +XclExpDxf::XclExpDxf(const XclExpRoot& rRoot, std::unique_ptr<XclExpCellArea> pCellArea) + : XclExpRoot(rRoot) + , mpCellArea(std::move(pCellArea)) +{ +} + +XclExpDxf::~XclExpDxf() +{ +} + +void XclExpDxf::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_dxf); + + if (mpFont) + mpFont->SaveXml(rStrm); + if (mpNumberFmt) + mpNumberFmt->SaveXml(rStrm); + if (mpColor) + mpColor->SaveXml(rStrm); + if (mpAlign) + mpAlign->SaveXml(rStrm); + if (mpBorder) + mpBorder->SaveXml(rStrm); + if (mpProt) + mpProt->SaveXml(rStrm); + if (mpCellArea) + mpCellArea->SaveXml(rStrm); + rStyleSheet->endElement( XML_dxf ); +} + +void XclExpDxf::SaveXmlExt( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElementNS( XML_x14, XML_dxf ); + + if (mpFont) + mpFont->SaveXml(rStrm); + if (mpNumberFmt) + mpNumberFmt->SaveXml(rStrm); + if (mpColor) + mpColor->SaveXml(rStrm); + if (mpAlign) + mpAlign->SaveXml(rStrm); + if (mpBorder) + mpBorder->SaveXml(rStrm); + if (mpProt) + mpProt->SaveXml(rStrm); + rStyleSheet->endElementNS( XML_x14, XML_dxf ); +} + + +XclExpXmlStyleSheet::XclExpXmlStyleSheet( const XclExpRoot& rRoot ) + : XclExpRoot( rRoot ) +{ +} + +void XclExpXmlStyleSheet::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr aStyleSheet = rStrm.CreateOutputStream( + "xl/styles.xml", + u"styles.xml", + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", + oox::getRelationship(Relationship::STYLES)); + rStrm.PushStream( aStyleSheet ); + + aStyleSheet->startElement(XML_styleSheet, XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls))); + + CreateRecord( EXC_ID_FORMATLIST )->SaveXml( rStrm ); + CreateRecord( EXC_ID_FONTLIST )->SaveXml( rStrm ); + CreateRecord( EXC_ID_XFLIST )->SaveXml( rStrm ); + CreateRecord( EXC_ID_DXFS )->SaveXml( rStrm ); + CreateRecord( EXC_ID_PALETTE )->SaveXml( rStrm ); + + aStyleSheet->endElement( XML_styleSheet ); + + rStrm.PopStream(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xetable.cxx b/sc/source/filter/excel/xetable.cxx new file mode 100644 index 000000000..728ea2339 --- /dev/null +++ b/sc/source/filter/excel/xetable.cxx @@ -0,0 +1,2841 @@ +/* -*- 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 <xetable.hxx> + +#include <map> +#include <numeric> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <scitems.hxx> +#include <svl/intitem.hxx> +#include <svl/numformat.hxx> +#include <svl/stritem.hxx> +#include <tools/UnitConversion.hxx> +#include <editeng/flditem.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <olinetab.hxx> +#include <formulacell.hxx> +#include <patattr.hxx> +#include <attrib.hxx> +#include <xehelper.hxx> +#include <xecontent.hxx> +#include <xeescher.hxx> +#include <xeextlst.hxx> +#include <xeformula.hxx> +#include <xlcontent.hxx> +#include <xltools.hxx> +#include <tokenarray.hxx> +#include <formula/errorcodes.hxx> +#include <comphelper/threadpool.hxx> +#include <oox/token/tokens.hxx> +#include <oox/export/utils.hxx> + +using namespace ::oox; + +namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + +// Helper records for cell records + +XclExpStringRec::XclExpStringRec( const XclExpRoot& rRoot, const OUString& rResult ) : + XclExpRecord( EXC_ID3_STRING ), + mxResult( XclExpStringHelper::CreateString( rRoot, rResult ) ) +{ + OSL_ENSURE( (rRoot.GetBiff() <= EXC_BIFF5) || (mxResult->Len() > 0), + "XclExpStringRec::XclExpStringRec - empty result not allowed in BIFF8+" ); + SetRecSize( mxResult->GetSize() ); +} + +void XclExpStringRec::WriteBody( XclExpStream& rStrm ) +{ + rStrm << *mxResult; +} + +// Additional records for special formula ranges ============================== + +XclExpRangeFmlaBase::XclExpRangeFmlaBase( + sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScAddress& rScPos ) : + XclExpRecord( nRecId, nRecSize ), + maXclRange( ScAddress::UNINITIALIZED ), + maBaseXclPos( ScAddress::UNINITIALIZED ) +{ + maBaseXclPos.Set( static_cast< sal_uInt16 >( rScPos.Col() ), static_cast< sal_uInt16 >( rScPos.Row() ) ); + maXclRange.maFirst = maXclRange.maLast = maBaseXclPos; +} + +XclExpRangeFmlaBase::XclExpRangeFmlaBase( + sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScRange& rScRange ) : + XclExpRecord( nRecId, nRecSize ), + maXclRange( ScAddress::UNINITIALIZED ), + maBaseXclPos( ScAddress::UNINITIALIZED ) +{ + maXclRange.Set( + static_cast< sal_uInt16 >( rScRange.aStart.Col() ), + static_cast< sal_uInt16 >( rScRange.aStart.Row() ), + static_cast< sal_uInt16 >( rScRange.aEnd.Col() ), + static_cast< sal_uInt16 >( rScRange.aEnd.Row() ) ); + maBaseXclPos = maXclRange.maFirst; +} + +bool XclExpRangeFmlaBase::IsBasePos( sal_uInt16 nXclCol, sal_uInt32 nXclRow ) const +{ + return (maBaseXclPos.mnCol == nXclCol) && (maBaseXclPos.mnRow == nXclRow); +} + +void XclExpRangeFmlaBase::Extend( const ScAddress& rScPos ) +{ + sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() ); + sal_uInt32 nXclRow = static_cast< sal_uInt32 >( rScPos.Row() ); + maXclRange.maFirst.mnCol = ::std::min( maXclRange.maFirst.mnCol, nXclCol ); + maXclRange.maFirst.mnRow = ::std::min( maXclRange.maFirst.mnRow, nXclRow ); + maXclRange.maLast.mnCol = ::std::max( maXclRange.maLast.mnCol, nXclCol ); + maXclRange.maLast.mnRow = ::std::max( maXclRange.maLast.mnRow, nXclRow ); +} + +void XclExpRangeFmlaBase::WriteRangeAddress( XclExpStream& rStrm ) const +{ + maXclRange.Write( rStrm, false ); +} + +// Array formulas ============================================================= + +XclExpArray::XclExpArray( const XclTokenArrayRef& xTokArr, const ScRange& rScRange ) : + XclExpRangeFmlaBase( EXC_ID3_ARRAY, 14 + xTokArr->GetSize(), rScRange ), + mxTokArr( xTokArr ) +{ +} + +XclTokenArrayRef XclExpArray::CreateCellTokenArray( const XclExpRoot& rRoot ) const +{ + return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos ); +} + +bool XclExpArray::IsVolatile() const +{ + return mxTokArr->IsVolatile(); +} + +void XclExpArray::WriteBody( XclExpStream& rStrm ) +{ + WriteRangeAddress( rStrm ); + sal_uInt16 nFlags = EXC_ARRAY_DEFAULTFLAGS; + ::set_flag( nFlags, EXC_ARRAY_RECALC_ALWAYS, IsVolatile() ); + rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr; +} + +XclExpArrayBuffer::XclExpArrayBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpArrayRef XclExpArrayBuffer::CreateArray( const ScTokenArray& rScTokArr, const ScRange& rScRange ) +{ + const ScAddress& rScPos = rScRange.aStart; + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_MATRIX, rScTokArr, &rScPos ); + + OSL_ENSURE( maRecMap.find( rScPos ) == maRecMap.end(), "XclExpArrayBuffer::CreateArray - array exists already" ); + XclExpArrayRef& rxRec = maRecMap[ rScPos ]; + rxRec = new XclExpArray( xTokArr, rScRange ); + return rxRec; +} + +XclExpArrayRef XclExpArrayBuffer::FindArray( const ScTokenArray& rScTokArr, const ScAddress& rBasePos ) const +{ + XclExpArrayRef xRec; + // try to extract a matrix reference token + if (rScTokArr.GetLen() != 1) + // Must consist of a single reference token. + return xRec; + + const formula::FormulaToken* pToken = rScTokArr.GetArray()[0]; + if (!pToken || pToken->GetOpCode() != ocMatRef) + // not a matrix reference token. + return xRec; + + const ScSingleRefData& rRef = *pToken->GetSingleRef(); + ScAddress aAbsPos = rRef.toAbs(GetRoot().GetDoc(), rBasePos); + XclExpArrayMap::const_iterator it = maRecMap.find(aAbsPos); + + if (it != maRecMap.end()) + xRec = it->second; + return xRec; +} + +// Shared formulas ============================================================ + +XclExpShrfmla::XclExpShrfmla( const XclTokenArrayRef& xTokArr, const ScAddress& rScPos ) : + XclExpRangeFmlaBase( EXC_ID_SHRFMLA, 10 + xTokArr->GetSize(), rScPos ), + mxTokArr( xTokArr ), + mnUsedCount( 1 ) +{ +} + +void XclExpShrfmla::ExtendRange( const ScAddress& rScPos ) +{ + Extend( rScPos ); + ++mnUsedCount; +} + +XclTokenArrayRef XclExpShrfmla::CreateCellTokenArray( const XclExpRoot& rRoot ) const +{ + return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos ); +} + +bool XclExpShrfmla::IsVolatile() const +{ + return mxTokArr->IsVolatile(); +} + +void XclExpShrfmla::WriteBody( XclExpStream& rStrm ) +{ + WriteRangeAddress( rStrm ); + rStrm << sal_uInt8( 0 ) << mnUsedCount << *mxTokArr; +} + +XclExpShrfmlaBuffer::XclExpShrfmlaBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +bool XclExpShrfmlaBuffer::IsValidTokenArray( const ScTokenArray& rArray ) const +{ + using namespace formula; + + FormulaToken** pTokens = rArray.GetArray(); + sal_uInt16 nLen = rArray.GetLen(); + for (sal_uInt16 i = 0; i < nLen; ++i) + { + const FormulaToken* p = pTokens[i]; + switch (p->GetType()) + { + case svSingleRef: + { + const ScSingleRefData& rRefData = *p->GetSingleRef(); + if (!GetFormulaCompiler().IsRef2D(rRefData)) + // Excel's shared formula cannot include 3D reference. + return false; + } + break; + case svDoubleRef: + { + const ScComplexRefData& rRefData = *p->GetDoubleRef(); + if (!GetFormulaCompiler().IsRef2D(rRefData)) + // Excel's shared formula cannot include 3D reference. + return false; + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svExternalName: + // External references aren't allowed. + return false; + default: + ; + } + } + return true; +} + +XclExpShrfmlaRef XclExpShrfmlaBuffer::CreateOrExtendShrfmla( + const ScFormulaCell& rScCell, const ScAddress& rScPos ) +{ + XclExpShrfmlaRef xRec; + const ScTokenArray* pShrdScTokArr = rScCell.GetSharedCode(); + if (!pShrdScTokArr) + // This formula cell is not shared formula cell. + return xRec; + + // Check to see if this shared formula contains any tokens that Excel's shared formula cannot handle. + if (maBadTokens.count(pShrdScTokArr) > 0) + // Already on the black list. Skip it. + return xRec; + + if (!IsValidTokenArray(*pShrdScTokArr)) + { + // We can't export this as shared formula. + maBadTokens.insert(pShrdScTokArr); + return xRec; + } + + TokensType::iterator aIt = maRecMap.find(pShrdScTokArr); + if( aIt == maRecMap.end() ) + { + // create a new record + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_SHARED, *pShrdScTokArr, &rScPos ); + xRec = new XclExpShrfmla( xTokArr, rScPos ); + maRecMap[ pShrdScTokArr ] = xRec; + } + else + { + // extend existing record + OSL_ENSURE( aIt->second, "XclExpShrfmlaBuffer::CreateOrExtendShrfmla - missing record" ); + xRec = aIt->second; + xRec->ExtendRange( rScPos ); + } + + return xRec; +} + +// Multiple operations ======================================================== + +XclExpTableop::XclExpTableop( const ScAddress& rScPos, + const XclMultipleOpRefs& rRefs, sal_uInt8 nScMode ) : + XclExpRangeFmlaBase( EXC_ID3_TABLEOP, 16, rScPos ), + mnLastAppXclCol( static_cast< sal_uInt16 >( rScPos.Col() ) ), + mnColInpXclCol( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Col() ) ), + mnColInpXclRow( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Row() ) ), + mnRowInpXclCol( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Col() ) ), + mnRowInpXclRow( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Row() ) ), + mnScMode( nScMode ), + mbValid( false ) +{ +} + +bool XclExpTableop::TryExtend( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs ) +{ + sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() ); + sal_uInt16 nXclRow = static_cast< sal_uInt16 >( rScPos.Row() ); + + bool bOk = IsAppendable( nXclCol, nXclRow ); + if( bOk ) + { + SCCOL nFirstScCol = static_cast< SCCOL >( maXclRange.maFirst.mnCol ); + SCROW nFirstScRow = static_cast< SCROW >( maXclRange.maFirst.mnRow ); + SCCOL nColInpScCol = static_cast< SCCOL >( mnColInpXclCol ); + SCROW nColInpScRow = static_cast< SCROW >( mnColInpXclRow ); + SCCOL nRowInpScCol = static_cast< SCCOL >( mnRowInpXclCol ); + SCROW nRowInpScRow = static_cast< SCROW >( mnRowInpXclRow ); + + bOk = ((mnScMode == 2) == rRefs.mbDblRefMode) && + (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) && + (nColInpScCol == rRefs.maColFirstScPos.Col()) && + (nColInpScRow == rRefs.maColFirstScPos.Row()) && + (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) && + (rScPos.Tab() == rRefs.maColRelScPos.Tab()); + + if( bOk ) switch( mnScMode ) + { + case 0: + bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col()) && + (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) && + (nFirstScCol == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()); + break; + case 1: + bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) && + (rScPos.Row() == rRefs.maFmlaScPos.Row()) && + (rScPos.Col() == rRefs.maColRelScPos.Col()) && + (nFirstScRow == rRefs.maColRelScPos.Row() + 1); + break; + case 2: + bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) && + (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) && + (nFirstScCol == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()) && + (nRowInpScCol == rRefs.maRowFirstScPos.Col()) && + (nRowInpScRow == rRefs.maRowFirstScPos.Row()) && + (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) && + (rScPos.Col() == rRefs.maRowRelScPos.Col()) && + (nFirstScRow == rRefs.maRowRelScPos.Row() + 1) && + (rScPos.Tab() == rRefs.maRowRelScPos.Tab()); + break; + default: + bOk = false; + } + + if( bOk ) + { + // extend the cell range + OSL_ENSURE( IsAppendable( nXclCol, nXclRow ), "XclExpTableop::TryExtend - wrong cell address" ); + Extend( rScPos ); + mnLastAppXclCol = nXclCol; + } + } + + return bOk; +} + +void XclExpTableop::Finalize() +{ + // is the range complete? (last appended cell is in last column) + mbValid = maXclRange.maLast.mnCol == mnLastAppXclCol; + // if last row is incomplete, try to shorten the used range + if( !mbValid && (maXclRange.maFirst.mnRow < maXclRange.maLast.mnRow) ) + { + --maXclRange.maLast.mnRow; + mbValid = true; + } + + // check if referred cells are outside of own range + if( !mbValid ) + return; + + switch( mnScMode ) + { + case 0: + mbValid = (mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) || + (mnColInpXclRow < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow); + break; + case 1: + mbValid = (mnColInpXclCol < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) || + (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow); + break; + case 2: + mbValid = ((mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) || + (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow)) && + ((mnRowInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnRowInpXclCol > maXclRange.maLast.mnCol) || + (mnRowInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnRowInpXclRow > maXclRange.maLast.mnRow)); + break; + } +} + +XclTokenArrayRef XclExpTableop::CreateCellTokenArray( const XclExpRoot& rRoot ) const +{ + XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler(); + return mbValid ? + rFmlaComp.CreateSpecialRefFormula( EXC_TOKID_TBL, maBaseXclPos ) : + rFmlaComp.CreateErrorFormula( EXC_ERR_NA ); +} + +bool XclExpTableop::IsVolatile() const +{ + return true; +} + +void XclExpTableop::Save( XclExpStream& rStrm ) +{ + if( mbValid ) + XclExpRangeFmlaBase::Save( rStrm ); +} + +bool XclExpTableop::IsAppendable( sal_uInt16 nXclCol, sal_uInt16 nXclRow ) const +{ + return ((nXclCol == mnLastAppXclCol + 1) && (nXclRow == maXclRange.maFirst.mnRow)) || + ((nXclCol == mnLastAppXclCol + 1) && (nXclCol <= maXclRange.maLast.mnCol) && (nXclRow == maXclRange.maLast.mnRow)) || + ((mnLastAppXclCol == maXclRange.maLast.mnCol) && (nXclCol == maXclRange.maFirst.mnCol) && (nXclRow == maXclRange.maLast.mnRow + 1)); +} + +void XclExpTableop::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nFlags = EXC_TABLEOP_DEFAULTFLAGS; + ::set_flag( nFlags, EXC_TABLEOP_RECALC_ALWAYS, IsVolatile() ); + switch( mnScMode ) + { + case 1: ::set_flag( nFlags, EXC_TABLEOP_ROW ); break; + case 2: ::set_flag( nFlags, EXC_TABLEOP_BOTH ); break; + } + + WriteRangeAddress( rStrm ); + rStrm << nFlags; + if( mnScMode == 2 ) + rStrm << mnRowInpXclRow << mnRowInpXclCol << mnColInpXclRow << mnColInpXclCol; + else + rStrm << mnColInpXclRow << mnColInpXclCol << sal_uInt32( 0 ); +} + +XclExpTableopBuffer::XclExpTableopBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpTableopRef XclExpTableopBuffer::CreateOrExtendTableop( + const ScTokenArray& rScTokArr, const ScAddress& rScPos ) +{ + XclExpTableopRef xRec; + + // try to extract cell references of a multiple operations formula + XclMultipleOpRefs aRefs; + if (XclTokenArrayHelper::GetMultipleOpRefs(GetDoc(), aRefs, rScTokArr, rScPos)) + { + // try to find an existing TABLEOP record for this cell position + for( size_t nPos = 0, nSize = maTableopList.GetSize(); !xRec && (nPos < nSize); ++nPos ) + { + XclExpTableop* xTempRec = maTableopList.GetRecord( nPos ); + if( xTempRec->TryExtend( rScPos, aRefs ) ) + xRec = xTempRec; + } + + // no record found, or found record not extensible + if( !xRec ) + xRec = TryCreate( rScPos, aRefs ); + } + + return xRec; +} + +void XclExpTableopBuffer::Finalize() +{ + for( size_t nPos = 0, nSize = maTableopList.GetSize(); nPos < nSize; ++nPos ) + maTableopList.GetRecord( nPos )->Finalize(); +} + +XclExpTableopRef XclExpTableopBuffer::TryCreate( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs ) +{ + sal_uInt8 nScMode = 0; + bool bOk = (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) && + (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) && + (rScPos.Tab() == rRefs.maColRelScPos.Tab()); + + if( bOk ) + { + if( rRefs.mbDblRefMode ) + { + nScMode = 2; + bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) && + (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) && + (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()) && + (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) && + (rScPos.Col() == rRefs.maRowRelScPos.Col()) && + (rScPos.Row() == rRefs.maRowRelScPos.Row() + 1) && + (rScPos.Tab() == rRefs.maRowRelScPos.Tab()); + } + else if( (rScPos.Col() == rRefs.maFmlaScPos.Col()) && + (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) && + (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()) ) + { + nScMode = 0; + } + else if( (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) && + (rScPos.Row() == rRefs.maFmlaScPos.Row()) && + (rScPos.Col() == rRefs.maColRelScPos.Col()) && + (rScPos.Row() == rRefs.maColRelScPos.Row() + 1) ) + { + nScMode = 1; + } + else + { + bOk = false; + } + } + + XclExpTableopRef xRec; + if( bOk ) + { + xRec = new XclExpTableop( rScPos, rRefs, nScMode ); + maTableopList.AppendRecord( xRec ); + } + + return xRec; +} + +// Cell records + +XclExpCellBase::XclExpCellBase( + sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos ) : + XclExpRecord( nRecId, nContSize + 4 ), + maXclPos( rXclPos ) +{ +} + +bool XclExpCellBase::IsMultiLineText() const +{ + return false; +} + +bool XclExpCellBase::TryMerge( const XclExpCellBase& /*rCell*/ ) +{ + return false; +} + +void XclExpCellBase::GetBlankXFIndexes( ScfUInt16Vec& /*rXFIndexes*/ ) const +{ + // default: do nothing +} + +void XclExpCellBase::RemoveUnusedBlankCells( const ScfUInt16Vec& /*rXFIndexes*/, size_t /*nStartAllNotFound*/ ) +{ + // default: do nothing +} + +// Single cell records ======================================================== + +XclExpSingleCellBase::XclExpSingleCellBase( + sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos, sal_uInt32 nXFId ) : + XclExpCellBase( nRecId, 2, rXclPos ), + maXFId( nXFId ), + mnContSize( nContSize ) +{ +} + +XclExpSingleCellBase::XclExpSingleCellBase( const XclExpRoot& rRoot, + sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_Int16 nScript, sal_uInt32 nForcedXFId ) : + XclExpCellBase( nRecId, 2, rXclPos ), + maXFId( nForcedXFId ), + mnContSize( nContSize ) +{ + if( GetXFId() == EXC_XFID_NOTFOUND ) + SetXFId( rRoot.GetXFBuffer().Insert( pPattern, nScript ) ); +} + +sal_uInt16 XclExpSingleCellBase::GetLastXclCol() const +{ + return GetXclCol(); +} + +sal_uInt32 XclExpSingleCellBase::GetFirstXFId() const +{ + return GetXFId(); +} + +bool XclExpSingleCellBase::IsEmpty() const +{ + return false; +} + +void XclExpSingleCellBase::ConvertXFIndexes( const XclExpRoot& rRoot ) +{ + maXFId.ConvertXFIndex( rRoot ); +} + +void XclExpSingleCellBase::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 ); + AddRecSize( mnContSize ); + XclExpCellBase::Save( rStrm ); +} + +void XclExpSingleCellBase::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast<sal_uInt16> (GetXclRow()) << GetXclCol() << maXFId.mnXFIndex; + WriteContents( rStrm ); +} + +XclExpNumberCell::XclExpNumberCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, double fValue ) : + // #i41210# always use latin script for number cells - may look wrong for special number formats... + XclExpSingleCellBase( rRoot, EXC_ID3_NUMBER, 8, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ), + mfValue( fValue ) +{ +} + +static OString lcl_GetStyleId( const XclExpXmlStream& rStrm, sal_uInt32 nXFIndex ) +{ + return OString::number( rStrm.GetRoot().GetXFBuffer() + .GetXmlCellIndex( nXFIndex ) ); +} + +static OString lcl_GetStyleId( const XclExpXmlStream& rStrm, const XclExpCellBase& rCell ) +{ + sal_uInt32 nXFId = rCell.GetFirstXFId(); + sal_uInt16 nXFIndex = rStrm.GetRoot().GetXFBuffer().GetXFIndex( nXFId ); + return lcl_GetStyleId( rStrm, nXFIndex ); +} + +void XclExpNumberCell::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, "n" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement(XML_v); + rWorksheet->write( mfValue ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpNumberCell::WriteContents( XclExpStream& rStrm ) +{ + rStrm << mfValue; +} + +XclExpBooleanCell::XclExpBooleanCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, bool bValue ) : + // #i41210# always use latin script for boolean cells + XclExpSingleCellBase( rRoot, EXC_ID3_BOOLERR, 2, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ), + mbValue( bValue ) +{ +} + +void XclExpBooleanCell::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, "b" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement( XML_v ); + rWorksheet->write( mbValue ? "1" : "0" ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpBooleanCell::WriteContents( XclExpStream& rStrm ) +{ + rStrm << sal_uInt16( mbValue ? 1 : 0 ) << EXC_BOOLERR_BOOL; +} + +XclExpLabelCell::XclExpLabelCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, const OUString& rStr ) : + XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId ) +{ + sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN; + XclExpStringRef xText = XclExpStringHelper::CreateCellString( + rRoot, rStr, pPattern, XclStrFlags::NONE, nMaxLen); + Init( rRoot, pPattern, xText ); +} + +XclExpLabelCell::XclExpLabelCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, + const EditTextObject* pEditText, XclExpHyperlinkHelper& rLinkHelper ) : + XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId ) +{ + sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN; + + XclExpStringRef xText; + if (pEditText) + xText = XclExpStringHelper::CreateCellString( + rRoot, *pEditText, pPattern, rLinkHelper, XclStrFlags::NONE, nMaxLen); + else + xText = XclExpStringHelper::CreateCellString( + rRoot, OUString(), pPattern, XclStrFlags::NONE, nMaxLen); + + Init( rRoot, pPattern, xText ); +} + +bool XclExpLabelCell::IsMultiLineText() const +{ + return mbLineBreak || mxText->IsWrapped(); +} + +void XclExpLabelCell::Init( const XclExpRoot& rRoot, + const ScPatternAttr* pPattern, XclExpStringRef const & xText ) +{ + OSL_ENSURE( xText && xText->Len(), "XclExpLabelCell::XclExpLabelCell - empty string passed" ); + mxText = xText; + mnSstIndex = 0; + + const XclFormatRunVec& rFormats = mxText->GetFormats(); + // remove formatting of the leading run if the entire string + // is equally formatted + sal_uInt16 nXclFont = EXC_FONT_NOTFOUND; + if( rFormats.size() == 1 ) + nXclFont = mxText->RemoveLeadingFont(); + else + nXclFont = mxText->GetLeadingFont(); + + // create cell format + if( GetXFId() == EXC_XFID_NOTFOUND ) + { + OSL_ENSURE( nXclFont != EXC_FONT_NOTFOUND, "XclExpLabelCell::Init - leading font not found" ); + bool bForceLineBreak = mxText->IsWrapped(); + SetXFId( rRoot.GetXFBuffer().InsertWithFont( pPattern, ApiScriptType::WEAK, nXclFont, bForceLineBreak ) ); + } + + // get auto-wrap attribute from cell format + const XclExpXF* pXF = rRoot.GetXFBuffer().GetXFById( GetXFId() ); + mbLineBreak = pXF && pXF->GetAlignmentData().mbLineBreak; + + // initialize the record contents + switch( rRoot.GetBiff() ) + { + case EXC_BIFF5: + // BIFF5-BIFF7: create a LABEL or RSTRING record + OSL_ENSURE( mxText->Len() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::XclExpLabelCell - string too long" ); + SetContSize( mxText->GetSize() ); + // formatted string is exported in an RSTRING record + if( mxText->IsRich() ) + { + OSL_ENSURE( mxText->GetFormatsCount() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::WriteContents - too many formats" ); + mxText->LimitFormatCount( EXC_LABEL_MAXLEN ); + SetRecId( EXC_ID_RSTRING ); + SetContSize( GetContSize() + 1 + 2 * mxText->GetFormatsCount() ); + } + break; + case EXC_BIFF8: + // BIFF8+: create a LABELSST record + mnSstIndex = rRoot.GetSst().Insert( xText ); + SetRecId( EXC_ID_LABELSST ); + SetContSize( 4 ); + break; + default: DBG_ERROR_BIFF(); + } +} + +void XclExpLabelCell::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, "s" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement( XML_v ); + rWorksheet->write( static_cast<sal_Int32>(mnSstIndex) ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpLabelCell::WriteContents( XclExpStream& rStrm ) +{ + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF5: + rStrm << *mxText; + if( mxText->IsRich() ) + { + rStrm << static_cast< sal_uInt8 >( mxText->GetFormatsCount() ); + mxText->WriteFormats( rStrm ); + } + break; + case EXC_BIFF8: + rStrm << mnSstIndex; + break; + default: DBG_ERROR_BIFF(); + } +} + +XclExpFormulaCell::XclExpFormulaCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, + const ScFormulaCell& rScFmlaCell, + XclExpArrayBuffer& rArrayBfr, + XclExpShrfmlaBuffer& rShrfmlaBfr, + XclExpTableopBuffer& rTableopBfr ) : + XclExpSingleCellBase( EXC_ID2_FORMULA, 0, rXclPos, nForcedXFId ), + mrScFmlaCell( const_cast< ScFormulaCell& >( rScFmlaCell ) ) +{ + // *** Find result number format overwriting cell number format *** ------- + + if( GetXFId() == EXC_XFID_NOTFOUND ) + { + SvNumberFormatter& rFormatter = rRoot.GetFormatter(); + XclExpNumFmtBuffer& rNumFmtBfr = rRoot.GetNumFmtBuffer(); + + // current cell number format + sal_uInt32 nScNumFmt = pPattern ? + pPattern->GetItemSet().Get( ATTR_VALUE_FORMAT ).GetValue() : + rNumFmtBfr.GetStandardFormat(); + + // alternative number format passed to XF buffer + sal_uInt32 nAltScNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND; + /* Xcl doesn't know Boolean number formats, we write + "TRUE";"FALSE" (language dependent). Don't do it for automatic + formula formats, because Excel gets them right. */ + /* #i8640# Don't set text format, if we have string results. */ + SvNumFormatType nFormatType = mrScFmlaCell.GetFormatType(); + if( ((nScNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0) && + (nFormatType != SvNumFormatType::LOGICAL) && + (nFormatType != SvNumFormatType::TEXT) ) + nAltScNumFmt = nScNumFmt; + /* If cell number format is Boolean and automatic formula + format is Boolean don't write that ugly special format. */ + else if( (nFormatType == SvNumFormatType::LOGICAL) && + (rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL) ) + nAltScNumFmt = rNumFmtBfr.GetStandardFormat(); + + // #i41420# find script type according to result type (always latin for numeric results) + sal_Int16 nScript = ApiScriptType::LATIN; + bool bForceLineBreak = false; + if( nFormatType == SvNumFormatType::TEXT ) + { + OUString aResult = mrScFmlaCell.GetString().getString(); + bForceLineBreak = mrScFmlaCell.IsMultilineResult(); + nScript = XclExpStringHelper::GetLeadingScriptType( rRoot, aResult ); + } + SetXFId( rRoot.GetXFBuffer().InsertWithNumFmt( pPattern, nScript, nAltScNumFmt, bForceLineBreak ) ); + } + + // *** Convert the formula token array *** -------------------------------- + + ScAddress aScPos( static_cast< SCCOL >( rXclPos.mnCol ), static_cast< SCROW >( rXclPos.mnRow ), rRoot.GetCurrScTab() ); + const ScTokenArray& rScTokArr = *mrScFmlaCell.GetCode(); + + // first try to create multiple operations + mxAddRec = rTableopBfr.CreateOrExtendTableop( rScTokArr, aScPos ); + + // no multiple operation found - try to create matrix formula + if( !mxAddRec ) + switch( mrScFmlaCell.GetMatrixFlag() ) + { + case ScMatrixMode::Formula: + { + // origin of the matrix - find the used matrix range + SCCOL nMatWidth; + SCROW nMatHeight; + mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight ); + OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" ); + ScRange aMatScRange( aScPos ); + ScAddress& rMatEnd = aMatScRange.aEnd; + rMatEnd.IncCol( static_cast< SCCOL >( nMatWidth - 1 ) ); + rMatEnd.IncRow( static_cast< SCROW >( nMatHeight - 1 ) ); + // reduce to valid range (range keeps valid, because start position IS valid) + rRoot.GetAddressConverter().ValidateRange( aMatScRange, true ); + // create the ARRAY record + mxAddRec = rArrayBfr.CreateArray( rScTokArr, aMatScRange ); + } + break; + case ScMatrixMode::Reference: + { + // other formula cell covered by a matrix - find the ARRAY record + mxAddRec = rArrayBfr.FindArray(rScTokArr, aScPos); + // should always be found, if Calc document is not broken + OSL_ENSURE( mxAddRec, "XclExpFormulaCell::XclExpFormulaCell - no matrix found" ); + } + break; + default:; + } + + // no matrix found - try to create shared formula + if( !mxAddRec ) + mxAddRec = rShrfmlaBfr.CreateOrExtendShrfmla(mrScFmlaCell, aScPos); + + // no shared formula found - create a simple cell formula + if( !mxAddRec ) + mxTokArr = rRoot.GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CELL, rScTokArr, &aScPos ); +} + +void XclExpFormulaCell::Save( XclExpStream& rStrm ) +{ + // create token array for FORMULA cells with additional record + if( mxAddRec ) + mxTokArr = mxAddRec->CreateCellTokenArray( rStrm.GetRoot() ); + + // FORMULA record itself + OSL_ENSURE( mxTokArr, "XclExpFormulaCell::Save - missing token array" ); + if( !mxTokArr ) + mxTokArr = rStrm.GetRoot().GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NA ); + SetContSize( 16 + mxTokArr->GetSize() ); + XclExpSingleCellBase::Save( rStrm ); + + // additional record (ARRAY, SHRFMLA, or TABLEOP), only for first FORMULA record + if( mxAddRec && mxAddRec->IsBasePos( GetXclCol(), GetXclRow() ) ) + mxAddRec->Save( rStrm ); + + // STRING record for string result + if( mxStringRec ) + mxStringRec->Save( rStrm ); +} + +void XclExpFormulaCell::SaveXml( XclExpXmlStream& rStrm ) +{ + const char* sType = nullptr; + OUString sValue; + XclXmlUtils::GetFormulaTypeAndValue( mrScFmlaCell, sType, sValue ); + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, sType + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + + bool bWriteFormula = true; + bool bTagStarted = false; + ScAddress aScPos( static_cast< SCCOL >( GetXclPos().mnCol ), + static_cast< SCROW >( GetXclPos().mnRow ), rStrm.GetRoot().GetCurrScTab() ); + + switch (mrScFmlaCell.GetMatrixFlag()) + { + case ScMatrixMode::NONE: + break; + case ScMatrixMode::Reference: + bWriteFormula = false; + break; + case ScMatrixMode::Formula: + { + // origin of the matrix - find the used matrix range + SCCOL nMatWidth; + SCROW nMatHeight; + mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight ); + OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" ); + ScRange aMatScRange( aScPos ); + ScAddress& rMatEnd = aMatScRange.aEnd; + rMatEnd.IncCol( static_cast< SCCOL >( nMatWidth - 1 ) ); + rMatEnd.IncRow( static_cast< SCROW >( nMatHeight - 1 ) ); + // reduce to valid range (range keeps valid, because start position IS valid + rStrm.GetRoot().GetAddressConverter().ValidateRange( aMatScRange, true ); + + OStringBuffer sFmlaCellRange; + if (rStrm.GetRoot().GetDoc().ValidRange(aMatScRange)) + { + // calculate the cell range. + sFmlaCellRange.append( XclXmlUtils::ToOString( + rStrm.GetRoot().GetStringBuf(), aMatScRange.aStart ).getStr()); + sFmlaCellRange.append(":"); + sFmlaCellRange.append( XclXmlUtils::ToOString( + rStrm.GetRoot().GetStringBuf(), aMatScRange.aEnd ).getStr()); + } + + if ( aMatScRange.aStart.Col() == GetXclPos().mnCol && + aMatScRange.aStart.Row() == static_cast<SCROW>(GetXclPos().mnRow)) + { + rWorksheet->startElement( XML_f, + XML_aca, ToPsz( (mxTokArr && mxTokArr->IsVolatile()) || + (mxAddRec && mxAddRec->IsVolatile())), + XML_t, mxAddRec ? "array" : nullptr, + XML_ref, !sFmlaCellRange.isEmpty()? sFmlaCellRange.getStr() : nullptr + // OOXTODO: XML_dt2D, bool + // OOXTODO: XML_dtr, bool + // OOXTODO: XML_del1, bool + // OOXTODO: XML_del2, bool + // OOXTODO: XML_r1, ST_CellRef + // OOXTODO: XML_r2, ST_CellRef + // OOXTODO: XML_ca, bool + // OOXTODO: XML_si, uint + // OOXTODO: XML_bx bool + ); + bTagStarted = true; + } + } + break; + } + + if (bWriteFormula) + { + if (!bTagStarted) + { + rWorksheet->startElement( XML_f, + XML_aca, ToPsz( (mxTokArr && mxTokArr->IsVolatile()) || + (mxAddRec && mxAddRec->IsVolatile()) ) ); + } + rWorksheet->writeEscaped( XclXmlUtils::ToOUString( + rStrm.GetRoot().GetCompileFormulaContext(), mrScFmlaCell.aPos, mrScFmlaCell.GetCode(), + mrScFmlaCell.GetErrCode())); + rWorksheet->endElement( XML_f ); + } + + if( strcmp( sType, "inlineStr" ) == 0 ) + { + rWorksheet->startElement(XML_is); + rWorksheet->startElement(XML_t); + rWorksheet->writeEscaped( sValue ); + rWorksheet->endElement( XML_t ); + rWorksheet->endElement( XML_is ); + } + else + { + rWorksheet->startElement(XML_v); + rWorksheet->writeEscaped( sValue ); + rWorksheet->endElement( XML_v ); + } + rWorksheet->endElement( XML_c ); +} + +void XclExpFormulaCell::WriteContents( XclExpStream& rStrm ) +{ + FormulaError nScErrCode = mrScFmlaCell.GetErrCode(); + if( nScErrCode != FormulaError::NONE ) + { + rStrm << EXC_FORMULA_RES_ERROR << sal_uInt8( 0 ) + << XclTools::GetXclErrorCode( nScErrCode ) + << sal_uInt8( 0 ) << sal_uInt16( 0 ) + << sal_uInt16( 0xFFFF ); + } + else + { + // result of the formula + switch( mrScFmlaCell.GetFormatType() ) + { + case SvNumFormatType::NUMBER: + { + // either value or error code + rStrm << mrScFmlaCell.GetValue(); + } + break; + + case SvNumFormatType::TEXT: + { + OUString aResult = mrScFmlaCell.GetString().getString(); + if( !aResult.isEmpty() || (rStrm.GetRoot().GetBiff() <= EXC_BIFF5) ) + { + rStrm << EXC_FORMULA_RES_STRING; + mxStringRec = new XclExpStringRec( rStrm.GetRoot(), aResult ); + } + else + rStrm << EXC_FORMULA_RES_EMPTY; // BIFF8 only + rStrm << sal_uInt8( 0 ) << sal_uInt32( 0 ) << sal_uInt16( 0xFFFF ); + } + break; + + case SvNumFormatType::LOGICAL: + { + sal_uInt8 nXclValue = (mrScFmlaCell.GetValue() == 0.0) ? 0 : 1; + rStrm << EXC_FORMULA_RES_BOOL << sal_uInt8( 0 ) + << nXclValue << sal_uInt8( 0 ) << sal_uInt16( 0 ) + << sal_uInt16( 0xFFFF ); + } + break; + + default: + rStrm << mrScFmlaCell.GetValue(); + } + } + + // flags and formula token array + sal_uInt16 nFlags = EXC_FORMULA_DEFAULTFLAGS; + ::set_flag( nFlags, EXC_FORMULA_RECALC_ALWAYS, mxTokArr->IsVolatile() || (mxAddRec && mxAddRec->IsVolatile()) ); + ::set_flag( nFlags, EXC_FORMULA_SHARED, mxAddRec && (mxAddRec->GetRecId() == EXC_ID_SHRFMLA) ); + rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr; +} + +// Multiple cell records ====================================================== + +XclExpMultiCellBase::XclExpMultiCellBase( + sal_uInt16 nRecId, sal_uInt16 nMulRecId, std::size_t nContSize, const XclAddress& rXclPos ) : + XclExpCellBase( nRecId, 0, rXclPos ), + mnMulRecId( nMulRecId ), + mnContSize( nContSize ) +{ +} + +sal_uInt16 XclExpMultiCellBase::GetLastXclCol() const +{ + return GetXclCol() + GetCellCount() - 1; +} + +sal_uInt32 XclExpMultiCellBase::GetFirstXFId() const +{ + return maXFIds.empty() ? XclExpXFBuffer::GetDefCellXFId() : maXFIds.front().mnXFId; +} + +bool XclExpMultiCellBase::IsEmpty() const +{ + return maXFIds.empty(); +} + +void XclExpMultiCellBase::ConvertXFIndexes( const XclExpRoot& rRoot ) +{ + for( auto& rXFId : maXFIds ) + rXFId.ConvertXFIndex( rRoot ); +} + +void XclExpMultiCellBase::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 ); + + XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end(); + XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin(); + XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg; + sal_uInt16 nBegXclCol = GetXclCol(); + sal_uInt16 nEndXclCol = nBegXclCol; + + while( aRangeEnd != aEnd ) + { + // find begin of next used XF range + aRangeBeg = aRangeEnd; + nBegXclCol = nEndXclCol; + while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) ) + { + nBegXclCol = nBegXclCol + aRangeBeg->mnCount; + ++aRangeBeg; + } + // find end of next used XF range + aRangeEnd = aRangeBeg; + nEndXclCol = nBegXclCol; + while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) ) + { + nEndXclCol = nEndXclCol + aRangeEnd->mnCount; + ++aRangeEnd; + } + + // export this range as a record + if( aRangeBeg != aRangeEnd ) + { + sal_uInt16 nCount = nEndXclCol - nBegXclCol; + bool bIsMulti = nCount > 1; + std::size_t nTotalSize = GetRecSize() + (2 + mnContSize) * nCount; + if( bIsMulti ) nTotalSize += 2; + + rStrm.StartRecord( bIsMulti ? mnMulRecId : GetRecId(), nTotalSize ); + rStrm << static_cast<sal_uInt16> (GetXclRow()) << nBegXclCol; + + sal_uInt16 nRelCol = nBegXclCol - GetXclCol(); + for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt ) + { + for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx ) + { + rStrm << aIt->mnXFIndex; + WriteContents( rStrm, nRelCol ); + ++nRelCol; + } + } + if( bIsMulti ) + rStrm << static_cast< sal_uInt16 >( nEndXclCol - 1 ); + rStrm.EndRecord(); + } + } +} + +void XclExpMultiCellBase::SaveXml( XclExpXmlStream& rStrm ) +{ + XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end(); + XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin(); + XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg; + sal_uInt16 nBegXclCol = GetXclCol(); + sal_uInt16 nEndXclCol = nBegXclCol; + + while( aRangeEnd != aEnd ) + { + // find begin of next used XF range + aRangeBeg = aRangeEnd; + nBegXclCol = nEndXclCol; + while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) ) + { + nBegXclCol = nBegXclCol + aRangeBeg->mnCount; + ++aRangeBeg; + } + // find end of next used XF range + aRangeEnd = aRangeBeg; + nEndXclCol = nBegXclCol; + while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) ) + { + nEndXclCol = nEndXclCol + aRangeEnd->mnCount; + ++aRangeEnd; + } + + // export this range as a record + if( aRangeBeg != aRangeEnd ) + { + sal_uInt16 nRelColIdx = nBegXclCol - GetXclCol(); + sal_Int32 nRelCol = 0; + for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt ) + { + for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx ) + { + WriteXmlContents( + rStrm, + XclAddress( static_cast<sal_uInt16>(nBegXclCol + nRelCol), GetXclRow() ), + aIt->mnXFIndex, + nRelColIdx ); + ++nRelCol; + ++nRelColIdx; + } + } + } + } +} + +sal_uInt16 XclExpMultiCellBase::GetCellCount() const +{ + return std::accumulate(maXFIds.begin(), maXFIds.end(), sal_uInt16(0), + [](const sal_uInt16& rSum, const XclExpMultiXFId& rXFId) { return rSum + rXFId.mnCount; }); +} + +void XclExpMultiCellBase::AppendXFId( const XclExpMultiXFId& rXFId ) +{ + if( maXFIds.empty() || (maXFIds.back().mnXFId != rXFId.mnXFId) ) + maXFIds.push_back( rXFId ); + else + maXFIds.back().mnCount += rXFId.mnCount; +} + +void XclExpMultiCellBase::AppendXFId( const XclExpRoot& rRoot, + const ScPatternAttr* pPattern, sal_uInt16 nScript, sal_uInt32 nForcedXFId, sal_uInt16 nCount ) +{ + sal_uInt32 nXFId = (nForcedXFId == EXC_XFID_NOTFOUND) ? + rRoot.GetXFBuffer().Insert( pPattern, nScript ) : nForcedXFId; + AppendXFId( XclExpMultiXFId( nXFId, nCount ) ); +} + +bool XclExpMultiCellBase::TryMergeXFIds( const XclExpMultiCellBase& rCell ) +{ + if( GetLastXclCol() + 1 == rCell.GetXclCol() ) + { + maXFIds.insert( maXFIds.end(), rCell.maXFIds.begin(), rCell.maXFIds.end() ); + return true; + } + return false; +} + +void XclExpMultiCellBase::GetXFIndexes( ScfUInt16Vec& rXFIndexes ) const +{ + OSL_ENSURE( GetLastXclCol() < rXFIndexes.size(), "XclExpMultiCellBase::GetXFIndexes - vector too small" ); + ScfUInt16Vec::iterator aDestIt = rXFIndexes.begin() + GetXclCol(); + for( const auto& rXFId : maXFIds ) + { + ::std::fill( aDestIt, aDestIt + rXFId.mnCount, rXFId.mnXFIndex ); + aDestIt += rXFId.mnCount; + } +} + +void XclExpMultiCellBase::RemoveUnusedXFIndexes( const ScfUInt16Vec& rXFIndexes, size_t nStartAllNotFound ) +{ + // save last column before calling maXFIds.clear() + sal_uInt16 nLastXclCol = GetLastXclCol(); + OSL_ENSURE( nLastXclCol < rXFIndexes.size(), "XclExpMultiCellBase::RemoveUnusedXFIndexes - XF index vector too small" ); + + // build new XF index vector, containing passed XF indexes + maXFIds.clear(); + // Process only all that possibly are not EXC_XF_NOTFOUND. + size_t nEnd = std::min<size_t>(nLastXclCol + 1, nStartAllNotFound); + for( size_t i = GetXclCol(); i < nEnd; ++i ) + { + XclExpMultiXFId aXFId( 0 ); + // AppendXFId() tests XclExpXFIndex::mnXFId, set it too + aXFId.mnXFId = aXFId.mnXFIndex = rXFIndexes[ i ]; + AppendXFId( aXFId ); + } + + // remove leading and trailing unused XF indexes + if( !maXFIds.empty() && (maXFIds.front().mnXFIndex == EXC_XF_NOTFOUND) ) + { + SetXclCol( GetXclCol() + maXFIds.front().mnCount ); + maXFIds.erase(maXFIds.begin(), maXFIds.begin() + 1); + } + if( !maXFIds.empty() && (maXFIds.back().mnXFIndex == EXC_XF_NOTFOUND) ) + maXFIds.pop_back(); + + // The Save() function will skip all XF indexes equal to EXC_XF_NOTFOUND. +} + +sal_uInt16 XclExpMultiCellBase::GetStartColAllDefaultCell() const +{ + sal_uInt16 col = GetXclCol(); + sal_uInt16 nMaxNonDefCol = col; + for( const auto& rXFId : maXFIds ) + { + col += rXFId.mnCount; + if (rXFId.mnXFIndex != EXC_XF_DEFAULTCELL) + nMaxNonDefCol = col; + } + return nMaxNonDefCol; +} + +XclExpBlankCell::XclExpBlankCell( const XclAddress& rXclPos, const XclExpMultiXFId& rXFId ) : + XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos ) +{ + OSL_ENSURE( rXFId.mnCount > 0, "XclExpBlankCell::XclExpBlankCell - invalid count" ); + AppendXFId( rXFId ); +} + +XclExpBlankCell::XclExpBlankCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, sal_uInt16 nLastXclCol, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId ) : + XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos ) +{ + OSL_ENSURE( rXclPos.mnCol <= nLastXclCol, "XclExpBlankCell::XclExpBlankCell - invalid column range" ); + // #i46627# use default script type instead of ApiScriptType::WEAK + AppendXFId( rRoot, pPattern, rRoot.GetDefApiScript(), nForcedXFId, nLastXclCol - rXclPos.mnCol + 1 ); +} + +bool XclExpBlankCell::TryMerge( const XclExpCellBase& rCell ) +{ + const XclExpBlankCell* pBlankCell = dynamic_cast< const XclExpBlankCell* >( &rCell ); + return pBlankCell && TryMergeXFIds( *pBlankCell ); +} + +void XclExpBlankCell::GetBlankXFIndexes( ScfUInt16Vec& rXFIndexes ) const +{ + GetXFIndexes( rXFIndexes ); +} + +void XclExpBlankCell::RemoveUnusedBlankCells( const ScfUInt16Vec& rXFIndexes, size_t nStartAllNotFound ) +{ + RemoveUnusedXFIndexes( rXFIndexes, nStartAllNotFound ); +} + +void XclExpBlankCell::WriteContents( XclExpStream& /*rStrm*/, sal_uInt16 /*nRelCol*/ ) +{ +} + +void XclExpBlankCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 /* nRelCol */ ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->singleElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), rAddress).getStr(), + XML_s, lcl_GetStyleId(rStrm, nXFId) ); +} + +XclExpRkCell::XclExpRkCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, sal_Int32 nRkValue ) : + XclExpMultiCellBase( EXC_ID_RK, EXC_ID_MULRK, 4, rXclPos ) +{ + // #i41210# always use latin script for number cells - may look wrong for special number formats... + AppendXFId( rRoot, pPattern, ApiScriptType::LATIN, nForcedXFId ); + maRkValues.push_back( nRkValue ); +} + +bool XclExpRkCell::TryMerge( const XclExpCellBase& rCell ) +{ + const XclExpRkCell* pRkCell = dynamic_cast< const XclExpRkCell* >( &rCell ); + if( pRkCell && TryMergeXFIds( *pRkCell ) ) + { + maRkValues.insert( maRkValues.end(), pRkCell->maRkValues.begin(), pRkCell->maRkValues.end() ); + return true; + } + return false; +} + +void XclExpRkCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 nRelCol ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), rAddress).getStr(), + XML_s, lcl_GetStyleId(rStrm, nXFId), + XML_t, "n" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement( XML_v ); + rWorksheet->write( XclTools::GetDoubleFromRK( maRkValues[ nRelCol ] ) ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpRkCell::WriteContents( XclExpStream& rStrm, sal_uInt16 nRelCol ) +{ + OSL_ENSURE( nRelCol < maRkValues.size(), "XclExpRkCell::WriteContents - overflow error" ); + rStrm << maRkValues[ nRelCol ]; +} + +// Rows and Columns + +XclExpOutlineBuffer::XclExpOutlineBuffer( const XclExpRoot& rRoot, bool bRows ) : + mpScOLArray( nullptr ), + maLevelInfos( SC_OL_MAXDEPTH ), + mnCurrLevel( 0 ), + mbCurrCollapse( false ) +{ + if( const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ) ) + mpScOLArray = &(bRows ? pOutlineTable->GetRowArray() : pOutlineTable->GetColArray()); + + if( mpScOLArray ) + for( size_t nLevel = 0; nLevel < SC_OL_MAXDEPTH; ++nLevel ) + if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nLevel, 0 ) ) + maLevelInfos[ nLevel ].mnScEndPos = pEntry->GetEnd(); +} + +void XclExpOutlineBuffer::UpdateColRow( SCCOLROW nScPos ) +{ + if( !mpScOLArray ) + return; + + // find open level index for passed position + size_t nNewOpenScLevel = 0; // new open level (0-based Calc index) + sal_uInt8 nNewLevel = 0; // new open level (1-based Excel index) + + if( mpScOLArray->FindTouchedLevel( nScPos, nScPos, nNewOpenScLevel ) ) + nNewLevel = static_cast< sal_uInt8 >( nNewOpenScLevel + 1 ); + // else nNewLevel keeps 0 to show that there are no groups + + mbCurrCollapse = false; + if( nNewLevel >= mnCurrLevel ) + { + // new level(s) opened, or no level closed - update all level infos + for( size_t nScLevel = 0; nScLevel <= nNewOpenScLevel; ++nScLevel ) + { + /* In each level: check if a new group is started (there may be + neighbored groups without gap - therefore check ALL levels). */ + if( maLevelInfos[ nScLevel ].mnScEndPos < nScPos ) + { + if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nScLevel, nScPos ) ) + { + maLevelInfos[ nScLevel ].mnScEndPos = pEntry->GetEnd(); + maLevelInfos[ nScLevel ].mbHidden = pEntry->IsHidden(); + } + } + } + } + else + { + // level(s) closed - check if any of the closed levels are collapsed + // Calc uses 0-based level indexes + sal_uInt16 nOldOpenScLevel = mnCurrLevel - 1; + for( sal_uInt16 nScLevel = nNewOpenScLevel + 1; !mbCurrCollapse && (nScLevel <= nOldOpenScLevel); ++nScLevel ) + mbCurrCollapse = maLevelInfos[ nScLevel ].mbHidden; + } + + // cache new opened level + mnCurrLevel = nNewLevel; +} + +XclExpGuts::XclExpGuts( const XclExpRoot& rRoot ) : + XclExpRecord( EXC_ID_GUTS, 8 ), + mnColLevels( 0 ), + mnColWidth( 0 ), + mnRowLevels( 0 ), + mnRowWidth( 0 ) +{ + const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ); + if(!pOutlineTable) + return; + + // column outline groups + const ScOutlineArray& rColArray = pOutlineTable->GetColArray(); + mnColLevels = ulimit_cast< sal_uInt16 >( rColArray.GetDepth(), EXC_OUTLINE_MAX ); + if( mnColLevels ) + { + ++mnColLevels; + mnColWidth = 12 * mnColLevels + 5; + } + + // row outline groups + const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray(); + mnRowLevels = ulimit_cast< sal_uInt16 >( rRowArray.GetDepth(), EXC_OUTLINE_MAX ); + if( mnRowLevels ) + { + ++mnRowLevels; + mnRowWidth = 12 * mnRowLevels + 5; + } +} + +void XclExpGuts::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnRowWidth << mnColWidth << mnRowLevels << mnColLevels; +} + +XclExpDimensions::XclExpDimensions( const XclExpRoot& rRoot ) : + mrRoot(rRoot), + mnFirstUsedXclRow( 0 ), + mnFirstFreeXclRow( 0 ), + mnFirstUsedXclCol( 0 ), + mnFirstFreeXclCol( 0 ) +{ + switch( rRoot.GetBiff() ) + { + case EXC_BIFF2: SetRecHeader( EXC_ID2_DIMENSIONS, 8 ); break; + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: SetRecHeader( EXC_ID3_DIMENSIONS, 10 ); break; + case EXC_BIFF8: SetRecHeader( EXC_ID3_DIMENSIONS, 14 ); break; + default: DBG_ERROR_BIFF(); + } +} + +void XclExpDimensions::SetDimensions( + sal_uInt16 nFirstUsedXclCol, sal_uInt32 nFirstUsedXclRow, + sal_uInt16 nFirstFreeXclCol, sal_uInt32 nFirstFreeXclRow ) +{ + mnFirstUsedXclRow = nFirstUsedXclRow; + mnFirstFreeXclRow = nFirstFreeXclRow; + mnFirstUsedXclCol = nFirstUsedXclCol; + mnFirstFreeXclCol = nFirstFreeXclCol; +} + +void XclExpDimensions::SaveXml( XclExpXmlStream& rStrm ) +{ + ScRange aRange; + aRange.aStart.SetRow( static_cast<SCROW>(mnFirstUsedXclRow) ); + aRange.aStart.SetCol( static_cast<SCCOL>(mnFirstUsedXclCol) ); + + if( mnFirstFreeXclRow != mnFirstUsedXclRow && mnFirstFreeXclCol != mnFirstUsedXclCol ) + { + aRange.aEnd.SetRow( static_cast<SCROW>(mnFirstFreeXclRow-1) ); + aRange.aEnd.SetCol( static_cast<SCCOL>(mnFirstFreeXclCol-1) ); + } + + aRange.PutInOrder(); + rStrm.GetCurrentStream()->singleElement( XML_dimension, + // To be compatible with MS Office 2007, + // we need full address notation format + // e.g. "A1:AMJ177" and not partial like: "1:177". + XML_ref, XclXmlUtils::ToOString(mrRoot.GetDoc(), aRange, true) ); +} + +void XclExpDimensions::WriteBody( XclExpStream& rStrm ) +{ + XclBiff eBiff = rStrm.GetRoot().GetBiff(); + if( eBiff == EXC_BIFF8 ) + rStrm << mnFirstUsedXclRow << mnFirstFreeXclRow; + else + rStrm << static_cast< sal_uInt16 >( mnFirstUsedXclRow ) << static_cast< sal_uInt16 >( mnFirstFreeXclRow ); + rStrm << mnFirstUsedXclCol << mnFirstFreeXclCol; + if( eBiff >= EXC_BIFF3 ) + rStrm << sal_uInt16( 0 ); +} + +namespace { + +double lclGetCChCorrection(const XclExpRoot& rRoot) +{ + // Convert the correction from 1/256ths of a character size to count of chars + // TODO: make to fit ECMA-376-1:2016 18.3.1.81 sheetFormatPr (Sheet Format Properties): + // 5 pixels are added to the base width: 2 for margin padding on each side, plus 1 for gridline + // So this should depend on rRoot.GetCharWidth(), not on font height + + tools::Long nFontHt = rRoot.GetFontBuffer().GetAppFontData().mnHeight; + return XclTools::GetXclDefColWidthCorrection(nFontHt) / 256.0; +} + +} // namespace + +XclExpDefcolwidth::XclExpDefcolwidth( const XclExpRoot& rRoot ) : + XclExpDoubleRecord(EXC_ID_DEFCOLWIDTH, EXC_DEFCOLWIDTH_DEF + lclGetCChCorrection(rRoot)), + XclExpRoot( rRoot ) +{ +} + +bool XclExpDefcolwidth::IsDefWidth( sal_uInt16 nXclColWidth ) const +{ + // This formula is taking number of characters with GetValue() + // and it is translating it into default column width. + // https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.column.aspx + double defaultColumnWidth = 256.0 * GetValue(); + + // exactly matched, if difference is less than 1/16 of a character to the left or to the right + return std::abs(defaultColumnWidth - nXclColWidth) < 256.0 * 1.0 / 16.0; +} + +void XclExpDefcolwidth::SetDefWidth( sal_uInt16 nXclColWidth, bool bXLS ) +{ + double fCCh = nXclColWidth / 256.0; + if (bXLS) + { + const double fCorrection = lclGetCChCorrection(GetRoot()); + const double fCorrectedCCh = fCCh - fCorrection; + // Now get the value which would be stored in XLS DefColWidth struct + double fCChRound = std::round(fCorrectedCCh); + // If default width was set to a value that is not representable as integral CCh between 0 + // and 255, then just ignore that value, and use an arbitrary default. That way, the stored + // default might not represent the most used column width (or any used column width), but + // that's OK, and it just means that those columns will explicitly store their width in + // 1/256ths of char, and have fUserSet in their ColInfo records. + if (fCChRound < 0.0 || fCChRound > 255.0 || std::abs(fCChRound - fCorrectedCCh) > 1.0 / 512) + fCChRound = 8.0; + fCCh = fCChRound + fCorrection; + } + + SetValue(fCCh); +} + +void XclExpDefcolwidth::Save(XclExpStream& rStrm) +{ + double fCorrectedCCh = GetValue() - lclGetCChCorrection(GetRoot()); + // Convert double to sal_uInt16 + XclExpUInt16Record aUInt16Rec(GetRecId(), + static_cast<sal_uInt16>(std::round(fCorrectedCCh))); + aUInt16Rec.Save(rStrm); +} + +XclExpColinfo::XclExpColinfo( const XclExpRoot& rRoot, + SCCOL nScCol, SCROW nLastScRow, XclExpColOutlineBuffer& rOutlineBfr ) : + XclExpRecord( EXC_ID_COLINFO, 12 ), + XclExpRoot( rRoot ), + mbCustomWidth( false ), + mnWidth( 0 ), + mnScWidth( 0 ), + mnFlags( 0 ), + mnOutlineLevel( 0 ), + mnFirstXclCol( static_cast< sal_uInt16 >( nScCol ) ), + mnLastXclCol( static_cast< sal_uInt16 >( nScCol ) ) +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + + // column default format + maXFId.mnXFId = GetXFBuffer().Insert( + rDoc.GetMostUsedPattern( nScCol, 0, nLastScRow, nScTab ), GetDefApiScript() ); + + // column width. If column is hidden then we should return real value (not zero) + sal_uInt16 nScWidth = rDoc.GetColWidth( nScCol, nScTab, false ); + mnWidth = XclTools::GetXclColumnWidth( nScWidth, GetCharWidth() ); + mnScWidth = convertTwipToMm100(nScWidth); + + // column flags + ::set_flag( mnFlags, EXC_COLINFO_HIDDEN, rDoc.ColHidden(nScCol, nScTab) ); + + // outline data + rOutlineBfr.Update( nScCol ); + ::set_flag( mnFlags, EXC_COLINFO_COLLAPSED, rOutlineBfr.IsCollapsed() ); + ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 8, 3 ); + mnOutlineLevel = rOutlineBfr.GetLevel(); +} + +void XclExpColinfo::ConvertXFIndexes() +{ + maXFId.ConvertXFIndex( GetRoot() ); +} + +bool XclExpColinfo::IsDefault( const XclExpDefcolwidth& rDefColWidth ) +{ + mbCustomWidth = !rDefColWidth.IsDefWidth(mnWidth); + return (maXFId.mnXFIndex == EXC_XF_DEFAULTCELL) && + (mnFlags == 0) && + (mnOutlineLevel == 0) && + !mbCustomWidth; +} + +bool XclExpColinfo::TryMerge( const XclExpColinfo& rColInfo ) +{ + if( (maXFId.mnXFIndex == rColInfo.maXFId.mnXFIndex) && + (mnWidth == rColInfo.mnWidth) && + (mnFlags == rColInfo.mnFlags) && + (mnOutlineLevel == rColInfo.mnOutlineLevel) && + (mnLastXclCol + 1 == rColInfo.mnFirstXclCol) ) + { + mnLastXclCol = rColInfo.mnLastXclCol; + return true; + } + return false; +} + +void XclExpColinfo::WriteBody( XclExpStream& rStrm ) +{ + // if last column is equal to last possible column, Excel adds one more + sal_uInt16 nLastXclCol = mnLastXclCol; + if( nLastXclCol == static_cast< sal_uInt16 >( rStrm.GetRoot().GetMaxPos().Col() ) ) + ++nLastXclCol; + + rStrm << mnFirstXclCol + << nLastXclCol + << mnWidth + << maXFId.mnXFIndex + << mnFlags + << sal_uInt16( 0 ); +} + +void XclExpColinfo::SaveXml( XclExpXmlStream& rStrm ) +{ + const double nExcelColumnWidth = mnScWidth / convertTwipToMm100<double>(GetCharWidth()); + + // tdf#101363 In MS specification the output value is set with double precision after delimiter: + // =Truncate(({width in pixels} - 5)/{Maximum Digit Width} * 100 + 0.5)/100 + // Explanation of magic numbers: + // 5 number - are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines. + // It is unknown if it should be applied during LibreOffice export + // 100 number - used to limit precision to 0.01 with formula =Truncate( {value}*100+0.5 ) / 100 + // 0.5 number (0.005 to output value) - used to increase value before truncating, + // to avoid situation when 2.997 will be truncated to 2.99 and not to 3.00 + const double nTruncatedExcelColumnWidth = std::trunc( nExcelColumnWidth * 100.0 + 0.5 ) / 100.0; + rStrm.GetCurrentStream()->singleElement( XML_col, + // OOXTODO: XML_bestFit, + XML_collapsed, ToPsz( ::get_flag( mnFlags, EXC_COLINFO_COLLAPSED ) ), + XML_customWidth, ToPsz( mbCustomWidth ), + XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_COLINFO_HIDDEN ) ), + XML_outlineLevel, OString::number(mnOutlineLevel), + XML_max, OString::number(mnLastXclCol + 1), + XML_min, OString::number(mnFirstXclCol + 1), + // OOXTODO: XML_phonetic, + XML_style, lcl_GetStyleId(rStrm, maXFId.mnXFIndex), + XML_width, OString::number(nTruncatedExcelColumnWidth) ); +} + +XclExpColinfoBuffer::XclExpColinfoBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maDefcolwidth( rRoot ), + maOutlineBfr( rRoot ), + mnHighestOutlineLevel( 0 ) +{ +} + +void XclExpColinfoBuffer::Initialize( SCROW nLastScRow ) +{ + + for( sal_uInt16 nScCol = 0, nLastScCol = GetMaxPos().Col(); nScCol <= nLastScCol; ++nScCol ) + { + maColInfos.AppendNewRecord( new XclExpColinfo( GetRoot(), nScCol, nLastScRow, maOutlineBfr ) ); + if( maOutlineBfr.GetLevel() > mnHighestOutlineLevel ) + { + mnHighestOutlineLevel = maOutlineBfr.GetLevel(); + } + } +} + +void XclExpColinfoBuffer::Finalize( ScfUInt16Vec& rXFIndexes, bool bXLS ) +{ + rXFIndexes.clear(); + rXFIndexes.reserve( maColInfos.GetSize() ); + + if( !maColInfos.IsEmpty()) + { + XclExpColinfo* xPrevRec = maColInfos.GetRecord( 0 ); + xPrevRec->ConvertXFIndexes(); + for( size_t nPos = 1; nPos < maColInfos.GetSize(); ++nPos ) + { + XclExpColinfo* xRec = maColInfos.GetRecord( nPos ); + xRec->ConvertXFIndexes(); + + // try to merge with previous record + if( xPrevRec->TryMerge( *xRec ) ) + maColInfos.InvalidateRecord( nPos ); + else + xPrevRec = xRec; + } + maColInfos.RemoveInvalidatedRecords(); + } + + // put XF indexes into passed vector, collect use count of all different widths + std::unordered_map< sal_uInt16, sal_uInt16 > aWidthMap; + sal_uInt16 nMaxColCount = 0; + sal_uInt16 nMaxUsedWidth = 0; + for( size_t nPos = 0; nPos < maColInfos.GetSize(); ++nPos ) + { + const XclExpColinfo* xRec = maColInfos.GetRecord( nPos ); + sal_uInt16 nColCount = xRec->GetColCount(); + + // add XF index to passed vector + rXFIndexes.resize( rXFIndexes.size() + nColCount, xRec->GetXFIndex() ); + + // collect use count of column width + sal_uInt16 nWidth = xRec->GetColWidth(); + sal_uInt16& rnMapCount = aWidthMap[ nWidth ]; + rnMapCount = rnMapCount + nColCount; + if( rnMapCount > nMaxColCount ) + { + nMaxColCount = rnMapCount; + nMaxUsedWidth = nWidth; + } + } + maDefcolwidth.SetDefWidth( nMaxUsedWidth, bXLS ); + + // remove all default COLINFO records + for( size_t nPos = 0; nPos < maColInfos.GetSize(); ++nPos ) + { + XclExpColinfo* xRec = maColInfos.GetRecord( nPos ); + if( xRec->IsDefault( maDefcolwidth ) ) + maColInfos.InvalidateRecord( nPos ); + } + maColInfos.RemoveInvalidatedRecords(); +} + +void XclExpColinfoBuffer::Save( XclExpStream& rStrm ) +{ + // DEFCOLWIDTH + maDefcolwidth.Save( rStrm ); + // COLINFO records + maColInfos.Save( rStrm ); +} + +void XclExpColinfoBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maColInfos.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_cols); + maColInfos.SaveXml( rStrm ); + rWorksheet->endElement( XML_cols ); +} + +XclExpDefaultRowData::XclExpDefaultRowData() : + mnFlags( EXC_DEFROW_DEFAULTFLAGS ), + mnHeight( EXC_DEFROW_DEFAULTHEIGHT ) +{ +} + +XclExpDefaultRowData::XclExpDefaultRowData( const XclExpRow& rRow ) : + mnFlags( EXC_DEFROW_DEFAULTFLAGS ), + mnHeight( rRow.GetHeight() ) +{ + ::set_flag( mnFlags, EXC_DEFROW_HIDDEN, rRow.IsHidden() ); + ::set_flag( mnFlags, EXC_DEFROW_UNSYNCED, rRow.IsUnsynced() ); +} + +static bool operator<( const XclExpDefaultRowData& rLeft, const XclExpDefaultRowData& rRight ) +{ + return (rLeft.mnHeight < rRight.mnHeight) || + ((rLeft.mnHeight == rRight.mnHeight) && (rLeft.mnFlags < rRight.mnFlags)); +} + +XclExpDefrowheight::XclExpDefrowheight() : + XclExpRecord( EXC_ID3_DEFROWHEIGHT, 4 ) +{ +} + +void XclExpDefrowheight::SetDefaultData( const XclExpDefaultRowData& rDefData ) +{ + maDefData = rDefData; +} + +void XclExpDefrowheight::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 ); + rStrm << maDefData.mnFlags << maDefData.mnHeight; +} + +XclExpRow::XclExpRow( const XclExpRoot& rRoot, sal_uInt32 nXclRow, + XclExpRowOutlineBuffer& rOutlineBfr, bool bAlwaysEmpty, bool bHidden, sal_uInt16 nHeight ) : + XclExpRecord( EXC_ID3_ROW, 16 ), + XclExpRoot( rRoot ), + mnXclRow( nXclRow ), + mnHeight( nHeight ), + mnFlags( EXC_ROW_DEFAULTFLAGS ), + mnXFIndex( EXC_XF_DEFAULTCELL ), + mnOutlineLevel( 0 ), + mnXclRowRpt( 1 ), + mnCurrentRow( nXclRow ), + mbAlwaysEmpty( bAlwaysEmpty ), + mbEnabled( true ) +{ + SCTAB nScTab = GetCurrScTab(); + SCROW nScRow = static_cast< SCROW >( mnXclRow ); + + // *** Row flags *** ------------------------------------------------------ + + CRFlags nRowFlags = GetDoc().GetRowFlags( nScRow, nScTab ); + bool bUserHeight( nRowFlags & CRFlags::ManualSize ); + ::set_flag( mnFlags, EXC_ROW_UNSYNCED, bUserHeight ); + ::set_flag( mnFlags, EXC_ROW_HIDDEN, bHidden ); + + // *** Outline data *** --------------------------------------------------- + + rOutlineBfr.Update( nScRow ); + ::set_flag( mnFlags, EXC_ROW_COLLAPSED, rOutlineBfr.IsCollapsed() ); + ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 0, 3 ); + mnOutlineLevel = rOutlineBfr.GetLevel(); + + // *** Progress bar *** --------------------------------------------------- + + XclExpProgressBar& rProgress = GetProgressBar(); + rProgress.IncRowRecordCount(); + rProgress.Progress(); +} + +static size_t findFirstAllSameUntilEnd( const ScfUInt16Vec& rIndexes, sal_uInt16 value, + size_t searchStart = std::numeric_limits<size_t>::max()) +{ + for( size_t i = std::min(rIndexes.size(), searchStart); i >= 1; --i ) + { + if( rIndexes[ i - 1 ] != value ) + return i; + } + return 0; +} + +void XclExpRow::AppendCell( XclExpCellRef const & xCell, bool bIsMergedBase ) +{ + OSL_ENSURE( !mbAlwaysEmpty, "XclExpRow::AppendCell - row is marked to be always empty" ); + // try to merge with last existing cell + InsertCell( xCell, maCellList.GetSize(), bIsMergedBase ); +} + +void XclExpRow::Finalize( const ScfUInt16Vec& rColXFIndexes, ScfUInt16Vec& aXFIndexes, size_t nStartColAllDefault, bool bProgress ) +{ + size_t nPos, nSize; + + // *** Convert XF identifiers *** ----------------------------------------- + + // additionally collect the blank XF indexes + size_t nColCount = GetMaxPos().Col() + 1; + OSL_ENSURE( rColXFIndexes.size() == nColCount, "XclExpRow::Finalize - wrong column XF index count" ); + + // The vector should be preset to all items being EXC_XF_NOTFOUND, to avoid repeated allocations + // and clearing. + assert( aXFIndexes.size() == nColCount ); + assert( aXFIndexes.front() == EXC_XF_NOTFOUND ); + assert( aXFIndexes.back() == EXC_XF_NOTFOUND ); + for( nPos = 0, nSize = maCellList.GetSize(); nPos < nSize; ++nPos ) + { + XclExpCellBase* pCell = maCellList.GetRecord( nPos ); + pCell->ConvertXFIndexes( GetRoot() ); + pCell->GetBlankXFIndexes( aXFIndexes ); + } + + // *** Fill gaps with BLANK/MULBLANK cell records *** --------------------- + + /* This is needed because nonexistent cells in Calc are not formatted at all, + but in Excel they would have the column default format. Blank cells that + are equal to the respective column default are removed later in this function. */ + if( !mbAlwaysEmpty ) + { + // XF identifier representing default cell XF + XclExpMultiXFId aXFId( XclExpXFBuffer::GetDefCellXFId() ); + aXFId.ConvertXFIndex( GetRoot() ); + + nPos = 0; + while( nPos <= maCellList.GetSize() ) // don't cache list size, may change in the loop + { + // get column index that follows previous cell + sal_uInt16 nFirstFreeXclCol = (nPos > 0) ? (maCellList.GetRecord( nPos - 1 )->GetLastXclCol() + 1) : 0; + // get own column index + sal_uInt16 nNextUsedXclCol = (nPos < maCellList.GetSize()) ? maCellList.GetRecord( nPos )->GetXclCol() : (GetMaxPos().Col() + 1); + + // is there a gap? + if( nFirstFreeXclCol < nNextUsedXclCol ) + { + aXFId.mnCount = nNextUsedXclCol - nFirstFreeXclCol; + XclExpCellRef xNewCell = new XclExpBlankCell( XclAddress( nFirstFreeXclCol, mnXclRow ), aXFId ); + // insert the cell, InsertCell() may merge it with existing BLANK records + InsertCell( xNewCell, nPos, false ); + // insert default XF indexes into aXFIndexes + for( size_t i = nFirstFreeXclCol; i < nNextUsedXclCol; ++i ) + aXFIndexes[ i ] = aXFId.mnXFIndex; + // don't step forward with nPos, InsertCell() may remove records + } + else + ++nPos; + } + } + + // *** Find default row format *** ---------------------------------------- + + // Often there will be many EXC_XF_DEFAULTCELL at the end, optimize by ignoring them. + size_t nStartSearchAllDefault = aXFIndexes.size(); + if( !maCellList.IsEmpty() && dynamic_cast< const XclExpBlankCell* >( maCellList.GetLastRecord())) + { + const XclExpBlankCell* pLastBlank = static_cast< const XclExpBlankCell* >( maCellList.GetLastRecord()); + assert(pLastBlank->GetLastXclCol() == aXFIndexes.size() - 1); + nStartSearchAllDefault = pLastBlank->GetStartColAllDefaultCell(); + } + size_t nStartAllDefault = findFirstAllSameUntilEnd( aXFIndexes, EXC_XF_DEFAULTCELL, nStartSearchAllDefault); + + // find most used XF index in the row + sal_uInt16 nRowXFIndex = EXC_XF_DEFAULTCELL; + const size_t nHalfIndexes = aXFIndexes.size() / 2; + if( nStartAllDefault > nHalfIndexes ) // Otherwise most are EXC_XF_DEFAULTCELL. + { + // Very likely the most common one is going to be the last one. + nRowXFIndex = aXFIndexes.back(); + size_t nStartLastSame = findFirstAllSameUntilEnd( aXFIndexes, nRowXFIndex ); + if( nStartLastSame > nHalfIndexes ) // No, find out the most used one by counting. + { + std::unordered_map< sal_uInt16, size_t > aIndexMap; + size_t nMaxXFCount = 0; + for( const auto& rXFIndex : aXFIndexes ) + { + if( rXFIndex != EXC_XF_NOTFOUND ) + { + size_t& rnCount = aIndexMap[ rXFIndex ]; + ++rnCount; + if( rnCount > nMaxXFCount ) + { + nRowXFIndex = rXFIndex; + nMaxXFCount = rnCount; + if (nMaxXFCount > nHalfIndexes) + { + // No other XF index can have a greater usage count, we + // don't need to loop through the remaining cells. + // Specifically for the tail of unused default + // cells/columns this makes a difference. + break; // for + } + } + } + } + } + } + + // decide whether to use the row default XF index or column default XF indexes + bool bUseColDefXFs = nRowXFIndex == EXC_XF_DEFAULTCELL; + if( !bUseColDefXFs ) + { + // count needed XF indexes for blank cells with and without row default XF index + size_t nXFCountWithRowDefXF = 0; + size_t nXFCountWithoutRowDefXF = 0; + ScfUInt16Vec::const_iterator aColIt = rColXFIndexes.begin(); + for( const auto& rXFIndex : aXFIndexes ) + { + sal_uInt16 nXFIndex = rXFIndex; + if( nXFIndex != EXC_XF_NOTFOUND ) + { + if( nXFIndex != nRowXFIndex ) + ++nXFCountWithRowDefXF; // with row default XF index + if( nXFIndex != *aColIt ) + ++nXFCountWithoutRowDefXF; // without row default XF index + } + ++aColIt; + } + + // use column XF indexes if this would cause less or equal number of BLANK records + bUseColDefXFs = nXFCountWithoutRowDefXF <= nXFCountWithRowDefXF; + } + + // *** Remove unused BLANK cell records *** ------------------------------- + + size_t maxStartAllNotFound; + if( bUseColDefXFs ) + { + size_t maxStartAllDefault = std::max( nStartAllDefault, nStartColAllDefault ); + // use column default XF indexes + // #i194#: remove cell XF indexes equal to column default XF indexes + for( size_t i = 0; i < maxStartAllDefault; ++i ) + { + if( aXFIndexes[ i ] == rColXFIndexes[ i ] ) + aXFIndexes[ i ] = EXC_XF_NOTFOUND; + } + // They can differ only up to maxNonDefault, in the rest they are the same. + for( size_t i = maxStartAllDefault; i < aXFIndexes.size(); ++i ) + aXFIndexes[ i ] = EXC_XF_NOTFOUND; + maxStartAllNotFound = maxStartAllDefault; + } + else + { + // use row default XF index + mnXFIndex = nRowXFIndex; + ::set_flag( mnFlags, EXC_ROW_USEDEFXF ); + // #98133#, #i194#, #i27407#: remove cell XF indexes equal to row default XF index + for( auto& rXFIndex : aXFIndexes ) + if( rXFIndex == nRowXFIndex ) + rXFIndex = EXC_XF_NOTFOUND; + maxStartAllNotFound = aXFIndexes.size(); + } + + // remove unused parts of BLANK/MULBLANK cell records + size_t nStartAllNotFound = findFirstAllSameUntilEnd( aXFIndexes, EXC_XF_NOTFOUND, maxStartAllNotFound ); + nPos = 0; + while( nPos < maCellList.GetSize() ) // do not cache list size, may change in the loop + { + XclExpCellBase* xCell = maCellList.GetRecord( nPos ); + xCell->RemoveUnusedBlankCells( aXFIndexes, nStartAllNotFound ); + if( xCell->IsEmpty() ) + maCellList.RemoveRecord( nPos ); + else + ++nPos; + } + // Ensure it's all EXC_XF_NOTFOUND again for next reuse. + for( size_t i = 0; i < nStartAllNotFound; ++i ) + aXFIndexes[ i ] = EXC_XF_NOTFOUND; + + // progress bar includes disabled rows; only update it in the lead thread. + if (bProgress) + GetProgressBar().Progress(); +} +sal_uInt16 XclExpRow::GetFirstUsedXclCol() const +{ + return maCellList.IsEmpty() ? 0 : maCellList.GetFirstRecord()->GetXclCol(); +} + +sal_uInt16 XclExpRow::GetFirstFreeXclCol() const +{ + return maCellList.IsEmpty() ? 0 : (maCellList.GetLastRecord()->GetLastXclCol() + 1); +} + +bool XclExpRow::IsDefaultable() const +{ + const sal_uInt16 nFlagsAlwaysMarkedAsDefault = EXC_ROW_DEFAULTFLAGS | EXC_ROW_HIDDEN | EXC_ROW_UNSYNCED; + return !::get_flag( mnFlags, static_cast< sal_uInt16 >( ~nFlagsAlwaysMarkedAsDefault ) ) && + IsEmpty(); +} + +void XclExpRow::DisableIfDefault( const XclExpDefaultRowData& rDefRowData ) +{ + mbEnabled = !IsDefaultable() || + (mnHeight != rDefRowData.mnHeight) || + (IsHidden() != rDefRowData.IsHidden()) || + (IsUnsynced() != rDefRowData.IsUnsynced()); +} + +void XclExpRow::WriteCellList( XclExpStream& rStrm ) +{ + OSL_ENSURE( mbEnabled || maCellList.IsEmpty(), "XclExpRow::WriteCellList - cells in disabled row" ); + maCellList.Save( rStrm ); +} + +void XclExpRow::Save( XclExpStream& rStrm ) +{ + if( mbEnabled ) + { + mnCurrentRow = mnXclRow; + for ( sal_uInt32 i = 0; i < mnXclRowRpt; ++i, ++mnCurrentRow ) + XclExpRecord::Save( rStrm ); + } +} + +void XclExpRow::InsertCell( XclExpCellRef xCell, size_t nPos, bool bIsMergedBase ) +{ + OSL_ENSURE( xCell, "XclExpRow::InsertCell - missing cell" ); + + /* If we have a multi-line text in a merged cell, and the resulting + row height has not been confirmed, we need to force the EXC_ROW_UNSYNCED + flag to be true to ensure Excel works correctly. */ + if( bIsMergedBase && xCell->IsMultiLineText() ) + ::set_flag( mnFlags, EXC_ROW_UNSYNCED ); + + // try to merge with previous cell, insert the new cell if not successful + XclExpCellBase* xPrevCell = maCellList.GetRecord( nPos - 1 ); + if( xPrevCell && xPrevCell->TryMerge( *xCell ) ) + xCell = xPrevCell; + else + maCellList.InsertRecord( xCell, nPos++ ); + // nPos points now to following cell + + // try to merge with following cell, remove it if successful + XclExpCellRef xNextCell = maCellList.GetRecord( nPos ); + if( xNextCell && xCell->TryMerge( *xNextCell ) ) + maCellList.RemoveRecord( nPos ); +} + +void XclExpRow::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt16 >(mnCurrentRow) + << GetFirstUsedXclCol() + << GetFirstFreeXclCol() + << mnHeight + << sal_uInt32( 0 ) + << mnFlags + << mnXFIndex; +} + +void XclExpRow::SaveXml( XclExpXmlStream& rStrm ) +{ + if( !mbEnabled ) + return; + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + bool haveFormat = ::get_flag( mnFlags, EXC_ROW_USEDEFXF ); + mnCurrentRow = mnXclRow + 1; + for ( sal_uInt32 i=0; i<mnXclRowRpt; ++i ) + { + rWorksheet->startElement( XML_row, + XML_r, OString::number(mnCurrentRow++), + // OOXTODO: XML_spans, optional + XML_s, haveFormat ? lcl_GetStyleId( rStrm, mnXFIndex ).getStr() : nullptr, + XML_customFormat, ToPsz( haveFormat ), + XML_ht, OString::number(static_cast<double>(mnHeight) / 20.0), + XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_ROW_HIDDEN ) ), + XML_customHeight, ToPsz( ::get_flag( mnFlags, EXC_ROW_UNSYNCED ) ), + XML_outlineLevel, OString::number(mnOutlineLevel), + XML_collapsed, ToPsz( ::get_flag( mnFlags, EXC_ROW_COLLAPSED ) ) + // OOXTODO: XML_thickTop, bool + // OOXTODO: XML_thickBot, bool + // OOXTODO: XML_ph, bool + ); + // OOXTODO: XML_extLst + maCellList.SaveXml( rStrm ); + rWorksheet->endElement( XML_row ); + } +} + +XclExpRowBuffer::XclExpRowBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maOutlineBfr( rRoot ), + maDimensions( rRoot ), + mnHighestOutlineLevel( 0 ) +{ +} + +void XclExpRowBuffer::AppendCell( XclExpCellRef const & xCell, bool bIsMergedBase ) +{ + OSL_ENSURE( xCell, "XclExpRowBuffer::AppendCell - missing cell" ); + GetOrCreateRow( xCell->GetXclRow(), false ).AppendCell( xCell, bIsMergedBase ); +} + +void XclExpRowBuffer::CreateRows( SCROW nFirstFreeScRow ) +{ + if( nFirstFreeScRow > 0 ) + GetOrCreateRow( ::std::max ( nFirstFreeScRow - 1, GetMaxPos().Row() ), true ); +} + +namespace { + +class RowFinalizeTask : public comphelper::ThreadTask +{ + bool mbProgress; + const ScfUInt16Vec& mrColXFIndexes; + size_t mnStartColAllDefault; + std::vector< XclExpRow * > maRows; +public: + RowFinalizeTask( const std::shared_ptr<comphelper::ThreadTaskTag> & pTag, + const ScfUInt16Vec& rColXFIndexes, + size_t nStartColAllDefault, + bool bProgress ) : + comphelper::ThreadTask( pTag ), + mbProgress( bProgress ), + mrColXFIndexes( rColXFIndexes ), + mnStartColAllDefault( nStartColAllDefault ) + {} + + void push_back( XclExpRow *pRow ) { maRows.push_back( pRow ); } + virtual void doWork() override + { + ScfUInt16Vec aXFIndexes( mrColXFIndexes.size(), EXC_XF_NOTFOUND ); + for (XclExpRow* p : maRows) + p->Finalize( mrColXFIndexes, aXFIndexes, mnStartColAllDefault, mbProgress ); + } +}; + +} + +void XclExpRowBuffer::Finalize( XclExpDefaultRowData& rDefRowData, + const ScfUInt16Vec& rColXFIndexes, + size_t nStartColAllDefault ) +{ + // *** Finalize all rows *** ---------------------------------------------- + + GetProgressBar().ActivateFinalRowsSegment(); + +#if 1 + // This is staggeringly slow, and each element operates only + // on its own data. + const size_t nRows = maRowMap.size(); + const size_t nThreads = nRows < 128 ? 1 : comphelper::ThreadPool::getPreferredConcurrency(); +#else + const size_t nThreads = 1; // globally disable multi-threading for now. +#endif + if (nThreads == 1) + { + ScfUInt16Vec aXFIndexes( rColXFIndexes.size(), EXC_XF_NOTFOUND ); + for (auto& rEntry : maRowMap) + rEntry.second->Finalize( rColXFIndexes, aXFIndexes, nStartColAllDefault, true ); + } + else + { + comphelper::ThreadPool &rPool = comphelper::ThreadPool::getSharedOptimalPool(); + std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag(); + std::vector<std::unique_ptr<RowFinalizeTask>> aTasks(nThreads); + for ( size_t i = 0; i < nThreads; i++ ) + aTasks[ i ].reset( new RowFinalizeTask( pTag, rColXFIndexes, nStartColAllDefault, i == 0 ) ); + + size_t nIdx = 0; + for ( const auto& rEntry : maRowMap ) + { + aTasks[ nIdx % nThreads ]->push_back( rEntry.second.get() ); + ++nIdx; + } + + for ( size_t i = 1; i < nThreads; i++ ) + rPool.pushTask( std::move(aTasks[ i ]) ); + + // Progress bar updates must be synchronous to avoid deadlock + aTasks[0]->doWork(); + + rPool.waitUntilDone(pTag); + } + + // *** Default row format *** --------------------------------------------- + + std::map< XclExpDefaultRowData, size_t > aDefRowMap; + + XclExpDefaultRowData aMaxDefData; + size_t nMaxDefCount = 0; + // only look for default format in existing rows, if there are more than unused + // if the row is hidden, then row xml must be created even if it not contain cells + XclExpRow* pPrev = nullptr; + std::vector< XclExpRow* > aRepeated; + for (const auto& rEntry : maRowMap) + { + const RowRef& rRow = rEntry.second; + if ( rRow->IsDefaultable() ) + { + XclExpDefaultRowData aDefData( *rRow ); + size_t& rnDefCount = aDefRowMap[ aDefData ]; + ++rnDefCount; + if( rnDefCount > nMaxDefCount ) + { + nMaxDefCount = rnDefCount; + aMaxDefData = aDefData; + } + } + if ( pPrev ) + { + if ( pPrev->IsDefaultable() ) + { + // if the previous row we processed is not + // defaultable then afaict the rows in between are + // not used ( and not repeatable ) + sal_uInt32 nRpt = rRow->GetXclRow() - pPrev->GetXclRow(); + if ( nRpt > 1 ) + aRepeated.push_back( pPrev ); + pPrev->SetXclRowRpt( nRpt ); + XclExpDefaultRowData aDefData( *pPrev ); + size_t& rnDefCount = aDefRowMap[ aDefData ]; + rnDefCount += ( pPrev->GetXclRowRpt() - 1 ); + if( rnDefCount > nMaxDefCount ) + { + nMaxDefCount = rnDefCount; + aMaxDefData = aDefData; + } + } + } + pPrev = rRow.get(); + } + // return the default row format to caller + rDefRowData = aMaxDefData; + + // now disable repeating extra (empty) rows that are equal to the default row + for (auto& rpRow : aRepeated) + { + if ( rpRow->GetXclRowRpt() > 1 + && rpRow->GetHeight() == rDefRowData.mnHeight + && rpRow->IsHidden() == rDefRowData.IsHidden() ) + { + rpRow->SetXclRowRpt( 1 ); + } + } + + // *** Disable unused ROW records, find used area *** --------------------- + + sal_uInt16 nFirstUsedXclCol = SAL_MAX_UINT16; + sal_uInt16 nFirstFreeXclCol = 0; + sal_uInt32 nFirstUsedXclRow = SAL_MAX_UINT32; + sal_uInt32 nFirstFreeXclRow = 0; + + for (const auto& rEntry : maRowMap) + { + const RowRef& rRow = rEntry.second; + // disable unused rows + rRow->DisableIfDefault( aMaxDefData ); + + // find used column range + if( !rRow->IsEmpty() ) // empty rows return (0...0) as used range + { + nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, rRow->GetFirstUsedXclCol() ); + nFirstFreeXclCol = ::std::max( nFirstFreeXclCol, rRow->GetFirstFreeXclCol() ); + } + + // find used row range + if( rRow->IsEnabled() ) + { + sal_uInt32 nXclRow = rRow->GetXclRow(); + nFirstUsedXclRow = ::std::min< sal_uInt32 >( nFirstUsedXclRow, nXclRow ); + nFirstFreeXclRow = ::std::max< sal_uInt32 >( nFirstFreeXclRow, nXclRow + 1 ); + } + } + + // adjust start position, if there are no or only empty/disabled ROW records + nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, nFirstFreeXclCol ); + nFirstUsedXclRow = ::std::min( nFirstUsedXclRow, nFirstFreeXclRow ); + + // initialize the DIMENSIONS record + maDimensions.SetDimensions( + nFirstUsedXclCol, nFirstUsedXclRow, nFirstFreeXclCol, nFirstFreeXclRow ); +} + +void XclExpRowBuffer::Save( XclExpStream& rStrm ) +{ + // DIMENSIONS record + maDimensions.Save( rStrm ); + + // save in blocks of 32 rows, each block contains first all ROWs, then all cells + size_t nSize = maRowMap.size(); + RowMap::iterator itr = maRowMap.begin(), itrEnd = maRowMap.end(); + RowMap::iterator itrBlkStart = maRowMap.begin(), itrBlkEnd = maRowMap.begin(); + sal_uInt16 nStartXclRow = (nSize == 0) ? 0 : itr->second->GetXclRow(); + + for (; itr != itrEnd; ++itr) + { + // find end of row block + itrBlkEnd = std::find_if_not(itrBlkEnd, itrEnd, + [&nStartXclRow](const RowMap::value_type& rRow) { return rRow.second->GetXclRow() - nStartXclRow < EXC_ROW_ROWBLOCKSIZE; }); + + // write the ROW records + std::for_each(itrBlkStart, itrBlkEnd, [&rStrm](const RowMap::value_type& rRow) { rRow.second->Save( rStrm ); }); + + // write the cell records + std::for_each(itrBlkStart, itrBlkEnd, [&rStrm](const RowMap::value_type& rRow) { rRow.second->WriteCellList( rStrm ); }); + + itrBlkStart = (itrBlkEnd == itrEnd) ? itrBlkEnd : itrBlkEnd++; + nStartXclRow += EXC_ROW_ROWBLOCKSIZE; + } +} + +void XclExpRowBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if (std::none_of(maRowMap.begin(), maRowMap.end(), [](const RowMap::value_type& rRow) { return rRow.second->IsEnabled(); })) + { + rStrm.GetCurrentStream()->singleElement(XML_sheetData); + return; + } + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_sheetData); + for (const auto& rEntry : maRowMap) + rEntry.second->SaveXml(rStrm); + rWorksheet->endElement( XML_sheetData ); +} + +XclExpRow& XclExpRowBuffer::GetOrCreateRow( sal_uInt32 nXclRow, bool bRowAlwaysEmpty ) +{ + // This is called rather often, so optimize for the most common case of saving row by row + // (so the requested row is often the last one in the map or belongs after the last one). + RowMap::iterator itr; + if(maRowMap.empty()) + itr = maRowMap.end(); + else + { + RowMap::reverse_iterator last = maRowMap.rbegin(); + if( last->first == nXclRow ) + return *last->second; + if( nXclRow > last->first ) + itr = maRowMap.end(); + else + itr = maRowMap.lower_bound( nXclRow ); + } + const bool bFound = itr != maRowMap.end(); + // bFoundHigher: nXclRow was identical to the previous entry, so not explicitly created earlier + // coverity[deref_iterator : FALSE] - clearly itr if only derefed if bFound which checks for valid itr + const bool bFoundHigher = bFound && itr->first != nXclRow; + if( bFound && !bFoundHigher ) + return *itr->second; + + size_t nFrom = 0; + RowRef pPrevEntry; + if( itr != maRowMap.begin() ) + { + --itr; + pPrevEntry = itr->second; + if( bFoundHigher ) + nFrom = nXclRow; + else + nFrom = itr->first + 1; + } + + const ScDocument& rDoc = GetRoot().GetDoc(); + const SCTAB nScTab = GetRoot().GetCurrScTab(); + // Do not repeatedly call RowHidden() / GetRowHeight() for same values. + bool bHidden = false; + SCROW lastSameHiddenRow = -1; + sal_uInt16 nHeight = 0; + SCROW lastSameHeightRow = -1; + // create the missing rows first + while( nFrom <= nXclRow ) + { + // only create RowMap entries if it is first row in spreadsheet, + // if it is the desired row, or for rows that differ from previous. + if( static_cast<SCROW>(nFrom) > lastSameHiddenRow ) + bHidden = rDoc.RowHidden(nFrom, nScTab, nullptr, &lastSameHiddenRow); + // Always get the actual row height even if the manual size flag is + // not set, to correctly export the heights of rows with wrapped + // texts. + if( static_cast<SCROW>(nFrom) > lastSameHeightRow ) + nHeight = rDoc.GetRowHeight(nFrom, nScTab, nullptr, &lastSameHeightRow, false); + if ( !pPrevEntry || ( nFrom == nXclRow ) || + ( maOutlineBfr.IsCollapsed() ) || + ( maOutlineBfr.GetLevel() != 0 ) || + ( bRowAlwaysEmpty && !pPrevEntry->IsEmpty() ) || + ( bHidden != pPrevEntry->IsHidden() ) || + ( nHeight != pPrevEntry->GetHeight() ) ) + { + if( maOutlineBfr.GetLevel() > mnHighestOutlineLevel ) + { + mnHighestOutlineLevel = maOutlineBfr.GetLevel(); + } + RowRef p = std::make_shared<XclExpRow>(GetRoot(), nFrom, maOutlineBfr, bRowAlwaysEmpty, bHidden, nHeight); + maRowMap.emplace(nFrom, p); + pPrevEntry = p; + } + ++nFrom; + } + itr = maRowMap.find(nXclRow); + return *itr->second; +} + +// Cell Table + +XclExpCellTable::XclExpCellTable( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maColInfoBfr( rRoot ), + maRowBfr( rRoot ), + maArrayBfr( rRoot ), + maShrfmlaBfr( rRoot ), + maTableopBfr( rRoot ), + mxDefrowheight( new XclExpDefrowheight() ), + mxGuts( new XclExpGuts( rRoot ) ), + mxNoteList( new XclExpNoteList ), + mxMergedcells( new XclExpMergedcells( rRoot ) ), + mxHyperlinkList( new XclExpHyperlinkList ), + mxDval( new XclExpDval( rRoot ) ), + mxExtLst( new XclExtLst( rRoot ) ) +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + SvNumberFormatter& rFormatter = GetFormatter(); + + // maximum sheet limits + SCCOL nMaxScCol = GetMaxPos().Col(); + SCROW nMaxScRow = GetMaxPos().Row(); + + // find used area (non-empty cells) + SCCOL nLastUsedScCol; + SCROW nLastUsedScRow; + rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow ); + + if(nLastUsedScCol > nMaxScCol) + nLastUsedScCol = nMaxScCol; + + // check extra blank rows to avoid of losing their not default settings (workaround for tdf#41425) + nLastUsedScRow += 1000; + + if(nLastUsedScRow > nMaxScRow) + nLastUsedScRow = nMaxScRow; + + ScRange aUsedRange( 0, 0, nScTab, nLastUsedScCol, nLastUsedScRow, nScTab ); + GetAddressConverter().ValidateRange( aUsedRange, true ); + nLastUsedScRow = aUsedRange.aEnd.Row(); + + // first row without any set attributes (height/hidden/...) + SCROW nFirstUnflaggedScRow = rDoc.GetLastFlaggedRow( nScTab ) + 1; + + // find range of outlines + SCROW nFirstUngroupedScRow = 0; + if( const ScOutlineTable* pOutlineTable = rDoc.GetOutlineTable( nScTab ) ) + { + SCCOLROW nScStartPos, nScEndPos; + const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray(); + rRowArray.GetRange( nScStartPos, nScEndPos ); + // +1 because open/close button is in next row in Excel, +1 for "end->first unused" + nFirstUngroupedScRow = static_cast< SCROW >( nScEndPos + 2 ); + } + + // column settings + /* #i30411# Files saved with SO7/OOo1.x with nonstandard default column + formatting cause big Excel files, because all rows from row 1 to row + 32000 are exported. Now, if the used area goes exactly to row 32000, + use this row as default and ignore all rows >32000. + #i59220# Tolerance of +-128 rows for inserted/removed rows. */ + if( (31871 <= nLastUsedScRow) && (nLastUsedScRow <= 32127) && (nFirstUnflaggedScRow < nLastUsedScRow) && (nFirstUngroupedScRow <= nLastUsedScRow) ) + nMaxScRow = nLastUsedScRow; + maColInfoBfr.Initialize( nMaxScRow ); + + // range for cell iterator + SCCOL nLastIterScCol = nMaxScCol; + SCROW nLastIterScRow = ulimit_cast< SCROW >( nLastUsedScRow, nMaxScRow ); + ScUsedAreaIterator aIt( rDoc, nScTab, 0, 0, nLastIterScCol, nLastIterScRow ); + + // activate the correct segment and sub segment at the progress bar + GetProgressBar().ActivateCreateRowsSegment(); + + for( bool bIt = aIt.GetNext(); bIt; bIt = aIt.GetNext() ) + { + SCCOL nScCol = aIt.GetStartCol(); + SCROW nScRow = aIt.GetRow(); + SCCOL nLastScCol = aIt.GetEndCol(); + ScAddress aScPos( nScCol, nScRow, nScTab ); + + XclAddress aXclPos( static_cast< sal_uInt16 >( nScCol ), static_cast< sal_uInt32 >( nScRow ) ); + sal_uInt16 nLastXclCol = static_cast< sal_uInt16 >( nLastScCol ); + + const ScRefCellValue& rScCell = aIt.GetCell(); + XclExpCellRef xCell; + + const ScPatternAttr* pPattern = aIt.GetPattern(); + + // handle overlapped merged cells before creating the cell record + sal_uInt32 nMergeBaseXFId = EXC_XFID_NOTFOUND; + bool bIsMergedBase = false; + if( pPattern ) + { + const SfxItemSet& rItemSet = pPattern->GetItemSet(); + // base cell in a merged range + const ScMergeAttr& rMergeItem = rItemSet.Get( ATTR_MERGE ); + bIsMergedBase = rMergeItem.IsMerged(); + /* overlapped cell in a merged range; in Excel all merged cells + must contain same XF index, for correct border */ + const ScMergeFlagAttr& rMergeFlagItem = rItemSet.Get( ATTR_MERGE_FLAG ); + if( rMergeFlagItem.IsOverlapped() ) + nMergeBaseXFId = mxMergedcells->GetBaseXFId( aScPos ); + } + + OUString aAddNoteText; // additional text to be appended to a note + + switch (rScCell.meType) + { + case CELLTYPE_VALUE: + { + double fValue = rScCell.mfValue; + + if (pPattern) + { + OUString aUrl = pPattern->GetItemSet().Get(ATTR_HYPERLINK).GetValue(); + if (!aUrl.isEmpty()) + { + rtl::Reference<XclExpHyperlink> aLink = + new XclExpHyperlink(GetRoot(), SvxURLField(aUrl, aUrl), aScPos); + mxHyperlinkList->AppendRecord(aLink); + } + } + + // try to create a Boolean cell + if( pPattern && ((fValue == 0.0) || (fValue == 1.0)) ) + { + sal_uInt32 nScNumFmt = pPattern->GetItemSet().Get( ATTR_VALUE_FORMAT ).GetValue(); + if( rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL ) + xCell = new XclExpBooleanCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue != 0.0 ); + } + + // try to create an RK value (compressed floating-point number) + sal_Int32 nRkValue; + if( !xCell && XclTools::GetRKFromDouble( nRkValue, fValue ) ) + xCell = new XclExpRkCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, nRkValue ); + + // else: simple floating-point number cell + if( !xCell ) + xCell = new XclExpNumberCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue ); + } + break; + + case CELLTYPE_STRING: + { + xCell = new XclExpLabelCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.mpString->getString()); + } + break; + + case CELLTYPE_EDIT: + { + XclExpHyperlinkHelper aLinkHelper( GetRoot(), aScPos ); + xCell = new XclExpLabelCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.mpEditText, aLinkHelper); + + // add a single created HLINK record to the record list + if( aLinkHelper.HasLinkRecord() ) + mxHyperlinkList->AppendRecord( aLinkHelper.GetLinkRecord() ); + // add list of multiple URLs to the additional cell note text + if( aLinkHelper.HasMultipleUrls() ) + aAddNoteText = ScGlobal::addToken( aAddNoteText, aLinkHelper.GetUrlList(), '\n', 2 ); + } + break; + + case CELLTYPE_FORMULA: + { + if (pPattern) + { + OUString aUrl = pPattern->GetItemSet().Get(ATTR_HYPERLINK).GetValue(); + if (!aUrl.isEmpty()) + { + rtl::Reference<XclExpHyperlink> aLink = + new XclExpHyperlink(GetRoot(), SvxURLField(aUrl, aUrl), aScPos); + mxHyperlinkList->AppendRecord(aLink); + } + } + + xCell = new XclExpFormulaCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, + *rScCell.mpFormula, maArrayBfr, maShrfmlaBfr, maTableopBfr); + } + break; + + default: + OSL_FAIL( "XclExpCellTable::XclExpCellTable - unknown cell type" ); + [[fallthrough]]; + case CELLTYPE_NONE: + { + xCell = new XclExpBlankCell( + GetRoot(), aXclPos, nLastXclCol, pPattern, nMergeBaseXFId ); + } + break; + } + + assert(xCell && "can only reach here with xCell set"); + + // insert the cell into the current row + maRowBfr.AppendCell( xCell, bIsMergedBase ); + + if ( !aAddNoteText.isEmpty() ) + mxNoteList->AppendNewRecord( new XclExpNote( GetRoot(), aScPos, nullptr, aAddNoteText ) ); + + // other sheet contents + if( pPattern ) + { + const SfxItemSet& rItemSet = pPattern->GetItemSet(); + + // base cell in a merged range + if( bIsMergedBase ) + { + const ScMergeAttr& rMergeItem = rItemSet.Get( ATTR_MERGE ); + ScRange aScRange( aScPos ); + aScRange.aEnd.IncCol( rMergeItem.GetColMerge() - 1 ); + aScRange.aEnd.IncRow( rMergeItem.GetRowMerge() - 1 ); + sal_uInt32 nXFId = xCell->GetFirstXFId(); + // blank cells merged vertically may occur repeatedly + OSL_ENSURE( (aScRange.aStart.Col() == aScRange.aEnd.Col()) || (nScCol == nLastScCol), + "XclExpCellTable::XclExpCellTable - invalid repeated blank merged cell" ); + for( SCCOL nIndex = nScCol; nIndex <= nLastScCol; ++nIndex ) + { + mxMergedcells->AppendRange( aScRange, nXFId ); + aScRange.aStart.IncCol(); + aScRange.aEnd.IncCol(); + } + } + + // data validation + if( ScfTools::CheckItem( rItemSet, ATTR_VALIDDATA, false ) ) + { + sal_uLong nScHandle = rItemSet.Get( ATTR_VALIDDATA ).GetValue(); + ScRange aScRange( aScPos ); + aScRange.aEnd.SetCol( nLastScCol ); + mxDval->InsertCellRange( aScRange, nScHandle ); + } + } + } + + // create missing row settings for rows anyhow flagged or with outlines + maRowBfr.CreateRows( ::std::max( nFirstUnflaggedScRow, nFirstUngroupedScRow ) ); +} + +void XclExpCellTable::Finalize(bool bXLS) +{ + // Finalize multiple operations. + maTableopBfr.Finalize(); + + /* Finalize column buffer. This calculates column default XF indexes from + the XF identifiers and fills a vector with these XF indexes. */ + ScfUInt16Vec aColXFIndexes; + maColInfoBfr.Finalize( aColXFIndexes, bXLS ); + + // Usually many indexes towards the end will be EXC_XF_DEFAULTCELL, find + // the index that starts all EXC_XF_DEFAULTCELL until the end. + size_t nStartColAllDefault = findFirstAllSameUntilEnd( aColXFIndexes, EXC_XF_DEFAULTCELL ); + + /* Finalize row buffer. This calculates all cell XF indexes from the XF + identifiers. Then the XF index vector aColXFIndexes (filled above) is + used to calculate the row default formats. With this, all unneeded blank + cell records (equal to row default or column default) will be removed. + The function returns the (most used) default row format in aDefRowData. */ + XclExpDefaultRowData aDefRowData; + maRowBfr.Finalize( aDefRowData, aColXFIndexes, nStartColAllDefault ); + + // Initialize the DEFROWHEIGHT record. + mxDefrowheight->SetDefaultData( aDefRowData ); +} + +XclExpRecordRef XclExpCellTable::CreateRecord( sal_uInt16 nRecId ) const +{ + XclExpRecordRef xRec; + switch( nRecId ) + { + case EXC_ID3_DIMENSIONS: xRec = new XclExpDelegatingRecord( &const_cast<XclExpRowBuffer*>(&maRowBfr)->GetDimensions() ); break; + case EXC_ID2_DEFROWHEIGHT: xRec = mxDefrowheight; break; + case EXC_ID_GUTS: xRec = mxGuts; break; + case EXC_ID_NOTE: xRec = mxNoteList; break; + case EXC_ID_MERGEDCELLS: xRec = mxMergedcells; break; + case EXC_ID_HLINK: xRec = mxHyperlinkList; break; + case EXC_ID_DVAL: xRec = mxDval; break; + case EXC_ID_EXTLST: xRec = mxExtLst; break; + default: OSL_FAIL( "XclExpCellTable::CreateRecord - unknown record id" ); + } + return xRec; +} + +void XclExpCellTable::Save( XclExpStream& rStrm ) +{ + // DEFCOLWIDTH and COLINFOs + maColInfoBfr.Save( rStrm ); + // ROWs and cell records + maRowBfr.Save( rStrm ); +} + +void XclExpCellTable::SaveXml( XclExpXmlStream& rStrm ) +{ + // DEFAULT row height + XclExpDefaultRowData& rDefData = mxDefrowheight->GetDefaultData(); + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_sheetFormatPr, + // OOXTODO: XML_baseColWidth + XML_defaultColWidth, OString::number(maColInfoBfr.GetDefColWidth()), + // OOXTODO: XML_customHeight + // OOXTODO: XML_thickTop + // OOXTODO: XML_thickBottom + XML_defaultRowHeight, OString::number(static_cast<double> (rDefData.mnHeight) / 20.0), + XML_zeroHeight, ToPsz( rDefData.IsHidden() ), + XML_outlineLevelRow, OString::number(maRowBfr.GetHighestOutlineLevel()), + XML_outlineLevelCol, OString::number(maColInfoBfr.GetHighestOutlineLevel()) ); + rWorksheet->endElement( XML_sheetFormatPr ); + + maColInfoBfr.SaveXml( rStrm ); + maRowBfr.SaveXml( rStrm ); + mxExtLst->SaveXml( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeview.cxx b/sc/source/filter/excel/xeview.cxx new file mode 100644 index 000000000..d94a94407 --- /dev/null +++ b/sc/source/filter/excel/xeview.cxx @@ -0,0 +1,537 @@ +/* -*- 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 <xeview.hxx> +#include <document.hxx> +#include <scextopt.hxx> +#include <viewopti.hxx> +#include <xelink.hxx> +#include <xestyle.hxx> +#include <xehelper.hxx> +#include <xltools.hxx> +#include <oox/token/tokens.hxx> +#include <oox/export/utils.hxx> + +using namespace ::oox; + +// Workbook view settings records ============================================= + +XclExpWindow1::XclExpWindow1( const XclExpRoot& rRoot ) + : XclExpRecord(EXC_ID_WINDOW1, 18) + , mnFlags(0) + , mnTabBarSize(600) +{ + const ScViewOptions& rViewOpt = rRoot.GetDoc().GetViewOptions(); + ::set_flag( mnFlags, EXC_WIN1_HOR_SCROLLBAR, rViewOpt.GetOption( VOPT_HSCROLL ) ); + ::set_flag( mnFlags, EXC_WIN1_VER_SCROLLBAR, rViewOpt.GetOption( VOPT_VSCROLL ) ); + ::set_flag( mnFlags, EXC_WIN1_TABBAR, rViewOpt.GetOption( VOPT_TABCONTROLS ) ); + + double fTabBarWidth = rRoot.GetExtDocOptions().GetDocSettings().mfTabBarWidth; + if( (0.0 <= fTabBarWidth) && (fTabBarWidth <= 1.0) ) + mnTabBarSize = static_cast< sal_uInt16 >( fTabBarWidth * 1000.0 + 0.5 ); +} + +void XclExpWindow1::SaveXml( XclExpXmlStream& rStrm ) +{ + const XclExpTabInfo& rTabInfo = rStrm.GetRoot().GetTabInfo(); + + rStrm.GetCurrentStream()->singleElement( XML_workbookView, + // OOXTODO: XML_visibility, // ST_visibility + // OOXTODO: XML_minimized, // bool + XML_showHorizontalScroll, ToPsz( ::get_flag( mnFlags, EXC_WIN1_HOR_SCROLLBAR ) ), + XML_showVerticalScroll, ToPsz( ::get_flag( mnFlags, EXC_WIN1_VER_SCROLLBAR ) ), + XML_showSheetTabs, ToPsz( ::get_flag( mnFlags, EXC_WIN1_TABBAR ) ), + XML_xWindow, "0", + XML_yWindow, "0", + XML_windowWidth, OString::number(0x4000), + XML_windowHeight, OString::number(0x2000), + XML_tabRatio, OString::number(mnTabBarSize), + XML_firstSheet, OString::number(rTabInfo.GetFirstVisXclTab()), + XML_activeTab, OString::number(rTabInfo.GetDisplayedXclTab()) + // OOXTODO: XML_autoFilterDateGrouping, // bool; AUTOFILTER12? 87Eh + ); +} + +void XclExpWindow1::WriteBody( XclExpStream& rStrm ) +{ + const XclExpTabInfo& rTabInfo = rStrm.GetRoot().GetTabInfo(); + + rStrm << sal_uInt16( 0 ) // X position of the window + << sal_uInt16( 0 ) // Y position of the window + << sal_uInt16( 0x4000 ) // width of the window + << sal_uInt16( 0x2000 ) // height of the window + << mnFlags + << rTabInfo.GetDisplayedXclTab() + << rTabInfo.GetFirstVisXclTab() + << rTabInfo.GetXclSelectedCount() + << mnTabBarSize; +} + +// Sheet view settings records ================================================ + +XclExpWindow2::XclExpWindow2( const XclExpRoot& rRoot, + const XclTabViewData& rData, sal_uInt32 nGridColorId ) : + XclExpRecord( EXC_ID_WINDOW2, (rRoot.GetBiff() == EXC_BIFF8) ? 18 : 10 ), + maGridColor( rData.maGridColor ), + mnGridColorId( nGridColorId ), + mnFlags( 0 ), + maFirstXclPos( rData.maFirstXclPos ), + mnNormalZoom( rData.mnNormalZoom ), + mnPageZoom( rData.mnPageZoom ) +{ + ::set_flag( mnFlags, EXC_WIN2_SHOWFORMULAS, rData.mbShowFormulas ); + ::set_flag( mnFlags, EXC_WIN2_SHOWGRID, rData.mbShowGrid ); + ::set_flag( mnFlags, EXC_WIN2_SHOWHEADINGS, rData.mbShowHeadings ); + ::set_flag( mnFlags, EXC_WIN2_FROZEN, rData.mbFrozenPanes ); + ::set_flag( mnFlags, EXC_WIN2_SHOWZEROS, rData.mbShowZeros ); + ::set_flag( mnFlags, EXC_WIN2_DEFGRIDCOLOR, rData.mbDefGridColor ); + ::set_flag( mnFlags, EXC_WIN2_MIRRORED, rData.mbMirrored ); + ::set_flag( mnFlags, EXC_WIN2_SHOWOUTLINE, rData.mbShowOutline ); + ::set_flag( mnFlags, EXC_WIN2_FROZENNOSPLIT, rData.mbFrozenPanes ); + ::set_flag( mnFlags, EXC_WIN2_SELECTED, rData.mbSelected ); + ::set_flag( mnFlags, EXC_WIN2_DISPLAYED, rData.mbDisplayed ); + ::set_flag( mnFlags, EXC_WIN2_PAGEBREAKMODE, rData.mbPageMode ); +} + +void XclExpWindow2::WriteBody( XclExpStream& rStrm ) +{ + const XclExpRoot& rRoot = rStrm.GetRoot(); + + rStrm << mnFlags + << maFirstXclPos; + + switch( rRoot.GetBiff() ) + { + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + rStrm << maGridColor; + break; + case EXC_BIFF8: + rStrm << rRoot.GetPalette().GetColorIndex( mnGridColorId ) + << sal_uInt16( 0 ) + << mnPageZoom + << mnNormalZoom + << sal_uInt32( 0 ); + break; + default: DBG_ERROR_BIFF(); + } +} + +XclExpScl::XclExpScl( sal_uInt16 nZoom ) : + XclExpRecord( EXC_ID_SCL, 4 ), + mnNum( nZoom ), + mnDenom( 100 ) +{ + Shorten( 2 ); + Shorten( 5 ); +} + +void XclExpScl::Shorten( sal_uInt16 nFactor ) +{ + while( (mnNum % nFactor == 0) && (mnDenom % nFactor == 0) ) + { + mnNum = mnNum / nFactor; + mnDenom = mnDenom / nFactor; + } +} + +void XclExpScl::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF4 ); + rStrm << mnNum << mnDenom; +} + +XclExpPane::XclExpPane( const XclTabViewData& rData ) : + XclExpRecord( EXC_ID_PANE, 10 ), + mnSplitX( rData.mnSplitX ), + mnSplitY( rData.mnSplitY ), + maSecondXclPos( rData.maSecondXclPos ), + mnActivePane( rData.mnActivePane ), + mbFrozenPanes( rData.mbFrozenPanes ) +{ + OSL_ENSURE( rData.IsSplit(), "XclExpPane::XclExpPane - no PANE record for unsplit view" ); +} + +static const char* lcl_GetActivePane( sal_uInt8 nActivePane ) +{ + switch( nActivePane ) + { + case EXC_PANE_TOPLEFT: return "topLeft"; + case EXC_PANE_TOPRIGHT: return "topRight"; + case EXC_PANE_BOTTOMLEFT: return "bottomLeft"; + case EXC_PANE_BOTTOMRIGHT: return "bottomRight"; + } + return "**error: lcl_GetActivePane"; +} + +void XclExpPane::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElement( XML_pane, + XML_xSplit, OString::number(mnSplitX), + XML_ySplit, OString::number(mnSplitY), + XML_topLeftCell, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), maSecondXclPos ).getStr(), + XML_activePane, lcl_GetActivePane( mnActivePane ), + XML_state, mbFrozenPanes ? "frozen" : "split" ); +} + +void XclExpPane::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnSplitX + << static_cast<sal_uInt16>( mnSplitY ) + << maSecondXclPos + << mnActivePane; + if( rStrm.GetRoot().GetBiff() >= EXC_BIFF5 ) + rStrm << sal_uInt8( 0 ); +} + +XclExpSelection::XclExpSelection( const XclTabViewData& rData, sal_uInt8 nPane ) : + XclExpRecord( EXC_ID_SELECTION, 15 ), + mnPane( nPane ) +{ + if( const XclSelectionData* pSelData = rData.GetSelectionData( nPane ) ) + maSelData = *pSelData; + + // find the cursor position in the selection list (or add it) + XclRangeList& rXclSel = maSelData.maXclSelection; + auto aIt = std::find_if(rXclSel.begin(), rXclSel.end(), + [this](const XclRange& rRange) { return rRange.Contains(maSelData.maXclCursor); }); + if (aIt != rXclSel.end()) + { + maSelData.mnCursorIdx = static_cast< sal_uInt16 >( std::distance(rXclSel.begin(), aIt) ); + } + else + { + /* Cursor cell not found in list? (e.g. inactive pane, or removed in + ConvertRangeList(), because Calc cursor on invalid pos) + -> insert the valid Excel cursor. */ + maSelData.mnCursorIdx = static_cast< sal_uInt16 >( rXclSel.size() ); + rXclSel.push_back( XclRange( maSelData.maXclCursor ) ); + } +} + +void XclExpSelection::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElement( XML_selection, + XML_pane, lcl_GetActivePane( mnPane ), + XML_activeCell, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), maSelData.maXclCursor ).getStr(), + XML_activeCellId, OString::number(maSelData.mnCursorIdx), + XML_sqref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maSelData.maXclSelection) ); +} + +void XclExpSelection::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnPane // pane for this selection + << maSelData.maXclCursor // cell cursor + << maSelData.mnCursorIdx; // index to range containing cursor + maSelData.maXclSelection.Write( rStrm, false ); +} + +XclExpTabBgColor::XclExpTabBgColor( const XclTabViewData& rTabViewData ) : + XclExpRecord( EXC_ID_SHEETEXT, 18 ), + mrTabViewData( rTabViewData ) +{ +} +//TODO Fix savexml... +/*void XclExpTabBgColor::SaveXml( XclExpXmlStream& rStrm ) +{ +}*/ + +void XclExpTabBgColor::WriteBody( XclExpStream& rStrm ) +{ + if ( mrTabViewData.IsDefaultTabBgColor() ) + return; + sal_uInt16 const rt = 0x0862; //rt + sal_uInt16 const grbitFrt = 0x0000; //grbit must be set to 0 + sal_uInt32 unused = 0x00000000; //Use twice... + sal_uInt32 const cb = 0x00000014; // Record Size, may be larger in future... + sal_uInt16 const reserved = 0x0000; //trailing bits are 0 + sal_uInt16 TabBgColorIndex; + XclExpPalette& rPal = rStrm.GetRoot().GetPalette(); + TabBgColorIndex = rPal.GetColorIndex(mrTabViewData.mnTabBgColorId); + if (TabBgColorIndex < 8 || TabBgColorIndex > 63 ) // only numbers 8 - 63 are valid numbers + TabBgColorIndex = 127; //Excel specs: 127 makes excel ignore tab color information. + rStrm << rt << grbitFrt << unused << unused << cb << TabBgColorIndex << reserved; +} + +// Sheet view settings ======================================================== + +namespace { + +/** Converts a Calc zoom factor into an Excel zoom factor. Returns 0 for a default zoom value. */ +sal_uInt16 lclGetXclZoom( tools::Long nScZoom, sal_uInt16 nDefXclZoom ) +{ + sal_uInt16 nXclZoom = limit_cast< sal_uInt16 >( nScZoom, EXC_ZOOM_MIN, EXC_ZOOM_MAX ); + return (nXclZoom == nDefXclZoom) ? 0 : nXclZoom; +} + +} // namespace + +XclExpTabViewSettings::XclExpTabViewSettings( const XclExpRoot& rRoot, SCTAB nScTab ) : + XclExpRoot( rRoot ), + mnGridColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ) ), + mbHasTabSettings(false) +{ + // *** sheet flags *** + + const XclExpTabInfo& rTabInfo = GetTabInfo(); + maData.mbSelected = rTabInfo.IsSelectedTab( nScTab ); + maData.mbDisplayed = rTabInfo.IsDisplayedTab( nScTab ); + maData.mbMirrored = rTabInfo.IsMirroredTab( nScTab ); + + const ScViewOptions& rViewOpt = GetDoc().GetViewOptions(); + maData.mbShowFormulas = rViewOpt.GetOption( VOPT_FORMULAS ); + maData.mbShowHeadings = rViewOpt.GetOption( VOPT_HEADER ); + maData.mbShowZeros = rViewOpt.GetOption( VOPT_NULLVALS ); + maData.mbShowOutline = rViewOpt.GetOption( VOPT_OUTLINER ); + + // *** sheet options: cursor, selection, splits, grid color, zoom *** + + if( const ScExtTabSettings* pTabSett = GetExtDocOptions().GetTabSettings( nScTab ) ) + { + mbHasTabSettings = true; + const ScExtTabSettings& rTabSett = *pTabSett; + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + + // first visible cell in top-left pane + if( (rTabSett.maFirstVis.Col() >= 0) && (rTabSett.maFirstVis.Row() >= 0) ) + maData.maFirstXclPos = rAddrConv.CreateValidAddress( rTabSett.maFirstVis, false ); + + // first visible cell in additional pane(s) + if( (rTabSett.maSecondVis.Col() >= 0) && (rTabSett.maSecondVis.Row() >= 0) ) + maData.maSecondXclPos = rAddrConv.CreateValidAddress( rTabSett.maSecondVis, false ); + + // active pane + switch( rTabSett.meActivePane ) + { + case SCEXT_PANE_TOPLEFT: maData.mnActivePane = EXC_PANE_TOPLEFT; break; + case SCEXT_PANE_TOPRIGHT: maData.mnActivePane = EXC_PANE_TOPRIGHT; break; + case SCEXT_PANE_BOTTOMLEFT: maData.mnActivePane = EXC_PANE_BOTTOMLEFT; break; + case SCEXT_PANE_BOTTOMRIGHT: maData.mnActivePane = EXC_PANE_BOTTOMRIGHT; break; + } + + // freeze/split position + maData.mbFrozenPanes = rTabSett.mbFrozenPanes; + if( maData.mbFrozenPanes ) + { + /* Frozen panes: handle split position as row/column positions. + #i35812# Excel uses number of visible rows/columns, Calc uses position of freeze. */ + SCCOL nFreezeScCol = rTabSett.maFreezePos.Col(); + if( (0 < nFreezeScCol) && (nFreezeScCol <= GetXclMaxPos().Col()) ) + maData.mnSplitX = static_cast< sal_uInt16 >( nFreezeScCol ) - maData.maFirstXclPos.mnCol; + SCROW nFreezeScRow = rTabSett.maFreezePos.Row(); + if( (0 < nFreezeScRow) && (nFreezeScRow <= GetXclMaxPos().Row()) ) + maData.mnSplitY = static_cast< sal_uInt32 >( nFreezeScRow ) - maData.maFirstXclPos.mnRow; + // if both splits are left out (address overflow), remove the frozen flag + maData.mbFrozenPanes = maData.IsSplit(); + + // #i20671# frozen panes: mostright/mostbottom pane is active regardless of cursor position + if( maData.HasPane( EXC_PANE_BOTTOMRIGHT ) ) + maData.mnActivePane = EXC_PANE_BOTTOMRIGHT; + else if( maData.HasPane( EXC_PANE_TOPRIGHT ) ) + maData.mnActivePane = EXC_PANE_TOPRIGHT; + else if( maData.HasPane( EXC_PANE_BOTTOMLEFT ) ) + maData.mnActivePane = EXC_PANE_BOTTOMLEFT; + } + else + { + // split window: position is in twips + maData.mnSplitX = static_cast<sal_uInt16>(rTabSett.maSplitPos.X()); + maData.mnSplitY = static_cast<sal_uInt32>(rTabSett.maSplitPos.Y()); + } + + // selection + CreateSelectionData( EXC_PANE_TOPLEFT, rTabSett.maCursor, rTabSett.maSelection ); + CreateSelectionData( EXC_PANE_TOPRIGHT, rTabSett.maCursor, rTabSett.maSelection ); + CreateSelectionData( EXC_PANE_BOTTOMLEFT, rTabSett.maCursor, rTabSett.maSelection ); + CreateSelectionData( EXC_PANE_BOTTOMRIGHT, rTabSett.maCursor, rTabSett.maSelection ); + + // grid color + const Color& rGridColor = rTabSett.maGridColor; + maData.mbDefGridColor = rGridColor == COL_AUTO; + if( !maData.mbDefGridColor ) + { + if( GetBiff() == EXC_BIFF8 ) + mnGridColorId = GetPalette().InsertColor( rGridColor, EXC_COLOR_GRID ); + else + maData.maGridColor = rGridColor; + } + maData.mbShowGrid = rTabSett.mbShowGrid; + + // view mode and zoom + maData.mbPageMode = (GetBiff() == EXC_BIFF8) && rTabSett.mbPageMode; + maData.mnNormalZoom = lclGetXclZoom( rTabSett.mnNormalZoom, EXC_WIN2_NORMALZOOM_DEF ); + maData.mnPageZoom = lclGetXclZoom( rTabSett.mnPageZoom, EXC_WIN2_PAGEZOOM_DEF ); + maData.mnCurrentZoom = maData.mbPageMode ? maData.mnPageZoom : maData.mnNormalZoom; + } + + // Tab Bg Color + if ( GetBiff() == EXC_BIFF8 && !GetDoc().IsDefaultTabBgColor(nScTab) ) + { + XclExpPalette& rPal = GetPalette(); + maData.maTabBgColor = GetDoc().GetTabBgColor(nScTab); + maData.mnTabBgColorId = rPal.InsertColor(maData.maTabBgColor, EXC_COLOR_TABBG, EXC_COLOR_NOTABBG ); + } +} + +void XclExpTabViewSettings::Save( XclExpStream& rStrm ) +{ + WriteWindow2( rStrm ); + WriteScl( rStrm ); + WritePane( rStrm ); + WriteSelection( rStrm, EXC_PANE_TOPLEFT ); + WriteSelection( rStrm, EXC_PANE_TOPRIGHT ); + WriteSelection( rStrm, EXC_PANE_BOTTOMLEFT ); + WriteSelection( rStrm, EXC_PANE_BOTTOMRIGHT ); + WriteTabBgColor( rStrm ); +} + +static void lcl_WriteSelection( XclExpXmlStream& rStrm, const XclTabViewData& rData, sal_uInt8 nPane ) +{ + if( rData.HasPane( nPane ) ) + XclExpSelection( rData, nPane ).SaveXml( rStrm ); +} + +static OString lcl_GetZoom( sal_uInt16 nZoom ) +{ + if( nZoom ) + return OString::number( nZoom ); + return "100"; +} + +void XclExpTabViewSettings::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_sheetViews); + + // handle missing viewdata at embedded XLSX OLE objects + if( !mbHasTabSettings && maData.mbSelected ) + { + SCCOL nPosLeft = rStrm.GetRoot().GetDoc().GetPosLeft(); + SCROW nPosTop = rStrm.GetRoot().GetDoc().GetPosTop(); + if (nPosLeft > 0 || nPosTop > 0) + { + ScAddress aLeftTop(nPosLeft, nPosTop, 0); + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + maData.maFirstXclPos = rAddrConv.CreateValidAddress( aLeftTop, false ); + } + } + + rWorksheet->startElement( XML_sheetView, + // OOXTODO: XML_windowProtection, + XML_showFormulas, ToPsz( maData.mbShowFormulas ), + XML_showGridLines, ToPsz( maData.mbShowGrid ), + XML_showRowColHeaders, ToPsz( maData.mbShowHeadings ), + XML_showZeros, ToPsz( maData.mbShowZeros ), + XML_rightToLeft, ToPsz( maData.mbMirrored ), + XML_tabSelected, ToPsz( maData.mbSelected ), + // OOXTODO: XML_showRuler, + XML_showOutlineSymbols, ToPsz( maData.mbShowOutline ), + XML_defaultGridColor, mnGridColorId == XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ) ? "true" : "false", + // OOXTODO: XML_showWhiteSpace, + XML_view, maData.mbPageMode ? "pageBreakPreview" : "normal", // OOXTODO: pageLayout + XML_topLeftCell, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), maData.maFirstXclPos ).getStr(), + XML_colorId, OString::number(rStrm.GetRoot().GetPalette().GetColorIndex(mnGridColorId)), + XML_zoomScale, lcl_GetZoom(maData.mnCurrentZoom), + XML_zoomScaleNormal, lcl_GetZoom(maData.mnNormalZoom), + // OOXTODO: XML_zoomScaleSheetLayoutView, + XML_zoomScalePageLayoutView, lcl_GetZoom(maData.mnPageZoom), + XML_workbookViewId, "0" // OOXTODO? 0-based index of document(xl/workbook.xml)/workbook/bookviews/workbookView + // should always be 0, as we only generate 1 such element. + ); + if( maData.IsSplit() ) + { + XclExpPane aPane( maData ); + aPane.SaveXml( rStrm ); + } + lcl_WriteSelection( rStrm, maData, EXC_PANE_TOPLEFT ); + lcl_WriteSelection( rStrm, maData, EXC_PANE_TOPRIGHT ); + lcl_WriteSelection( rStrm, maData, EXC_PANE_BOTTOMLEFT ); + lcl_WriteSelection( rStrm, maData, EXC_PANE_BOTTOMRIGHT ); + rWorksheet->endElement( XML_sheetView ); + // OOXTODO: XML_extLst + rWorksheet->endElement( XML_sheetViews ); +} + +// private -------------------------------------------------------------------- + +void XclExpTabViewSettings::CreateSelectionData( sal_uInt8 nPane, + const ScAddress& rCursor, const ScRangeList& rSelection ) +{ + if( !maData.HasPane( nPane ) ) + return; + + XclSelectionData& rSelData = maData.CreateSelectionData( nPane ); + + // first step: use top-left visible cell as cursor + rSelData.maXclCursor.mnCol = ((nPane == EXC_PANE_TOPLEFT) || (nPane == EXC_PANE_BOTTOMLEFT)) ? + maData.maFirstXclPos.mnCol : maData.maSecondXclPos.mnCol; + rSelData.maXclCursor.mnRow = ((nPane == EXC_PANE_TOPLEFT) || (nPane == EXC_PANE_TOPRIGHT)) ? + maData.maFirstXclPos.mnRow : maData.maSecondXclPos.mnRow; + + // second step, active pane: create actual selection data with current cursor position + if( nPane == maData.mnActivePane ) + { + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + // cursor position (keep top-left pane position from above, if rCursor is invalid) + if( (rCursor.Col() >= 0) && (rCursor.Row() >= 0) ) + rSelData.maXclCursor = rAddrConv.CreateValidAddress( rCursor, false ); + // selection + rAddrConv.ConvertRangeList( rSelData.maXclSelection, rSelection, false ); + } +} + +void XclExpTabViewSettings::WriteWindow2( XclExpStream& rStrm ) const +{ +// #i43553# GCC 3.3 parse error +// XclExpWindow2( GetRoot(), maData, mnGridColorId ).Save( rStrm ); + XclExpWindow2 aWindow2( GetRoot(), maData, mnGridColorId ); + aWindow2.Save( rStrm ); +} + +void XclExpTabViewSettings::WriteScl( XclExpStream& rStrm ) const +{ + if( maData.mnCurrentZoom != 0 ) + XclExpScl( maData.mnCurrentZoom ).Save( rStrm ); +} + +void XclExpTabViewSettings::WritePane( XclExpStream& rStrm ) const +{ + if( maData.IsSplit() ) +// #i43553# GCC 3.3 parse error +// XclExpPane( GetRoot(), maData ).Save( rStrm ); + { + XclExpPane aPane( maData ); + aPane.Save( rStrm ); + } +} + +void XclExpTabViewSettings::WriteSelection( XclExpStream& rStrm, sal_uInt8 nPane ) const +{ + if( maData.HasPane( nPane ) ) + XclExpSelection( maData, nPane ).Save( rStrm ); +} + +void XclExpTabViewSettings::WriteTabBgColor( XclExpStream& rStrm ) const +{ + if ( !maData.IsDefaultTabBgColor() ) + XclExpTabBgColor( maData ).Save( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xichart.cxx b/sc/source/filter/excel/xichart.cxx new file mode 100644 index 000000000..dc14076b7 --- /dev/null +++ b/sc/source/filter/excel/xichart.cxx @@ -0,0 +1,4415 @@ +/* -*- 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 <xichart.hxx> + +#include <algorithm> +#include <memory> +#include <utility> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/drawing/Direction3D.hpp> +#include <com/sun/star/drawing/ProjectionMode.hpp> +#include <com/sun/star/drawing/ShadeMode.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/chart/ChartAxisArrangeOrderType.hpp> +#include <com/sun/star/chart/ChartAxisLabelPosition.hpp> +#include <com/sun/star/chart/ChartAxisMarkPosition.hpp> +#include <com/sun/star/chart/ChartAxisPosition.hpp> +#include <com/sun/star/chart/ChartLegendExpansion.hpp> +#include <com/sun/star/chart/TimeInterval.hpp> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart/XChartDocument.hpp> +#include <com/sun/star/chart/XDiagramPositioning.hpp> +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart/ErrorBarStyle.hpp> +#include <com/sun/star/chart/MissingValueTreatment.hpp> +#include <com/sun/star/chart2/LinearRegressionCurve.hpp> +#include <com/sun/star/chart2/ExponentialRegressionCurve.hpp> +#include <com/sun/star/chart2/LogarithmicRegressionCurve.hpp> +#include <com/sun/star/chart2/PotentialRegressionCurve.hpp> +#include <com/sun/star/chart2/PolynomialRegressionCurve.hpp> +#include <com/sun/star/chart2/MovingAverageRegressionCurve.hpp> +#include <com/sun/star/chart2/CartesianCoordinateSystem2d.hpp> +#include <com/sun/star/chart2/CartesianCoordinateSystem3d.hpp> +#include <com/sun/star/chart2/FormattedString.hpp> +#include <com/sun/star/chart2/LogarithmicScaling.hpp> +#include <com/sun/star/chart2/LinearScaling.hpp> +#include <com/sun/star/chart2/PolarCoordinateSystem2d.hpp> +#include <com/sun/star/chart2/PolarCoordinateSystem3d.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/XDiagram.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XDataSeriesContainer.hpp> +#include <com/sun/star/chart2/XRegressionCurveContainer.hpp> +#include <com/sun/star/chart2/XTitled.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/chart2/CurveStyle.hpp> +#include <com/sun/star/chart2/DataPointGeometry3D.hpp> +#include <com/sun/star/chart2/DataPointLabel.hpp> +#include <com/sun/star/chart2/LegendPosition.hpp> +#include <com/sun/star/chart2/StackingDirection.hpp> +#include <com/sun/star/chart2/TickmarkStyle.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/data/XDataProvider.hpp> +#include <com/sun/star/chart2/data/XDataReceiver.hpp> +#include <com/sun/star/chart2/data/XDataSink.hpp> +#include <com/sun/star/chart2/data/LabeledDataSequence.hpp> +#include <comphelper/processfactory.hxx> +#include <o3tl/numeric.hxx> +#include <o3tl/unit_conversion.hxx> +#include <sfx2/objsh.hxx> +#include <svx/svdpage.hxx> +#include <svx/unoapi.hxx> +#include <sal/log.hxx> +#include <tools/helpers.hxx> + +#include <document.hxx> +#include <drwlayer.hxx> +#include <tokenarray.hxx> +#include <compiler.hxx> +#include <reftokenhelper.hxx> +#include <chartlis.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <xltracer.hxx> +#include <xltools.hxx> +#include <xistream.hxx> +#include <xiformula.hxx> +#include <xistyle.hxx> +#include <xipage.hxx> +#include <xiview.hxx> + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::com::sun::star::drawing::XDrawPage; +using ::com::sun::star::drawing::XDrawPageSupplier; +using ::com::sun::star::drawing::XShape; + +using namespace ::com::sun::star::chart2; + +using ::com::sun::star::chart2::data::XDataProvider; +using ::com::sun::star::chart2::data::XDataReceiver; +using ::com::sun::star::chart2::data::XDataSequence; +using ::com::sun::star::chart2::data::XDataSink; +using ::com::sun::star::chart2::data::XLabeledDataSequence; +using ::com::sun::star::chart2::data::LabeledDataSequence; + +using ::formula::FormulaToken; +using ::formula::FormulaTokenArrayPlainIterator; +using ::std::unique_ptr; + +namespace cssc = ::com::sun::star::chart; +namespace cssc2 = ::com::sun::star::chart2; + +// Helpers ==================================================================== + +namespace { + +XclImpStream& operator>>( XclImpStream& rStrm, XclChRectangle& rRect ) +{ + rRect.mnX = rStrm.ReadInt32(); + rRect.mnY = rStrm.ReadInt32(); + rRect.mnWidth = rStrm.ReadInt32(); + rRect.mnHeight = rStrm.ReadInt32(); + return rStrm; +} + +void lclSetValueOrClearAny( Any& rAny, double fValue, bool bClear ) +{ + if( bClear ) + rAny.clear(); + else + rAny <<= fValue; +} + +void lclSetExpValueOrClearAny( Any& rAny, double fValue, bool bLogScale, bool bClear ) +{ + if( !bClear && bLogScale ) + fValue = pow( 10.0, fValue ); + lclSetValueOrClearAny( rAny, fValue, bClear ); +} + +double lclGetSerialDay( const XclImpRoot& rRoot, sal_uInt16 nValue, sal_uInt16 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: + return nValue; + case EXC_CHDATERANGE_MONTHS: + return rRoot.GetDoubleFromDateTime( Date( 1, static_cast< sal_uInt16 >( 1 + nValue % 12 ), static_cast< sal_uInt16 >( rRoot.GetBaseYear() + nValue / 12 ) ) ); + case EXC_CHDATERANGE_YEARS: + return rRoot.GetDoubleFromDateTime( Date( 1, 1, static_cast< sal_uInt16 >( rRoot.GetBaseYear() + nValue ) ) ); + default: + OSL_ENSURE( false, "lclGetSerialDay - unexpected time unit" ); + } + return nValue; +} + +void lclConvertTimeValue( const XclImpRoot& rRoot, Any& rAny, sal_uInt16 nValue, bool bAuto, sal_uInt16 nTimeUnit ) +{ + if( bAuto ) + rAny.clear(); + else + rAny <<= lclGetSerialDay( rRoot, nValue, nTimeUnit ); +} + +sal_Int32 lclGetApiTimeUnit( sal_uInt16 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: return cssc::TimeUnit::DAY; + case EXC_CHDATERANGE_MONTHS: return cssc::TimeUnit::MONTH; + case EXC_CHDATERANGE_YEARS: return cssc::TimeUnit::YEAR; + default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" ); + } + return cssc::TimeUnit::DAY; +} + +void lclConvertTimeInterval( Any& rInterval, sal_uInt16 nValue, bool bAuto, sal_uInt16 nTimeUnit ) +{ + if( bAuto || (nValue == 0) ) + rInterval.clear(); + else + rInterval <<= cssc::TimeInterval( nValue, lclGetApiTimeUnit( nTimeUnit ) ); +} + +} // namespace + +// Common ===================================================================== + +/** Stores global data needed in various classes of the Chart import filter. */ +struct XclImpChRootData : public XclChRootData +{ + XclImpChChart& mrChartData; /// The chart data object. + + explicit XclImpChRootData( XclImpChChart& rChartData ) : mrChartData( rChartData ) {} +}; + +XclImpChRoot::XclImpChRoot( const XclImpRoot& rRoot, XclImpChChart& rChartData ) : + XclImpRoot( rRoot ), + mxChData( std::make_shared<XclImpChRootData>( rChartData ) ) +{ +} + +XclImpChRoot::~XclImpChRoot() +{ +} + +XclImpChChart& XclImpChRoot::GetChartData() const +{ + return mxChData->mrChartData; +} + +const XclChTypeInfo& XclImpChRoot::GetChartTypeInfo( XclChTypeId eType ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfo( eType ); +} + +const XclChTypeInfo& XclImpChRoot::GetChartTypeInfo( sal_uInt16 nRecId ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfoFromRecId( nRecId ); +} + +const XclChFormatInfo& XclImpChRoot::GetFormatInfo( XclChObjectType eObjType ) const +{ + return mxChData->mxFmtInfoProv->GetFormatInfo( eObjType ); +} + +Color XclImpChRoot::GetFontAutoColor() const +{ + return GetPalette().GetColor( EXC_COLOR_CHWINDOWTEXT ); +} + +Color XclImpChRoot::GetSeriesLineAutoColor( sal_uInt16 nFormatIdx ) const +{ + return GetPalette().GetColor( XclChartHelper::GetSeriesLineAutoColorIdx( nFormatIdx ) ); +} + +Color XclImpChRoot::GetSeriesFillAutoColor( sal_uInt16 nFormatIdx ) const +{ + const XclImpPalette& rPal = GetPalette(); + Color aColor = rPal.GetColor( XclChartHelper::GetSeriesFillAutoColorIdx( nFormatIdx ) ); + sal_uInt8 nTrans = XclChartHelper::GetSeriesFillAutoTransp( nFormatIdx ); + return ScfTools::GetMixedColor( aColor, rPal.GetColor( EXC_COLOR_CHWINDOWBACK ), nTrans ); +} + +void XclImpChRoot::InitConversion( const Reference<XChartDocument>& xChartDoc, const tools::Rectangle& rChartRect ) const +{ + // create formatting object tables + mxChData->InitConversion( GetRoot(), xChartDoc, rChartRect ); + + // lock the model to suppress any internal updates + if( xChartDoc.is() ) + xChartDoc->lockControllers(); + + SfxObjectShell* pDocShell = GetDocShell(); + Reference< XDataReceiver > xDataRec( xChartDoc, UNO_QUERY ); + if( pDocShell && xDataRec.is() ) + { + // create and register a data provider + Reference< XDataProvider > xDataProv( + ScfApiHelper::CreateInstance( pDocShell, SERVICE_CHART2_DATAPROVIDER ), UNO_QUERY ); + if( xDataProv.is() ) + xDataRec->attachDataProvider( xDataProv ); + // attach the number formatter + Reference< XNumberFormatsSupplier > xNumFmtSupp( pDocShell->GetModel(), UNO_QUERY ); + if( xNumFmtSupp.is() ) + xDataRec->attachNumberFormatsSupplier( xNumFmtSupp ); + } +} + +void XclImpChRoot::FinishConversion( XclImpDffConverter& rDffConv ) const +{ + rDffConv.Progress( EXC_CHART_PROGRESS_SIZE ); + // unlock the model + Reference< XModel > xModel = mxChData->mxChartDoc; + if( xModel.is() ) + xModel->unlockControllers(); + rDffConv.Progress( EXC_CHART_PROGRESS_SIZE ); + + mxChData->FinishConversion(); +} + +Reference< XDataProvider > XclImpChRoot::GetDataProvider() const +{ + return mxChData->mxChartDoc->getDataProvider(); +} + +Reference< XShape > XclImpChRoot::GetTitleShape( const XclChTextKey& rTitleKey ) const +{ + return mxChData->GetTitleShape( rTitleKey ); +} + +sal_Int32 XclImpChRoot::CalcHmmFromChartX( sal_Int32 nPosX ) const +{ + return static_cast< sal_Int32 >( mxChData->mfUnitSizeX * nPosX + mxChData->mnBorderGapX + 0.5 ); +} + +sal_Int32 XclImpChRoot::CalcHmmFromChartY( sal_Int32 nPosY ) const +{ + return static_cast< sal_Int32 >( mxChData->mfUnitSizeY * nPosY + mxChData->mnBorderGapY + 0.5 ); +} + +css::awt::Rectangle XclImpChRoot::CalcHmmFromChartRect( const XclChRectangle& rRect ) const +{ + return css::awt::Rectangle( + CalcHmmFromChartX( rRect.mnX ), + CalcHmmFromChartY( rRect.mnY ), + CalcHmmFromChartX( rRect.mnWidth ), + CalcHmmFromChartY( rRect.mnHeight ) ); +} + +double XclImpChRoot::CalcRelativeFromHmmX( sal_Int32 nPosX ) const +{ + const tools::Long nWidth = mxChData->maChartRect.GetWidth(); + if (!nWidth) + throw o3tl::divide_by_zero(); + return static_cast<double>(nPosX) / nWidth; +} + +double XclImpChRoot::CalcRelativeFromHmmY( sal_Int32 nPosY ) const +{ + const tools::Long nHeight = mxChData->maChartRect.GetHeight(); + if (!nHeight) + throw o3tl::divide_by_zero(); + return static_cast<double >(nPosY) / nHeight; +} + +double XclImpChRoot::CalcRelativeFromChartX( sal_Int32 nPosX ) const +{ + return CalcRelativeFromHmmX( CalcHmmFromChartX( nPosX ) ); +} + +double XclImpChRoot::CalcRelativeFromChartY( sal_Int32 nPosY ) const +{ + return CalcRelativeFromHmmY( CalcHmmFromChartY( nPosY ) ); +} + +void XclImpChRoot::ConvertLineFormat( ScfPropertySet& rPropSet, + const XclChLineFormat& rLineFmt, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteLineProperties( + rPropSet, *mxChData->mxLineDashTable, rLineFmt, ePropMode ); +} + +void XclImpChRoot::ConvertAreaFormat( ScfPropertySet& rPropSet, + const XclChAreaFormat& rAreaFmt, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteAreaProperties( rPropSet, rAreaFmt, ePropMode ); +} + +void XclImpChRoot::ConvertEscherFormat( ScfPropertySet& rPropSet, + const XclChEscherFormat& rEscherFmt, const XclChPicFormat* pPicFmt, + sal_uInt32 nDffFillType, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteEscherProperties( rPropSet, + *mxChData->mxGradientTable, *mxChData->mxBitmapTable, + rEscherFmt, pPicFmt, nDffFillType, ePropMode ); +} + +void XclImpChRoot::ConvertFont( ScfPropertySet& rPropSet, + sal_uInt16 nFontIdx, const Color* pFontColor ) const +{ + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CHART, nFontIdx, pFontColor ); +} + +void XclImpChRoot::ConvertPieRotation( ScfPropertySet& rPropSet, sal_uInt16 nAngle ) +{ + sal_Int32 nApiRot = (450 - (nAngle % 360)) % 360; + rPropSet.SetProperty( EXC_CHPROP_STARTINGANGLE, nApiRot ); +} + +XclImpChGroupBase::~XclImpChGroupBase() +{ +} + +void XclImpChGroupBase::ReadRecordGroup( XclImpStream& rStrm ) +{ + // read contents of the header record + ReadHeaderRecord( rStrm ); + + // only read sub records, if the next record is a CHBEGIN + if( rStrm.GetNextRecId() != EXC_ID_CHBEGIN ) + return; + + // read the CHBEGIN record, may be used for special initial processing + rStrm.StartNextRecord(); + ReadSubRecord( rStrm ); + + // read the nested records + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_CHEND; + // skip unsupported nested blocks + if( nRecId == EXC_ID_CHBEGIN ) + SkipBlock( rStrm ); + else + ReadSubRecord( rStrm ); + } + /* Returns with current CHEND record or unchanged stream, if no record + group present. In every case another call to StartNextRecord() will go + to next record of interest. */ +} + +void XclImpChGroupBase::SkipBlock( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecId() == EXC_ID_CHBEGIN, "XclImpChGroupBase::SkipBlock - no CHBEGIN record" ); + // do nothing if current record is not CHBEGIN + bool bLoop = rStrm.GetRecId() == EXC_ID_CHBEGIN; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_CHEND; + // skip nested record groups + if( nRecId == EXC_ID_CHBEGIN ) + SkipBlock( rStrm ); + } +} + +// Frame formatting =========================================================== + +void XclImpChFramePos::ReadChFramePos( XclImpStream& rStrm ) +{ + maData.mnTLMode = rStrm.ReaduInt16(); + maData.mnBRMode = rStrm.ReaduInt16(); + /* According to the spec, the upper 16 bits of all members in the + rectangle are unused and may contain garbage. */ + maData.maRect.mnX = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnY = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnWidth = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnHeight = rStrm.ReadInt16(); rStrm.Ignore( 2 ); +} + +void XclImpChLineFormat::ReadChLineFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maColor; + maData.mnPattern = rStrm.ReaduInt16(); + maData.mnWeight = rStrm.ReadInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + // BIFF8: index into palette used instead of RGB data + maData.maColor = rRoot.GetPalette().GetColor( rStrm.ReaduInt16() ); +} + +void XclImpChLineFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( IsAuto() ) + { + XclChLineFormat aLineFmt; + aLineFmt.maColor = (eObjType == EXC_CHOBJTYPE_LINEARSERIES) ? + rRoot.GetSeriesLineAutoColor( nFormatIdx ) : + rRoot.GetPalette().GetColor( rFmtInfo.mnAutoLineColorIdx ); + aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + aLineFmt.mnWeight = rFmtInfo.mnAutoLineWeight; + rRoot.ConvertLineFormat( rPropSet, aLineFmt, rFmtInfo.mePropMode ); + } + else + { + rRoot.ConvertLineFormat( rPropSet, maData, rFmtInfo.mePropMode ); + } +} + +void XclImpChAreaFormat::ReadChAreaFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maPattColor >> maData.maBackColor; + maData.mnPattern = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + const XclImpPalette& rPal = rRoot.GetPalette(); + maData.maPattColor = rPal.GetColor( rStrm.ReaduInt16() ); + maData.maBackColor = rPal.GetColor( rStrm.ReaduInt16()); + } +} + +void XclImpChAreaFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( IsAuto() ) + { + XclChAreaFormat aAreaFmt; + aAreaFmt.maPattColor = (eObjType == EXC_CHOBJTYPE_FILLEDSERIES) ? + rRoot.GetSeriesFillAutoColor( nFormatIdx ) : + rRoot.GetPalette().GetColor( rFmtInfo.mnAutoPattColorIdx ); + aAreaFmt.mnPattern = EXC_PATT_SOLID; + rRoot.ConvertAreaFormat( rPropSet, aAreaFmt, rFmtInfo.mePropMode ); + } + else + { + rRoot.ConvertAreaFormat( rPropSet, maData, rFmtInfo.mePropMode ); + } +} + +XclImpChEscherFormat::XclImpChEscherFormat( const XclImpRoot& rRoot ) : + mnDffFillType( mso_fillSolid ) +{ + maData.mxItemSet = + std::make_shared<SfxItemSet>( rRoot.GetDoc().GetDrawLayer()->GetItemPool() ); +} + +void XclImpChEscherFormat::ReadHeaderRecord( XclImpStream& rStrm ) +{ + // read from stream - CHESCHERFORMAT uses own ID for record continuation + XclImpDffPropSet aPropSet( rStrm.GetRoot() ); + rStrm.ResetRecord( true, rStrm.GetRecId() ); + rStrm >> aPropSet; + // get the data + aPropSet.FillToItemSet( *maData.mxItemSet ); + // get fill type from DFF property set + mnDffFillType = aPropSet.GetPropertyValue( DFF_Prop_fillType ); +} + +void XclImpChEscherFormat::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHPICFORMAT: + maPicFmt.mnBmpMode = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + maPicFmt.mnFlags = rStrm.ReaduInt16(); + maPicFmt.mfScale = rStrm.ReadDouble(); + break; + } +} + +void XclImpChEscherFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, bool bUsePicFmt ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + rRoot.ConvertEscherFormat( rPropSet, maData, bUsePicFmt ? &maPicFmt : nullptr, mnDffFillType, rFmtInfo.mePropMode ); +} + +XclImpChFrameBase::XclImpChFrameBase( const XclChFormatInfo& rFmtInfo ) +{ + if( !rFmtInfo.mbCreateDefFrame ) + return; + + switch( rFmtInfo.meDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + mxLineFmt = new XclImpChLineFormat(); + if( rFmtInfo.mbIsFrame ) + mxAreaFmt = std::make_shared<XclImpChAreaFormat>(); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + { + XclChLineFormat aLineFmt; + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, false ); + aLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + mxLineFmt = new XclImpChLineFormat( aLineFmt ); + if( rFmtInfo.mbIsFrame ) + { + XclChAreaFormat aAreaFmt; + ::set_flag( aAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, false ); + aAreaFmt.mnPattern = EXC_PATT_NONE; + mxAreaFmt = std::make_shared<XclImpChAreaFormat>( aAreaFmt ); + } + } + break; + default: + OSL_FAIL( "XclImpChFrameBase::XclImpChFrameBase - unknown frame type" ); + } +} + +void XclImpChFrameBase::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHLINEFORMAT: + mxLineFmt = new XclImpChLineFormat(); + mxLineFmt->ReadChLineFormat( rStrm ); + break; + case EXC_ID_CHAREAFORMAT: + mxAreaFmt = std::make_shared<XclImpChAreaFormat>(); + mxAreaFmt->ReadChAreaFormat( rStrm ); + break; + case EXC_ID_CHESCHERFORMAT: + mxEscherFmt = std::make_shared<XclImpChEscherFormat>( rStrm.GetRoot() ); + mxEscherFmt->ReadRecordGroup( rStrm ); + break; + } +} + +void XclImpChFrameBase::ConvertLineBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + if( mxLineFmt ) + mxLineFmt->Convert( rRoot, rPropSet, eObjType, nFormatIdx ); +} + +void XclImpChFrameBase::ConvertAreaBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx, bool bUsePicFmt ) const +{ + if( rRoot.GetFormatInfo( eObjType ).mbIsFrame ) + { + // CHESCHERFORMAT overrides CHAREAFORMAT (even if it is auto) + if( mxEscherFmt ) + mxEscherFmt->Convert( rRoot, rPropSet, eObjType, bUsePicFmt ); + else if( mxAreaFmt ) + mxAreaFmt->Convert( rRoot, rPropSet, eObjType, nFormatIdx ); + } +} + +void XclImpChFrameBase::ConvertFrameBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx, bool bUsePicFmt ) const +{ + ConvertLineBase( rRoot, rPropSet, eObjType, nFormatIdx ); + ConvertAreaBase( rRoot, rPropSet, eObjType, nFormatIdx, bUsePicFmt ); +} + +XclImpChFrame::XclImpChFrame( const XclImpChRoot& rRoot, XclChObjectType eObjType ) : + XclImpChFrameBase( rRoot.GetFormatInfo( eObjType ) ), + XclImpChRoot( rRoot ), + meObjType( eObjType ) +{ +} + +void XclImpChFrame::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnFormat = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChFrame::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + const XclImpPalette& rPal = GetPalette(); + + if( rLineData.IsVisible() && (!mxLineFmt || !mxLineFmt->HasLine()) ) + { + // line formatting + XclChLineFormat aLineFmt; + aLineFmt.maColor = rPal.GetColor( rLineData.mnColorIdx ); + switch( rLineData.mnStyle ) + { + case EXC_OBJ_LINE_SOLID: aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; break; + case EXC_OBJ_LINE_DASH: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASH; break; + case EXC_OBJ_LINE_DOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DOT; break; + case EXC_OBJ_LINE_DASHDOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASHDOT; break; + case EXC_OBJ_LINE_DASHDOTDOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASHDOTDOT; break; + case EXC_OBJ_LINE_MEDTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_MEDTRANS; break; + case EXC_OBJ_LINE_DARKTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DARKTRANS; break; + case EXC_OBJ_LINE_LIGHTTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_LIGHTTRANS; break; + case EXC_OBJ_LINE_NONE: aLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; break; + default: aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + } + switch( rLineData.mnWidth ) + { + case EXC_OBJ_LINE_HAIR: aLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; break; + case EXC_OBJ_LINE_THIN: aLineFmt.mnWeight = EXC_CHLINEFORMAT_SINGLE; break; + case EXC_OBJ_LINE_MEDIUM: aLineFmt.mnWeight = EXC_CHLINEFORMAT_DOUBLE; break; + case EXC_OBJ_LINE_THICK: aLineFmt.mnWeight = EXC_CHLINEFORMAT_TRIPLE; break; + default: aLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; + } + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, rLineData.IsAuto() ); + mxLineFmt = new XclImpChLineFormat( aLineFmt ); + } + + if( rFillData.IsFilled() && (!mxAreaFmt || !mxAreaFmt->HasArea()) && !mxEscherFmt ) + { + // area formatting + XclChAreaFormat aAreaFmt; + aAreaFmt.maPattColor = rPal.GetColor( rFillData.mnPattColorIdx ); + aAreaFmt.maBackColor = rPal.GetColor( rFillData.mnBackColorIdx ); + aAreaFmt.mnPattern = rFillData.mnPattern; + ::set_flag( aAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, rFillData.IsAuto() ); + mxAreaFmt = std::make_shared<XclImpChAreaFormat>( aAreaFmt ); + } +} + +void XclImpChFrame::Convert( ScfPropertySet& rPropSet, bool bUsePicFmt ) const +{ + ConvertFrameBase( GetChRoot(), rPropSet, meObjType, EXC_CHDATAFORMAT_UNKNOWN, bUsePicFmt ); +} + +// Source links =============================================================== + +namespace { + +/** Creates a labeled data sequence object, adds link for series title if present. */ +Reference< XLabeledDataSequence > lclCreateLabeledDataSequence( + const XclImpChSourceLinkRef& xValueLink, const OUString& rValueRole, + const XclImpChSourceLink* pTitleLink = nullptr ) +{ + // create data sequence for values and title + Reference< XDataSequence > xValueSeq; + if( xValueLink ) + xValueSeq = xValueLink->CreateDataSequence( rValueRole ); + Reference< XDataSequence > xTitleSeq; + if( pTitleLink ) + xTitleSeq = pTitleLink->CreateDataSequence( EXC_CHPROP_ROLE_LABEL ); + + // create the labeled data sequence, if values or title are present + Reference< XLabeledDataSequence > xLabeledSeq; + if( xValueSeq.is() || xTitleSeq.is() ) + xLabeledSeq = LabeledDataSequence::create(comphelper::getProcessComponentContext()); + if( xLabeledSeq.is() ) + { + if( xValueSeq.is() ) + xLabeledSeq->setValues( xValueSeq ); + if( xTitleSeq.is() ) + xLabeledSeq->setLabel( xTitleSeq ); + } + return xLabeledSeq; +} + +} // namespace + +XclImpChSourceLink::XclImpChSourceLink( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +XclImpChSourceLink::~XclImpChSourceLink() +{ +} + +void XclImpChSourceLink::ReadChSourceLink( XclImpStream& rStrm ) +{ + maData.mnDestType = rStrm.ReaduInt8(); + maData.mnLinkType = rStrm.ReaduInt8(); + maData.mnFlags = rStrm.ReaduInt16(); + maData.mnNumFmtIdx = rStrm.ReaduInt16(); + + mxTokenArray.reset(); + if( GetLinkType() == EXC_CHSRCLINK_WORKSHEET ) + { + // read token array + XclTokenArray aXclTokArr; + rStrm >> aXclTokArr; + + // convert BIFF formula tokens to Calc token array + if( std::unique_ptr<ScTokenArray> pTokens = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CHART, aXclTokArr ) ) + mxTokenArray = std::move( pTokens ); + } + + // try to read a following CHSTRING record + if( (rStrm.GetNextRecId() == EXC_ID_CHSTRING) && rStrm.StartNextRecord() ) + { + mxString = std::make_shared<XclImpString>(); + rStrm.Ignore( 2 ); + mxString->Read( rStrm, XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); + } +} + +void XclImpChSourceLink::SetString( const OUString& rString ) +{ + if( !mxString ) + mxString = std::make_shared<XclImpString>(); + mxString->SetText( rString ); +} + +void XclImpChSourceLink::SetTextFormats( XclFormatRunVec&& rFormats ) +{ + if( mxString ) + mxString->SetFormats( std::move(rFormats) ); +} + +sal_uInt16 XclImpChSourceLink::GetCellCount() const +{ + sal_uInt32 nCellCount = 0; + if( mxTokenArray ) + { + FormulaTokenArrayPlainIterator aIter(*mxTokenArray); + for( const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next() ) + { + switch( pToken->GetType() ) + { + case ::formula::svSingleRef: + case ::formula::svExternalSingleRef: + // single cell + ++nCellCount; + break; + case ::formula::svDoubleRef: + case ::formula::svExternalDoubleRef: + { + // cell range + const ScComplexRefData& rComplexRef = *pToken->GetDoubleRef(); + ScAddress aAbs1 = rComplexRef.Ref1.toAbs(GetRoot().GetDoc(), ScAddress()); + ScAddress aAbs2 = rComplexRef.Ref2.toAbs(GetRoot().GetDoc(), ScAddress()); + sal_uInt32 nTabs = static_cast<sal_uInt32>(aAbs2.Tab() - aAbs1.Tab() + 1); + sal_uInt32 nCols = static_cast<sal_uInt32>(aAbs2.Col() - aAbs1.Col() + 1); + sal_uInt32 nRows = static_cast<sal_uInt32>(aAbs2.Row() - aAbs1.Row() + 1); + nCellCount += nCols * nRows * nTabs; + } + break; + default: ; + } + } + } + return limit_cast< sal_uInt16 >( nCellCount ); +} + +void XclImpChSourceLink::ConvertNumFmt( ScfPropertySet& rPropSet, bool bPercent ) const +{ + bool bLinkToSource = ::get_flag( maData.mnFlags, EXC_CHSRCLINK_NUMFMT ); + sal_uInt32 nScNumFmt = bLinkToSource ? GetNumFmtBuffer().GetScFormat( maData.mnNumFmtIdx ) : NUMBERFORMAT_ENTRY_NOT_FOUND; + OUString aPropName = bPercent ? OUString( EXC_CHPROP_PERCENTAGENUMFMT ) : OUString( EXC_CHPROP_NUMBERFORMAT ); + if( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND ) + rPropSet.SetProperty( aPropName, static_cast< sal_Int32 >( nScNumFmt ) ); + else + // restore 'link to source' at data point (series may contain manual number format) + rPropSet.SetAnyProperty( aPropName, Any() ); +} + +Reference< XDataSequence > XclImpChSourceLink::CreateDataSequence( const OUString& rRole ) const +{ + Reference< XDataSequence > xDataSeq; + Reference< XDataProvider > xDataProv = GetDataProvider(); + if( xDataProv.is() ) + { + if ( mxTokenArray ) + { + ScCompiler aComp( GetDoc(), ScAddress(), *mxTokenArray, GetDoc().GetGrammar() ); + OUStringBuffer aRangeRep; + aComp.CreateStringFromTokenArray( aRangeRep ); + try + { + xDataSeq = xDataProv->createDataSequenceByRangeRepresentation( aRangeRep.makeStringAndClear() ); + // set sequence role + ScfPropertySet aSeqProp( xDataSeq ); + aSeqProp.SetProperty( EXC_CHPROP_ROLE, rRole ); + } + catch( Exception& ) + { + // OSL_FAIL( "XclImpChSourceLink::CreateDataSequence - cannot create data sequence" ); + } + } + else if( rRole == EXC_CHPROP_ROLE_LABEL && mxString && !mxString->GetText().isEmpty() ) + { + try + { + OUString aString("\""); + xDataSeq = xDataProv->createDataSequenceByRangeRepresentation( aString + mxString->GetText() + aString ); + // set sequence role + ScfPropertySet aSeqProp( xDataSeq ); + aSeqProp.SetProperty( EXC_CHPROP_ROLE, rRole ); + } + catch( Exception& ) { } + } + } + return xDataSeq; +} + +Sequence< Reference< XFormattedString > > XclImpChSourceLink::CreateStringSequence( + const XclImpChRoot& rRoot, sal_uInt16 nLeadFontIdx, const Color& rLeadFontColor ) const +{ + ::std::vector< Reference< XFormattedString > > aStringVec; + if( mxString ) + { + for( XclImpStringIterator aIt( *mxString ); aIt.Is(); ++aIt ) + { + Reference< css::chart2::XFormattedString2 > xFmtStr = css::chart2::FormattedString::create( comphelper::getProcessComponentContext() ); + // set text data + xFmtStr->setString( aIt.GetPortionText() ); + + // set font formatting and font color + ScfPropertySet aStringProp( xFmtStr ); + sal_uInt16 nFontIdx = aIt.GetPortionFont(); + if( (nFontIdx == EXC_FONT_NOTFOUND) && (aIt.GetPortionIndex() == 0) ) + // leading unformatted portion - use passed font settings + rRoot.ConvertFont( aStringProp, nLeadFontIdx, &rLeadFontColor ); + else + rRoot.ConvertFont( aStringProp, nFontIdx ); + + // add string to vector of strings + aStringVec.emplace_back(xFmtStr ); + } + } + return ScfApiHelper::VectorToSequence( aStringVec ); +} + +void XclImpChSourceLink::FillSourceLink( ::std::vector< ScTokenRef >& rTokens ) const +{ + if( !mxTokenArray ) + // no links to fill. + return; + + FormulaTokenArrayPlainIterator aIter(*mxTokenArray); + for (FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + ScTokenRef pToken(p->Clone()); + if (ScRefTokenHelper::isRef(pToken)) + // This is a reference token. Store it. + ScRefTokenHelper::join(&GetRoot().GetDoc(), rTokens, pToken, ScAddress()); + } +} + +// Text ======================================================================= + +XclImpChFontBase::~XclImpChFontBase() +{ +} + +void XclImpChFontBase::ConvertFontBase( const XclImpChRoot& rRoot, ScfPropertySet& rPropSet ) const +{ + Color aFontColor = GetFontColor(); + rRoot.ConvertFont( rPropSet, GetFontIndex(), &aFontColor ); +} + +void XclImpChFontBase::ConvertRotationBase( ScfPropertySet& rPropSet, bool bSupportsStacked ) const +{ + XclChPropSetHelper::WriteRotationProperties( rPropSet, GetRotation(), bSupportsStacked ); +} + +XclImpChFont::XclImpChFont() : + mnFontIdx( EXC_FONT_NOTFOUND ) +{ +} + +void XclImpChFont::ReadChFont( XclImpStream& rStrm ) +{ + mnFontIdx = rStrm.ReaduInt16(); +} + +XclImpChText::XclImpChText( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChText::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnHAlign = rStrm.ReaduInt8(); + maData.mnVAlign = rStrm.ReaduInt8(); + maData.mnBackMode = rStrm.ReaduInt16(); + rStrm >> maData.maTextColor + >> maData.maRect; + maData.mnFlags = rStrm.ReaduInt16(); + + if( GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + maData.maTextColor = GetPalette().GetColor( rStrm.ReaduInt16() ); + // placement and rotation + maData.mnFlags2 = rStrm.ReaduInt16(); + maData.mnRotation = rStrm.ReaduInt16(); + } + else + { + // BIFF2-BIFF7: get rotation from text orientation + sal_uInt8 nOrient = ::extract_value< sal_uInt8 >( maData.mnFlags, 8, 3 ); + maData.mnRotation = XclTools::GetXclRotFromOrient( nOrient ); + } +} + +void XclImpChText::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared<XclImpChFramePos>(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHFONT: + mxFont = std::make_shared<XclImpChFont>(); + mxFont->ReadChFont( rStrm ); + break; + case EXC_ID_CHFORMATRUNS: + if( GetBiff() == EXC_BIFF8 ) + XclImpString::ReadFormats( rStrm, maFormats ); + break; + case EXC_ID_CHSOURCELINK: + mxSrcLink = std::make_shared<XclImpChSourceLink>( GetChRoot() ); + mxSrcLink->ReadChSourceLink( rStrm ); + break; + case EXC_ID_CHFRAME: + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_TEXT ); + mxFrame->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHOBJECTLINK: + maObjLink.mnTarget = rStrm.ReaduInt16(); + maObjLink.maPointPos.mnSeriesIdx = rStrm.ReaduInt16(); + maObjLink.maPointPos.mnPointIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHFRLABELPROPS: + ReadChFrLabelProps( rStrm ); + break; + case EXC_ID_CHEND: + if( mxSrcLink && !maFormats.empty() ) + mxSrcLink->SetTextFormats( std::vector(maFormats) ); + break; + } +} + +sal_uInt16 XclImpChText::GetFontIndex() const +{ + return mxFont ? mxFont->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +Color XclImpChText::GetFontColor() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR ) ? GetFontAutoColor() : maData.maTextColor; +} + +sal_uInt16 XclImpChText::GetRotation() const +{ + return maData.mnRotation; +} + +void XclImpChText::SetString( const OUString& rString ) +{ + if( !mxSrcLink ) + mxSrcLink = std::make_shared<XclImpChSourceLink>( GetChRoot() ); + mxSrcLink->SetString( rString ); +} + +void XclImpChText::UpdateText( const XclImpChText* pParentText ) +{ + if( !pParentText ) + return; + + // update missing members + if( !mxFrame ) + mxFrame = pParentText->mxFrame; + if( !mxFont ) + { + mxFont = pParentText->mxFont; + // text color is taken from CHTEXT record, not from font in CHFONT + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR, ::get_flag( pParentText->maData.mnFlags, EXC_CHTEXT_AUTOCOLOR ) ); + maData.maTextColor = pParentText->maData.maTextColor; + } +} + +void XclImpChText::UpdateDataLabel( bool bCateg, bool bValue, bool bPercent ) +{ + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG, bCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE, bValue ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT, bPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC, bCateg && bPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED, !bCateg && !bValue && !bPercent ); +} + +void XclImpChText::ConvertFont( ScfPropertySet& rPropSet ) const +{ + ConvertFontBase( GetChRoot(), rPropSet ); +} + +void XclImpChText::ConvertRotation( ScfPropertySet& rPropSet, bool bSupportsStacked ) const +{ + ConvertRotationBase( rPropSet, bSupportsStacked ); +} + +void XclImpChText::ConvertFrame( ScfPropertySet& rPropSet ) const +{ + if( mxFrame ) + mxFrame->Convert( rPropSet ); +} + +void XclImpChText::ConvertNumFmt( ScfPropertySet& rPropSet, bool bPercent ) const +{ + if( mxSrcLink ) + mxSrcLink->ConvertNumFmt( rPropSet, bPercent ); +} + +void XclImpChText::ConvertDataLabel( ScfPropertySet& rPropSet, const XclChTypeInfo& rTypeInfo, const ScfPropertySet* pGlobalPropSet ) const +{ + // existing CHFRLABELPROPS record wins over flags from CHTEXT + sal_uInt16 nShowFlags = mxLabelProps ? mxLabelProps->mnFlags : maData.mnFlags; + sal_uInt16 SHOWANYCATEG = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWCATEG : (EXC_CHTEXT_SHOWCATEGPERC | EXC_CHTEXT_SHOWCATEG); + sal_uInt16 SHOWANYVALUE = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWVALUE : EXC_CHTEXT_SHOWVALUE; + sal_uInt16 SHOWANYPERCENT = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWPERCENT : (EXC_CHTEXT_SHOWPERCENT | EXC_CHTEXT_SHOWCATEGPERC); + sal_uInt16 SHOWANYBUBBLE = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWBUBBLE : EXC_CHTEXT_SHOWBUBBLE; + + // get raw flags for label values + bool bShowNone = IsDeleted(); + bool bShowCateg = !bShowNone && ::get_flag( nShowFlags, SHOWANYCATEG ); + bool bShowPercent = !bShowNone && ::get_flag( nShowFlags, SHOWANYPERCENT ); + bool bShowValue = !bShowNone && ::get_flag( nShowFlags, SHOWANYVALUE ); + bool bShowBubble = !bShowNone && ::get_flag( nShowFlags, SHOWANYBUBBLE ); + + // adjust to Chart2 behaviour + if( rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ) + bShowValue = bShowBubble; // Chart2 bubble charts show bubble size if 'ShowValue' is set + + // other flags + bool bShowAny = bShowValue || bShowPercent || bShowCateg; + bool bShowSymbol = bShowAny && ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWSYMBOL ); + + // create API struct for label values, set API label separator + cssc2::DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol, false, false ); + rPropSet.SetProperty( EXC_CHPROP_LABEL, aPointLabel ); + OUString aSep = mxLabelProps ? mxLabelProps->maSeparator : OUString('\n'); + if( aSep.isEmpty() ) + aSep = "; "; + rPropSet.SetStringProperty( EXC_CHPROP_LABELSEPARATOR, aSep ); + + // text properties of attached label + if( !bShowAny ) + return; + + ConvertFont( rPropSet ); + ConvertRotation( rPropSet, false ); + // label placement + using namespace cssc::DataLabelPlacement; + sal_Int32 nPlacement = rTypeInfo.mnDefaultLabelPos; + switch( ::extract_value< sal_uInt16 >( maData.mnFlags2, 0, 4 ) ) + { + case EXC_CHTEXT_POS_DEFAULT: nPlacement = rTypeInfo.mnDefaultLabelPos; break; + case EXC_CHTEXT_POS_OUTSIDE: nPlacement = OUTSIDE; break; + case EXC_CHTEXT_POS_INSIDE: nPlacement = INSIDE; break; + case EXC_CHTEXT_POS_CENTER: nPlacement = CENTER; break; + case EXC_CHTEXT_POS_AXIS: nPlacement = NEAR_ORIGIN; break; + case EXC_CHTEXT_POS_ABOVE: nPlacement = TOP; break; + case EXC_CHTEXT_POS_BELOW: nPlacement = BOTTOM; break; + case EXC_CHTEXT_POS_LEFT: nPlacement = LEFT; break; + case EXC_CHTEXT_POS_RIGHT: nPlacement = RIGHT; break; + case EXC_CHTEXT_POS_AUTO: nPlacement = AVOID_OVERLAP; break; + } + sal_Int32 nGlobalPlacement = 0; + if ( ( nPlacement == rTypeInfo.mnDefaultLabelPos ) && pGlobalPropSet && + pGlobalPropSet->GetProperty( nGlobalPlacement, EXC_CHPROP_LABELPLACEMENT ) ) + nPlacement = nGlobalPlacement; + + rPropSet.SetProperty( EXC_CHPROP_LABELPLACEMENT, nPlacement ); + // label number format (percentage format wins over value format) + if( bShowPercent || bShowValue ) + ConvertNumFmt( rPropSet, bShowPercent ); +} + +Reference< XTitle > XclImpChText::CreateTitle() const +{ + Reference< XTitle > xTitle; + if( mxSrcLink && mxSrcLink->HasString() ) + { + // create the formatted strings + Sequence< Reference< XFormattedString > > aStringSeq( + mxSrcLink->CreateStringSequence( GetChRoot(), GetFontIndex(), GetFontColor() ) ); + if( aStringSeq.hasElements() ) + { + // create the title object + xTitle.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_TITLE ), UNO_QUERY ); + if( xTitle.is() ) + { + // set the formatted strings + xTitle->setText( aStringSeq ); + // more title formatting properties + ScfPropertySet aTitleProp( xTitle ); + ConvertFrame( aTitleProp ); + ConvertRotation( aTitleProp, true ); + } + } + } + return xTitle; +} + +void XclImpChText::ConvertTitlePosition( const XclChTextKey& rTitleKey ) const +{ + if( !mxFramePos ) return; + + const XclChFramePos& rPosData = mxFramePos->GetFramePosData(); + OSL_ENSURE( (rPosData.mnTLMode == EXC_CHFRAMEPOS_PARENT) && (rPosData.mnBRMode == EXC_CHFRAMEPOS_PARENT), + "XclImpChText::ConvertTitlePosition - unexpected frame position mode" ); + + /* Check if title is moved manually. To get the actual position of the + title, we do some kind of hack and use the values from the CHTEXT + record, effectively ignoring the contents of the CHFRAMEPOS record + which contains the position relative to the default title position + (according to the spec, the CHFRAMEPOS supersedes the CHTEXT record). + Especially when it comes to axis titles, things would become very + complicated here, because the relative title position is stored in a + measurement unit that is dependent on the size of the inner plot area, + the interpretation of the X and Y coordinate is dependent on the + direction of the axis, and in 3D charts, and the title default + positions are dependent on the 3D view settings (rotation, elevation, + and perspective). Thus, it is easier to assume that the creator has + written out the correct absolute position and size of the title in the + CHTEXT record. This is assured by checking that the shape size stored + in the CHTEXT record is non-zero. */ + if( !((rPosData.mnTLMode == EXC_CHFRAMEPOS_PARENT) && + ((rPosData.maRect.mnX != 0) || (rPosData.maRect.mnY != 0)) && + (maData.maRect.mnWidth > 0) && (maData.maRect.mnHeight > 0)) ) + return; + + try + { + Reference< XShape > xTitleShape( GetTitleShape( rTitleKey ), UNO_SET_THROW ); + // the call to XShape.getSize() may recalc the chart view + css::awt::Size aTitleSize = xTitleShape->getSize(); + // rotated titles need special handling... + Degree100 nScRot = XclTools::GetScRotation( GetRotation(), 0_deg100 ); + double fRad = toRadians(nScRot); + double fSin = fabs( sin( fRad ) ); + // calculate the title position from the values in the CHTEXT record + css::awt::Point aTitlePos( + CalcHmmFromChartX( maData.maRect.mnX ), + CalcHmmFromChartY( maData.maRect.mnY ) ); + // add part of height to X direction, if title is rotated down (clockwise) + if( nScRot > 18000_deg100 ) + aTitlePos.X += static_cast< sal_Int32 >( fSin * aTitleSize.Height + 0.5 ); + // add part of width to Y direction, if title is rotated up (counterclockwise) + else if( nScRot > 0_deg100 ) + aTitlePos.Y += static_cast< sal_Int32 >( fSin * aTitleSize.Width + 0.5 ); + // set the resulting position at the title shape + xTitleShape->setPosition( aTitlePos ); + } + catch( Exception& ) + { + } +} + +void XclImpChText::ReadChFrLabelProps( XclImpStream& rStrm ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + mxLabelProps = std::make_shared<XclChFrLabelProps>(); + sal_uInt16 nSepLen; + rStrm.Ignore( 12 ); + mxLabelProps->mnFlags = rStrm.ReaduInt16(); + nSepLen = rStrm.ReaduInt16(); + if( nSepLen > 0 ) + mxLabelProps->maSeparator = rStrm.ReadUniString( nSepLen ); + } +} + +namespace { + +void lclUpdateText( XclImpChTextRef& rxText, const XclImpChText* xDefText ) +{ + if (rxText) + rxText->UpdateText( xDefText ); + else if (xDefText) + { + rxText = std::make_shared<XclImpChText>(*xDefText); + } +} + +void lclFinalizeTitle( XclImpChTextRef& rxTitle, const XclImpChText* pDefText, const OUString& rAutoTitle ) +{ + /* Do not update a title, if it is not visible (if rxTitle is null). + Existing reference indicates enabled title. */ + if( rxTitle ) + { + if( !rxTitle->HasString() ) + rxTitle->SetString( rAutoTitle ); + if( rxTitle->HasString() ) + rxTitle->UpdateText(pDefText); + else + rxTitle.reset(); + } +} + +} // namespace + +// Data series ================================================================ + +void XclImpChMarkerFormat::ReadChMarkerFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maLineColor >> maData.maFillColor; + maData.mnMarkerType = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + const XclImpPalette& rPal = rRoot.GetPalette(); + maData.maLineColor = rPal.GetColor( rStrm.ReaduInt16() ); + maData.maFillColor = rPal.GetColor( rStrm.ReaduInt16() ); + // marker size + maData.mnMarkerSize = rStrm.ReaduInt32(); + } +} + +void XclImpChMarkerFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx, sal_Int16 nLineWeight ) const +{ + if( IsAuto() ) + { + XclChMarkerFormat aMarkerFmt; + // line and fill color of the symbol are equal to series line color + //TODO: Excel sets no fill color for specific symbols (e.g. cross) + aMarkerFmt.maLineColor = aMarkerFmt.maFillColor = rRoot.GetSeriesLineAutoColor( nFormatIdx ); + switch( nLineWeight ) + { + case EXC_CHLINEFORMAT_HAIR: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_HAIRSIZE; break; + case EXC_CHLINEFORMAT_SINGLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_SINGLESIZE; break; + case EXC_CHLINEFORMAT_DOUBLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_DOUBLESIZE; break; + case EXC_CHLINEFORMAT_TRIPLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_TRIPLESIZE; break; + default: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_SINGLESIZE; + } + aMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + XclChPropSetHelper::WriteMarkerProperties( rPropSet, aMarkerFmt ); + } + else + { + XclChPropSetHelper::WriteMarkerProperties( rPropSet, maData ); + } +} + +void XclImpChMarkerFormat::ConvertColor( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) const +{ + Color aLineColor = IsAuto() ? rRoot.GetSeriesLineAutoColor( nFormatIdx ) : maData.maFillColor; + rPropSet.SetColorProperty( EXC_CHPROP_COLOR, aLineColor ); +} + +XclImpChPieFormat::XclImpChPieFormat() : + mnPieDist( 0 ) +{ +} + +void XclImpChPieFormat::ReadChPieFormat( XclImpStream& rStrm ) +{ + mnPieDist = rStrm.ReaduInt16(); +} + +void XclImpChPieFormat::Convert( ScfPropertySet& rPropSet ) const +{ + double fApiDist = ::std::min< double >( mnPieDist / 100.0, 1.0 ); + rPropSet.SetProperty( EXC_CHPROP_OFFSET, fApiDist ); +} + +XclImpChSeriesFormat::XclImpChSeriesFormat() : + mnFlags( 0 ) +{ +} + +void XclImpChSeriesFormat::ReadChSeriesFormat( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); +} + +void XclImpCh3dDataFormat::ReadCh3dDataFormat( XclImpStream& rStrm ) +{ + maData.mnBase = rStrm.ReaduInt8(); + maData.mnTop = rStrm.ReaduInt8(); +} + +void XclImpCh3dDataFormat::Convert( ScfPropertySet& rPropSet ) const +{ + using namespace ::com::sun::star::chart2::DataPointGeometry3D; + sal_Int32 nApiType = (maData.mnBase == EXC_CH3DDATAFORMAT_RECT) ? + ((maData.mnTop == EXC_CH3DDATAFORMAT_STRAIGHT) ? CUBOID : PYRAMID) : + ((maData.mnTop == EXC_CH3DDATAFORMAT_STRAIGHT) ? CYLINDER : CONE); + rPropSet.SetProperty( EXC_CHPROP_GEOMETRY3D, nApiType ); +} + +XclImpChAttachedLabel::XclImpChAttachedLabel( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + mnFlags( 0 ) +{ +} + +void XclImpChAttachedLabel::ReadChAttachedLabel( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); +} + +XclImpChTextRef XclImpChAttachedLabel::CreateDataLabel( const XclImpChText* pParent ) const +{ + const sal_uInt16 EXC_CHATTLABEL_SHOWANYVALUE = EXC_CHATTLABEL_SHOWVALUE; + const sal_uInt16 EXC_CHATTLABEL_SHOWANYPERCENT = EXC_CHATTLABEL_SHOWPERCENT | EXC_CHATTLABEL_SHOWCATEGPERC; + const sal_uInt16 EXC_CHATTLABEL_SHOWANYCATEG = EXC_CHATTLABEL_SHOWCATEG | EXC_CHATTLABEL_SHOWCATEGPERC; + + XclImpChTextRef xLabel; + if ( pParent ) + xLabel = std::make_shared<XclImpChText>( *pParent ); + else + xLabel = std::make_shared<XclImpChText>( GetChRoot() ); + xLabel->UpdateDataLabel( + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYCATEG ), + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYVALUE ), + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYPERCENT ) ); + return xLabel; +} + +XclImpChDataFormat::XclImpChDataFormat( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChDataFormat::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.maPointPos.mnPointIdx = rStrm.ReaduInt16(); + maData.maPointPos.mnSeriesIdx = rStrm.ReaduInt16(); + maData.mnFormatIdx = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChDataFormat::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHMARKERFORMAT: + mxMarkerFmt = std::make_shared<XclImpChMarkerFormat>(); + mxMarkerFmt->ReadChMarkerFormat( rStrm ); + break; + case EXC_ID_CHPIEFORMAT: + mxPieFmt = std::make_shared<XclImpChPieFormat>(); + mxPieFmt->ReadChPieFormat( rStrm ); + break; + case EXC_ID_CHSERIESFORMAT: + mxSeriesFmt = std::make_shared<XclImpChSeriesFormat>(); + mxSeriesFmt->ReadChSeriesFormat( rStrm ); + break; + case EXC_ID_CH3DDATAFORMAT: + mx3dDataFmt = std::make_shared<XclImpCh3dDataFormat>(); + mx3dDataFmt->ReadCh3dDataFormat( rStrm ); + break; + case EXC_ID_CHATTACHEDLABEL: + mxAttLabel = std::make_shared<XclImpChAttachedLabel>( GetChRoot() ); + mxAttLabel->ReadChAttachedLabel( rStrm ); + break; + default: + XclImpChFrameBase::ReadSubRecord( rStrm ); + } +} + +void XclImpChDataFormat::SetPointPos( const XclChDataPointPos& rPointPos, sal_uInt16 nFormatIdx ) +{ + maData.maPointPos = rPointPos; + maData.mnFormatIdx = nFormatIdx; +} + +void XclImpChDataFormat::UpdateGroupFormat( const XclChExtTypeInfo& rTypeInfo ) +{ + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); +} + +void XclImpChDataFormat::UpdateSeriesFormat( const XclChExtTypeInfo& rTypeInfo, const XclImpChDataFormat* pGroupFmt ) +{ + // update missing formats from passed chart type group format + if( pGroupFmt ) + { + if( !mxLineFmt ) + mxLineFmt = pGroupFmt->mxLineFmt; + if( !mxAreaFmt && !mxEscherFmt ) + { + mxAreaFmt = pGroupFmt->mxAreaFmt; + mxEscherFmt = pGroupFmt->mxEscherFmt; + } + if( !mxMarkerFmt ) + mxMarkerFmt = pGroupFmt->mxMarkerFmt; + if( !mxPieFmt ) + mxPieFmt = pGroupFmt->mxPieFmt; + if( !mxSeriesFmt ) + mxSeriesFmt = pGroupFmt->mxSeriesFmt; + if( !mx3dDataFmt ) + mx3dDataFmt = pGroupFmt->mx3dDataFmt; + if( !mxAttLabel ) + mxAttLabel = pGroupFmt->mxAttLabel; + } + + /* Create missing but required formats. Existing line, area, and marker + format objects are needed to create automatic series formatting. */ + if( !mxLineFmt ) + mxLineFmt = new XclImpChLineFormat(); + if( !mxAreaFmt && !mxEscherFmt ) + mxAreaFmt = std::make_shared<XclImpChAreaFormat>(); + if( !mxMarkerFmt ) + mxMarkerFmt = std::make_shared<XclImpChMarkerFormat>(); + + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); + // update data label + UpdateDataLabel( pGroupFmt ); +} + +void XclImpChDataFormat::UpdatePointFormat( const XclChExtTypeInfo& rTypeInfo, const XclImpChDataFormat* pSeriesFmt ) +{ + // remove formats if they are automatic in this and in the passed series format + if( pSeriesFmt ) + { + if( IsAutoLine() && pSeriesFmt->IsAutoLine() ) + mxLineFmt.clear(); + if( IsAutoArea() && pSeriesFmt->IsAutoArea() ) + mxAreaFmt.reset(); + if( IsAutoMarker() && pSeriesFmt->IsAutoMarker() ) + mxMarkerFmt.reset(); + mxSeriesFmt.reset(); + } + + // Excel ignores 3D bar format for single data points + mx3dDataFmt.reset(); + // remove point line formats for linear chart types, TODO: implement in OOChart + if( !rTypeInfo.IsSeriesFrameFormat() ) + mxLineFmt.clear(); + + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); + // update data label + UpdateDataLabel( pSeriesFmt ); +} + +void XclImpChDataFormat::UpdateTrendLineFormat() +{ + if( !mxLineFmt ) + mxLineFmt = new XclImpChLineFormat(); + mxAreaFmt.reset(); + mxEscherFmt.reset(); + mxMarkerFmt.reset(); + mxPieFmt.reset(); + mxSeriesFmt.reset(); + mx3dDataFmt.reset(); + mxAttLabel.reset(); + // update data label + UpdateDataLabel( nullptr ); +} + +void XclImpChDataFormat::Convert( ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo, const ScfPropertySet* pGlobalPropSet ) const +{ + /* Line and area format. + #i71810# If the data points are filled with bitmaps, textures, or + patterns, then only bar charts will use the CHPICFORMAT record to + determine stacking/stretching mode. All other chart types ignore this + record and always use the property 'fill-type' from the DFF property + set (stretched for bitmaps, and stacked for textures and patterns). */ + bool bUsePicFmt = rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR; + ConvertFrameBase( GetChRoot(), rPropSet, rTypeInfo.GetSeriesObjectType(), maData.mnFormatIdx, bUsePicFmt ); + + // #i83151# only hair lines in 3D charts with filled data points + if( rTypeInfo.mb3dChart && rTypeInfo.IsSeriesFrameFormat() && mxLineFmt && mxLineFmt->HasLine() ) + rPropSet.SetProperty< sal_Int32 >( "BorderWidth", 0 ); + + // other formatting + if( mxMarkerFmt ) + mxMarkerFmt->Convert( GetChRoot(), rPropSet, maData.mnFormatIdx, GetLineWeight() ); + if( mxPieFmt ) + mxPieFmt->Convert( rPropSet ); + if( mx3dDataFmt ) + mx3dDataFmt->Convert( rPropSet ); + if( mxLabel ) + mxLabel->ConvertDataLabel( rPropSet, rTypeInfo, pGlobalPropSet ); + + // 3D settings + rPropSet.SetProperty< sal_Int16 >( EXC_CHPROP_PERCENTDIAGONAL, 0 ); + + /* Special case: set marker color as line color, if series line is not + visible. This makes the color visible in the marker area. + TODO: remove this if OOChart supports own colors in markers. */ + if( !rTypeInfo.IsSeriesFrameFormat() && !HasLine() && mxMarkerFmt ) + mxMarkerFmt->ConvertColor( GetChRoot(), rPropSet, maData.mnFormatIdx ); +} + +void XclImpChDataFormat::ConvertLine( ScfPropertySet& rPropSet, XclChObjectType eObjType ) const +{ + ConvertLineBase( GetChRoot(), rPropSet, eObjType ); +} + +void XclImpChDataFormat::ConvertArea( ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) const +{ + ConvertAreaBase( GetChRoot(), rPropSet, EXC_CHOBJTYPE_FILLEDSERIES, nFormatIdx ); +} + +void XclImpChDataFormat::RemoveUnusedFormats( const XclChExtTypeInfo& rTypeInfo ) +{ + // data point marker only in linear 2D charts + if( rTypeInfo.IsSeriesFrameFormat() ) + mxMarkerFmt.reset(); + // pie format only in pie/donut charts + if( rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_PIE ) + mxPieFmt.reset(); + // 3D format only in 3D bar charts + if( !rTypeInfo.mb3dChart || (rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_BAR) ) + mx3dDataFmt.reset(); +} + +void XclImpChDataFormat::UpdateDataLabel( const XclImpChDataFormat* pParentFmt ) +{ + /* CHTEXT groups linked to data labels override existing CHATTACHEDLABEL + records. Only if there is a CHATTACHEDLABEL record without a CHTEXT + group, the contents of the CHATTACHEDLABEL record are used. In this + case a new CHTEXT group is created and filled with the settings from + the CHATTACHEDLABEL record. */ + const XclImpChText* pDefText = nullptr; + if (pParentFmt) + pDefText = pParentFmt->GetDataLabel(); + if (!pDefText) + pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_DATALABEL ); + if (mxLabel) + mxLabel->UpdateText(pDefText); + else if (mxAttLabel) + mxLabel = mxAttLabel->CreateDataLabel( pDefText ); +} + +XclImpChSerTrendLine::XclImpChSerTrendLine( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChSerTrendLine::ReadChSerTrendLine( XclImpStream& rStrm ) +{ + maData.mnLineType = rStrm.ReaduInt8(); + maData.mnOrder = rStrm.ReaduInt8(); + maData.mfIntercept = rStrm.ReadDouble(); + maData.mnShowEquation = rStrm.ReaduInt8(); + maData.mnShowRSquared = rStrm.ReaduInt8(); + maData.mfForecastFor = rStrm.ReadDouble(); + maData.mfForecastBack = rStrm.ReadDouble(); +} + +Reference< XRegressionCurve > XclImpChSerTrendLine::CreateRegressionCurve() const +{ + // trend line type + Reference< XRegressionCurve > xRegCurve; + switch( maData.mnLineType ) + { + case EXC_CHSERTREND_POLYNOMIAL: + if( maData.mnOrder == 1 ) + { + xRegCurve = LinearRegressionCurve::create( comphelper::getProcessComponentContext() ); + } else { + xRegCurve = PolynomialRegressionCurve::create( comphelper::getProcessComponentContext() ); + } + break; + case EXC_CHSERTREND_EXPONENTIAL: + xRegCurve = ExponentialRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_LOGARITHMIC: + xRegCurve = LogarithmicRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_POWER: + xRegCurve = PotentialRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_MOVING_AVG: + xRegCurve = MovingAverageRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + } + + // trend line formatting + if( xRegCurve.is() && mxDataFmt ) + { + ScfPropertySet aPropSet( xRegCurve ); + mxDataFmt->ConvertLine( aPropSet, EXC_CHOBJTYPE_TRENDLINE ); + + aPropSet.SetProperty(EXC_CHPROP_CURVENAME, maTrendLineName); + aPropSet.SetProperty(EXC_CHPROP_POLYNOMIAL_DEGREE, static_cast<sal_Int32> (maData.mnOrder) ); + aPropSet.SetProperty(EXC_CHPROP_MOVING_AVERAGE_PERIOD, static_cast<sal_Int32> (maData.mnOrder) ); + aPropSet.SetProperty(EXC_CHPROP_EXTRAPOLATE_FORWARD, maData.mfForecastFor); + aPropSet.SetProperty(EXC_CHPROP_EXTRAPOLATE_BACKWARD, maData.mfForecastBack); + + bool bForceIntercept = std::isfinite(maData.mfIntercept); + aPropSet.SetProperty(EXC_CHPROP_FORCE_INTERCEPT, bForceIntercept); + if (bForceIntercept) + { + aPropSet.SetProperty(EXC_CHPROP_INTERCEPT_VALUE, maData.mfIntercept); + } + + // #i83100# show equation and correlation coefficient + ScfPropertySet aLabelProp( xRegCurve->getEquationProperties() ); + aLabelProp.SetBoolProperty( EXC_CHPROP_SHOWEQUATION, maData.mnShowEquation != 0 ); + aLabelProp.SetBoolProperty( EXC_CHPROP_SHOWCORRELATION, maData.mnShowRSquared != 0 ); + + // #i83100# formatting of the equation text box + if (const XclImpChText* pLabel = mxDataFmt->GetDataLabel()) + { + pLabel->ConvertFont( aLabelProp ); + pLabel->ConvertFrame( aLabelProp ); + pLabel->ConvertNumFmt( aLabelProp, false ); + } + } + + return xRegCurve; +} + +XclImpChSerErrorBar::XclImpChSerErrorBar( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChSerErrorBar::ReadChSerErrorBar( XclImpStream& rStrm ) +{ + maData.mnBarType = rStrm.ReaduInt8(); + maData.mnSourceType = rStrm.ReaduInt8(); + maData.mnLineEnd = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + maData.mfValue = rStrm.ReadDouble(); + maData.mnValueCount = rStrm.ReaduInt16(); +} + +void XclImpChSerErrorBar::SetSeriesData( XclImpChSourceLinkRef const & xValueLink, XclImpChDataFormatRef const & xDataFmt ) +{ + mxValueLink = xValueLink; + mxDataFmt = xDataFmt; +} + +Reference< XLabeledDataSequence > XclImpChSerErrorBar::CreateValueSequence() const +{ + return lclCreateLabeledDataSequence( mxValueLink, XclChartHelper::GetErrorBarValuesRole( maData.mnBarType ) ); +} + +Reference< XPropertySet > XclImpChSerErrorBar::CreateErrorBar( const XclImpChSerErrorBar* pPosBar, const XclImpChSerErrorBar* pNegBar ) +{ + Reference< XPropertySet > xErrorBar; + + if( const XclImpChSerErrorBar* pPrimaryBar = pPosBar ? pPosBar : pNegBar ) + { + xErrorBar.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_ERRORBAR ), UNO_QUERY ); + ScfPropertySet aBarProp( xErrorBar ); + + // plus/minus bars visible? + aBarProp.SetBoolProperty( EXC_CHPROP_SHOWPOSITIVEERROR, pPosBar != nullptr ); + aBarProp.SetBoolProperty( EXC_CHPROP_SHOWNEGATIVEERROR, pNegBar != nullptr ); + + // type of displayed error + switch( pPrimaryBar->maData.mnSourceType ) + { + case EXC_CHSERERR_PERCENT: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::RELATIVE ); + aBarProp.SetProperty( EXC_CHPROP_POSITIVEERROR, pPrimaryBar->maData.mfValue ); + aBarProp.SetProperty( EXC_CHPROP_NEGATIVEERROR, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_FIXED: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::ABSOLUTE ); + aBarProp.SetProperty( EXC_CHPROP_POSITIVEERROR, pPrimaryBar->maData.mfValue ); + aBarProp.SetProperty( EXC_CHPROP_NEGATIVEERROR, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_STDDEV: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::STANDARD_DEVIATION ); + aBarProp.SetProperty( EXC_CHPROP_WEIGHT, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_STDERR: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::STANDARD_ERROR ); + break; + case EXC_CHSERERR_CUSTOM: + { + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::FROM_DATA ); + // attach data sequences to error bar + Reference< XDataSink > xDataSink( xErrorBar, UNO_QUERY ); + if( xDataSink.is() ) + { + // create vector of all value sequences + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + // add positive values + if( pPosBar ) + { + Reference< XLabeledDataSequence > xValueSeq = pPosBar->CreateValueSequence(); + if( xValueSeq.is() ) + aLabeledSeqVec.push_back( xValueSeq ); + } + // add negative values + if( pNegBar ) + { + Reference< XLabeledDataSequence > xValueSeq = pNegBar->CreateValueSequence(); + if( xValueSeq.is() ) + aLabeledSeqVec.push_back( xValueSeq ); + } + // attach labeled data sequences to series + if( aLabeledSeqVec.empty() ) + xErrorBar.clear(); + else + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + } + } + break; + default: + xErrorBar.clear(); + } + + // error bar formatting + if( pPrimaryBar->mxDataFmt && xErrorBar.is() ) + pPrimaryBar->mxDataFmt->ConvertLine( aBarProp, EXC_CHOBJTYPE_ERRORBAR ); + } + + return xErrorBar; +} + +XclImpChSeries::XclImpChSeries( const XclImpChRoot& rRoot, sal_uInt16 nSeriesIdx ) : + XclImpChRoot( rRoot ), + mnGroupIdx( EXC_CHSERGROUP_NONE ), + mnSeriesIdx( nSeriesIdx ), + mnParentIdx( EXC_CHSERIES_INVALID ), + mbLabelDeleted( false ) +{ +} + +void XclImpChSeries::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnCategType = rStrm.ReaduInt16(); + maData.mnValueType = rStrm.ReaduInt16(); + maData.mnCategCount = rStrm.ReaduInt16(); + maData.mnValueCount = rStrm.ReaduInt16(); + if( GetBiff() == EXC_BIFF8 ) + { + maData.mnBubbleType = rStrm.ReaduInt16(); + maData.mnBubbleCount = rStrm.ReaduInt16(); + } +} + +void XclImpChSeries::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHSOURCELINK: + ReadChSourceLink( rStrm ); + break; + case EXC_ID_CHDATAFORMAT: + ReadChDataFormat( rStrm ); + break; + case EXC_ID_CHSERGROUP: + mnGroupIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHSERPARENT: + ReadChSerParent( rStrm ); + break; + case EXC_ID_CHSERTRENDLINE: + ReadChSerTrendLine( rStrm ); + break; + case EXC_ID_CHSERERRORBAR: + ReadChSerErrorBar( rStrm ); + break; + case EXC_ID_CHLEGENDEXCEPTION: + ReadChLegendException( rStrm ); + break; + } +} + +void XclImpChSeries::SetDataFormat( const XclImpChDataFormatRef& xDataFmt ) +{ + if (!xDataFmt) + return; + + sal_uInt16 nPointIdx = xDataFmt->GetPointPos().mnPointIdx; + if (nPointIdx == EXC_CHDATAFORMAT_ALLPOINTS) + { + if (mxSeriesFmt) + // Don't overwrite the existing format. + return; + + mxSeriesFmt = xDataFmt; + if (HasParentSeries()) + return; + + XclImpChTypeGroupRef pTypeGroup = GetChartData().GetTypeGroup(mnGroupIdx); + if (pTypeGroup) + pTypeGroup->SetUsedFormatIndex(xDataFmt->GetFormatIdx()); + + return; + } + + if (nPointIdx >= EXC_CHDATAFORMAT_MAXPOINTCOUNT) + // Above the max point count. Bail out. + return; + + XclImpChDataFormatMap::iterator itr = maPointFmts.lower_bound(nPointIdx); + if (itr == maPointFmts.end() || maPointFmts.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert it. + itr = maPointFmts.insert(itr, XclImpChDataFormatMap::value_type(nPointIdx, xDataFmt)); + } +} + +void XclImpChSeries::SetDataLabel( const XclImpChTextRef& xLabel ) +{ + if (!xLabel) + return; + + sal_uInt16 nPointIdx = xLabel->GetPointPos().mnPointIdx; + if ((nPointIdx != EXC_CHDATAFORMAT_ALLPOINTS) && (nPointIdx >= EXC_CHDATAFORMAT_MAXPOINTCOUNT)) + // Above the maximum allowed data points. Bail out. + return; + + XclImpChTextMap::iterator itr = maLabels.lower_bound(nPointIdx); + if (itr == maLabels.end() || maLabels.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert it. + itr = maLabels.insert(itr, XclImpChTextMap::value_type(nPointIdx, xLabel)); + } +} + +void XclImpChSeries::AddChildSeries( const XclImpChSeries& rSeries ) +{ + OSL_ENSURE( !HasParentSeries(), "XclImpChSeries::AddChildSeries - not allowed for child series" ); + if (&rSeries == this) + { + SAL_WARN("sc.filter", "self add attempt"); + return; + } + + /* In Excel, trend lines and error bars are stored as own series. In Calc, + these are properties of the parent series. This function adds the + settings of the passed series to this series. */ + maTrendLines.insert( maTrendLines.end(), rSeries.maTrendLines.begin(), rSeries.maTrendLines.end() ); + for (auto const& it : rSeries.m_ErrorBars) + { + m_ErrorBars.insert(std::make_pair(it.first, std::make_unique<XclImpChSerErrorBar>(*it.second))); + } +} + +void XclImpChSeries::FinalizeDataFormats() +{ + if( HasParentSeries() ) + { + // *** series is a child series, e.g. trend line or error bar *** + + // create missing series format + if( !mxSeriesFmt ) + mxSeriesFmt = CreateDataFormat( EXC_CHDATAFORMAT_ALLPOINTS, 0 ); + + if( mxSeriesFmt ) + { + // #i83100# set text label format, e.g. for trend line equations + XclImpChTextRef xLabel; + XclImpChTextMap::iterator itr = maLabels.find(EXC_CHDATAFORMAT_ALLPOINTS); + if (itr != maLabels.end()) + xLabel = itr->second; + mxSeriesFmt->SetDataLabel(xLabel); + // create missing automatic formats + mxSeriesFmt->UpdateTrendLineFormat(); + } + + // copy series formatting to child objects + for (auto const& trendLine : maTrendLines) + { + trendLine->SetDataFormat(mxSeriesFmt); + if (mxTitleLink && mxTitleLink->HasString()) + { + trendLine->SetTrendlineName(mxTitleLink->GetString()); + } + } + for (auto const& it : m_ErrorBars) + { + it.second->SetSeriesData( mxValueLink, mxSeriesFmt ); + } + } + else if( XclImpChTypeGroup* pTypeGroup = GetChartData().GetTypeGroup( mnGroupIdx ).get() ) + { + // *** series is a regular data series *** + + // create missing series format + if( !mxSeriesFmt ) + { + // #i51639# use a new unused format index to create series default format + sal_uInt16 nFormatIdx = pTypeGroup->PopUnusedFormatIndex(); + mxSeriesFmt = CreateDataFormat( EXC_CHDATAFORMAT_ALLPOINTS, nFormatIdx ); + } + + // set text labels to data formats + for (auto const& label : maLabels) + { + sal_uInt16 nPointIdx = label.first; + if (nPointIdx == EXC_CHDATAFORMAT_ALLPOINTS) + { + if (!mxSeriesFmt) + mxSeriesFmt = CreateDataFormat(nPointIdx, EXC_CHDATAFORMAT_DEFAULT); + mxSeriesFmt->SetDataLabel(label.second); + } + else if (nPointIdx < EXC_CHDATAFORMAT_MAXPOINTCOUNT) + { + XclImpChDataFormatRef p; + XclImpChDataFormatMap::iterator itr = maPointFmts.lower_bound(nPointIdx); + if (itr == maPointFmts.end() || maPointFmts.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert + // a new one. + p = CreateDataFormat(nPointIdx, EXC_CHDATAFORMAT_DEFAULT); + itr = maPointFmts.insert( + itr, XclImpChDataFormatMap::value_type(nPointIdx, p)); + } + else + p = itr->second; + p->SetDataLabel(label.second); + } + } + + // update series format (copy missing formatting from group default format) + if( mxSeriesFmt ) + mxSeriesFmt->UpdateSeriesFormat( pTypeGroup->GetTypeInfo(), pTypeGroup->GetGroupFormat().get() ); + + // update data point formats (removes unchanged automatic formatting) + for (auto const& pointFormat : maPointFmts) + pointFormat.second->UpdatePointFormat( pTypeGroup->GetTypeInfo(), mxSeriesFmt.get() ); + } +} + +namespace { + +/** Returns the property set of the specified data point. */ +ScfPropertySet lclGetPointPropSet( Reference< XDataSeries > const & xDataSeries, sal_uInt16 nPointIdx ) +{ + ScfPropertySet aPropSet; + try + { + aPropSet.Set( xDataSeries->getDataPointByIndex( static_cast< sal_Int32 >( nPointIdx ) ) ); + } + catch( Exception& ) + { + OSL_FAIL( "lclGetPointPropSet - no data point property set" ); + } + return aPropSet; +} + +} // namespace + +Reference< XLabeledDataSequence > XclImpChSeries::CreateValueSequence( const OUString& rValueRole ) const +{ + return lclCreateLabeledDataSequence( mxValueLink, rValueRole, mxTitleLink.get() ); +} + +Reference< XLabeledDataSequence > XclImpChSeries::CreateCategSequence( const OUString& rCategRole ) const +{ + return lclCreateLabeledDataSequence( mxCategLink, rCategRole ); +} + +Reference< XDataSeries > XclImpChSeries::CreateDataSeries() const +{ + Reference< XDataSeries > xDataSeries; + if( const XclImpChTypeGroup* pTypeGroup = GetChartData().GetTypeGroup( mnGroupIdx ).get() ) + { + const XclChExtTypeInfo& rTypeInfo = pTypeGroup->GetTypeInfo(); + + // create the data series object + xDataSeries.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_DATASERIES ), UNO_QUERY ); + + // attach data and title sequences to series + Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY ); + if( xDataSink.is() ) + { + // create vector of all value sequences + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + // add Y values + Reference< XLabeledDataSequence > xYValueSeq = + CreateValueSequence( EXC_CHPROP_ROLE_YVALUES ); + if( xYValueSeq.is() ) + aLabeledSeqVec.push_back( xYValueSeq ); + // add X values + if( !rTypeInfo.mbCategoryAxis ) + { + Reference< XLabeledDataSequence > xXValueSeq = + CreateCategSequence( EXC_CHPROP_ROLE_XVALUES ); + if( xXValueSeq.is() ) + aLabeledSeqVec.push_back( xXValueSeq ); + // add size values of bubble charts + if( rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ) + { + Reference< XLabeledDataSequence > xSizeValueSeq = + lclCreateLabeledDataSequence( mxBubbleLink, EXC_CHPROP_ROLE_SIZEVALUES, mxTitleLink.get() ); + if( xSizeValueSeq.is() ) + aLabeledSeqVec.push_back( xSizeValueSeq ); + } + } + // attach labeled data sequences to series + if( !aLabeledSeqVec.empty() ) + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + } + + // series formatting + ScfPropertySet aSeriesProp( xDataSeries ); + if( mxSeriesFmt ) + mxSeriesFmt->Convert( aSeriesProp, rTypeInfo ); + + if (mbLabelDeleted) + aSeriesProp.SetProperty(EXC_CHPROP_SHOWLEGENDENTRY, false); + + // trend lines + ConvertTrendLines( xDataSeries ); + + // error bars + Reference< XPropertySet > xErrorBarX = CreateErrorBar( EXC_CHSERERR_XPLUS, EXC_CHSERERR_XMINUS ); + if( xErrorBarX.is() ) + aSeriesProp.SetProperty( EXC_CHPROP_ERRORBARX, xErrorBarX ); + Reference< XPropertySet > xErrorBarY = CreateErrorBar( EXC_CHSERERR_YPLUS, EXC_CHSERERR_YMINUS ); + if( xErrorBarY.is() ) + aSeriesProp.SetProperty( EXC_CHPROP_ERRORBARY, xErrorBarY ); + + // own area formatting for every data point (TODO: varying line color not supported) + bool bVarPointFmt = pTypeGroup->HasVarPointFormat() && rTypeInfo.IsSeriesFrameFormat(); + aSeriesProp.SetBoolProperty( EXC_CHPROP_VARYCOLORSBY, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ); + // #i91271# always set area formatting for every point in pie/doughnut charts + if (mxSeriesFmt && mxValueLink && ((bVarPointFmt && mxSeriesFmt->IsAutoArea()) || (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE))) + { + for( sal_uInt16 nPointIdx = 0, nPointCount = mxValueLink->GetCellCount(); nPointIdx < nPointCount; ++nPointIdx ) + { + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIdx ); + mxSeriesFmt->ConvertArea( aPointProp, bVarPointFmt ? nPointIdx : mnSeriesIdx ); + } + } + + // data point formatting + for (auto const& pointFormat : maPointFmts) + { + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, pointFormat.first ); + pointFormat.second->Convert( aPointProp, rTypeInfo, &aSeriesProp ); + } + } + return xDataSeries; +} + +void XclImpChSeries::FillAllSourceLinks( ::std::vector< ScTokenRef >& rTokens ) const +{ + if( mxValueLink ) + mxValueLink->FillSourceLink( rTokens ); + if( mxCategLink ) + mxCategLink->FillSourceLink( rTokens ); + if( mxTitleLink ) + mxTitleLink->FillSourceLink( rTokens ); + if( mxBubbleLink ) + mxBubbleLink->FillSourceLink( rTokens ); +} + +void XclImpChSeries::ReadChSourceLink( XclImpStream& rStrm ) +{ + XclImpChSourceLinkRef xSrcLink = std::make_shared<XclImpChSourceLink>( GetChRoot() ); + xSrcLink->ReadChSourceLink( rStrm ); + switch( xSrcLink->GetDestType() ) + { + case EXC_CHSRCLINK_TITLE: mxTitleLink = xSrcLink; break; + case EXC_CHSRCLINK_VALUES: mxValueLink = xSrcLink; break; + case EXC_CHSRCLINK_CATEGORY: mxCategLink = xSrcLink; break; + case EXC_CHSRCLINK_BUBBLES: mxBubbleLink = xSrcLink; break; + } +} + +void XclImpChSeries::ReadChDataFormat( XclImpStream& rStrm ) +{ + // #i51639# chart stores all data formats and assigns them later to the series + GetChartData().ReadChDataFormat( rStrm ); +} + +void XclImpChSeries::ReadChSerParent( XclImpStream& rStrm ) +{ + mnParentIdx = rStrm.ReaduInt16(); + // index to parent series is 1-based, convert it to 0-based + if( mnParentIdx > 0 ) + --mnParentIdx; + else + mnParentIdx = EXC_CHSERIES_INVALID; +} + +void XclImpChSeries::ReadChSerTrendLine( XclImpStream& rStrm ) +{ + XclImpChSerTrendLineRef xTrendLine = std::make_shared<XclImpChSerTrendLine>( GetChRoot() ); + xTrendLine->ReadChSerTrendLine( rStrm ); + maTrendLines.push_back( xTrendLine ); +} + +void XclImpChSeries::ReadChSerErrorBar( XclImpStream& rStrm ) +{ + unique_ptr<XclImpChSerErrorBar> pErrorBar(new XclImpChSerErrorBar(GetChRoot())); + pErrorBar->ReadChSerErrorBar(rStrm); + sal_uInt8 nBarType = pErrorBar->GetBarType(); + m_ErrorBars.insert(std::make_pair(nBarType, std::move(pErrorBar))); +} + +XclImpChDataFormatRef XclImpChSeries::CreateDataFormat( sal_uInt16 nPointIdx, sal_uInt16 nFormatIdx ) +{ + XclImpChDataFormatRef xDataFmt = std::make_shared<XclImpChDataFormat>( GetChRoot() ); + xDataFmt->SetPointPos( XclChDataPointPos( mnSeriesIdx, nPointIdx ), nFormatIdx ); + return xDataFmt; +} + +void XclImpChSeries::ConvertTrendLines( Reference< XDataSeries > const & xDataSeries ) const +{ + Reference< XRegressionCurveContainer > xRegCurveCont( xDataSeries, UNO_QUERY ); + if( !xRegCurveCont.is() ) + return; + + for (auto const& trendLine : maTrendLines) + { + try + { + Reference< XRegressionCurve > xRegCurve = trendLine->CreateRegressionCurve(); + if( xRegCurve.is() ) + { + xRegCurveCont->addRegressionCurve( xRegCurve ); + } + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChSeries::ConvertTrendLines - cannot add regression curve" ); + } + } +} + +Reference< XPropertySet > XclImpChSeries::CreateErrorBar( sal_uInt8 nPosBarId, sal_uInt8 nNegBarId ) const +{ + XclImpChSerErrorBarMap::const_iterator itrPosBar = m_ErrorBars.find(nPosBarId); + XclImpChSerErrorBarMap::const_iterator itrNegBar = m_ErrorBars.find(nNegBarId); + XclImpChSerErrorBarMap::const_iterator itrEnd = m_ErrorBars.end(); + if (itrPosBar == itrEnd || itrNegBar == itrEnd) + return Reference<XPropertySet>(); + + return XclImpChSerErrorBar::CreateErrorBar(itrPosBar->second.get(), itrNegBar->second.get()); +} + +void XclImpChSeries::ReadChLegendException(XclImpStream& rStrm) +{ + rStrm.Ignore(2); + sal_uInt16 nFlags = rStrm.ReaduInt16(); + mbLabelDeleted = (nFlags & EXC_CHLEGENDEXCEPTION_DELETED); +} + +// Chart type groups ========================================================== + +XclImpChType::XclImpChType( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + mnRecId( EXC_ID_CHUNKNOWN ), + maTypeInfo( rRoot.GetChartTypeInfo( EXC_CHTYPEID_UNKNOWN ) ) +{ +} + +void XclImpChType::ReadChType( XclImpStream& rStrm ) +{ + sal_uInt16 nRecId = rStrm.GetRecId(); + bool bKnownType = true; + + switch( nRecId ) + { + case EXC_ID_CHBAR: + maData.mnOverlap = rStrm.ReadInt16(); + maData.mnGap = rStrm.ReadInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + break; + + case EXC_ID_CHLINE: + case EXC_ID_CHAREA: + case EXC_ID_CHRADARLINE: + case EXC_ID_CHRADARAREA: + maData.mnFlags = rStrm.ReaduInt16(); + break; + + case EXC_ID_CHPIE: + maData.mnRotation = rStrm.ReaduInt16(); + maData.mnPieHole = rStrm.ReaduInt16(); + if( GetBiff() == EXC_BIFF8 ) + maData.mnFlags = rStrm.ReaduInt16(); + else + maData.mnFlags = 0; + break; + + case EXC_ID_CHPIEEXT: + maData.mnRotation = 0; + maData.mnPieHole = 0; + maData.mnFlags = 0; + break; + + case EXC_ID_CHSCATTER: + if( GetBiff() == EXC_BIFF8 ) + { + maData.mnBubbleSize = rStrm.ReaduInt16(); + maData.mnBubbleType = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + } + else + maData.mnFlags = 0; + break; + + case EXC_ID_CHSURFACE: + maData.mnFlags = rStrm.ReaduInt16(); + break; + + default: + bKnownType = false; + } + + if( bKnownType ) + mnRecId = nRecId; +} + +void XclImpChType::Finalize( bool bStockChart ) +{ + switch( mnRecId ) + { + case EXC_ID_CHLINE: + maTypeInfo = GetChartTypeInfo( bStockChart ? + EXC_CHTYPEID_STOCK : EXC_CHTYPEID_LINE ); + break; + case EXC_ID_CHBAR: + maTypeInfo = GetChartTypeInfo( ::get_flagvalue( + maData.mnFlags, EXC_CHBAR_HORIZONTAL, + EXC_CHTYPEID_HORBAR, EXC_CHTYPEID_BAR ) ); + break; + case EXC_ID_CHPIE: + maTypeInfo = GetChartTypeInfo( (maData.mnPieHole > 0) ? + EXC_CHTYPEID_DONUT : EXC_CHTYPEID_PIE ); + break; + case EXC_ID_CHSCATTER: + maTypeInfo = GetChartTypeInfo( ::get_flagvalue( + maData.mnFlags, EXC_CHSCATTER_BUBBLES, + EXC_CHTYPEID_BUBBLES, EXC_CHTYPEID_SCATTER ) ); + break; + default: + maTypeInfo = GetChartTypeInfo( mnRecId ); + } + + switch( maTypeInfo.meTypeId ) + { + case EXC_CHTYPEID_PIEEXT: + case EXC_CHTYPEID_BUBBLES: + case EXC_CHTYPEID_SURFACE: + case EXC_CHTYPEID_UNKNOWN: + GetTracer().TraceChartUnKnownType(); + break; + default:; + } +} + +bool XclImpChType::IsStacked() const +{ + bool bStacked = false; + if( maTypeInfo.mbSupportsStacking ) switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + bStacked = + ::get_flag( maData.mnFlags, EXC_CHLINE_STACKED ) && + !::get_flag( maData.mnFlags, EXC_CHLINE_PERCENT ); + break; + case EXC_CHTYPECATEG_BAR: + bStacked = + ::get_flag( maData.mnFlags, EXC_CHBAR_STACKED ) && + !::get_flag( maData.mnFlags, EXC_CHBAR_PERCENT ); + break; + default:; + } + return bStacked; +} + +bool XclImpChType::IsPercent() const +{ + bool bPercent = false; + if( maTypeInfo.mbSupportsStacking ) switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + bPercent = + ::get_flag( maData.mnFlags, EXC_CHLINE_STACKED ) && + ::get_flag( maData.mnFlags, EXC_CHLINE_PERCENT ); + break; + case EXC_CHTYPECATEG_BAR: + bPercent = + ::get_flag( maData.mnFlags, EXC_CHBAR_STACKED ) && + ::get_flag( maData.mnFlags, EXC_CHBAR_PERCENT ); + break; + default:; + } + return bPercent; +} + +bool XclImpChType::HasCategoryLabels() const +{ + // radar charts disable category labels in chart type, not in CHTICK of X axis + return (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_RADAR) || ::get_flag( maData.mnFlags, EXC_CHRADAR_AXISLABELS ); +} + +Reference< XCoordinateSystem > XclImpChType::CreateCoordSystem( bool b3dChart ) const +{ + // create the coordinate system object + Reference< css::uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + Reference< XCoordinateSystem > xCoordSystem; + if( maTypeInfo.mbPolarCoordSystem ) + { + if( b3dChart ) + xCoordSystem = css::chart2::PolarCoordinateSystem3d::create(xContext); + else + xCoordSystem = css::chart2::PolarCoordinateSystem2d::create(xContext); + } + else + { + if( b3dChart ) + xCoordSystem = css::chart2::CartesianCoordinateSystem3d::create(xContext); + else + xCoordSystem = css::chart2::CartesianCoordinateSystem2d::create(xContext); + } + + // swap X and Y axis + if( maTypeInfo.mbSwappedAxesSet ) + { + ScfPropertySet aCoordSysProp( xCoordSystem ); + aCoordSysProp.SetBoolProperty( EXC_CHPROP_SWAPXANDYAXIS, true ); + } + + return xCoordSystem; +} + +Reference< XChartType > XclImpChType::CreateChartType( Reference< XDiagram > const & xDiagram, bool b3dChart ) const +{ + OUString aService = OUString::createFromAscii( maTypeInfo.mpcServiceName ); + Reference< XChartType > xChartType( ScfApiHelper::CreateInstance( aService ), UNO_QUERY ); + + // additional properties + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_BAR: + { + ScfPropertySet aTypeProp( xChartType ); + Sequence< sal_Int32 > aInt32Seq{ -maData.mnOverlap, -maData.mnOverlap }; + aTypeProp.SetProperty( EXC_CHPROP_OVERLAPSEQ, aInt32Seq ); + aInt32Seq = { maData.mnGap, maData.mnGap }; + aTypeProp.SetProperty( EXC_CHPROP_GAPWIDTHSEQ, aInt32Seq ); + } + break; + case EXC_CHTYPECATEG_PIE: + { + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetBoolProperty( EXC_CHPROP_USERINGS, maTypeInfo.meTypeId == EXC_CHTYPEID_DONUT ); + /* #i85166# starting angle of first pie slice. 3D pie charts use Y + rotation setting in view3D element. Of-pie charts do not + support pie rotation. */ + if( !b3dChart && (maTypeInfo.meTypeId != EXC_CHTYPEID_PIEEXT) ) + { + ScfPropertySet aDiaProp( xDiagram ); + XclImpChRoot::ConvertPieRotation( aDiaProp, maData.mnRotation ); + } + } + break; + default:; + } + + return xChartType; +} + +void XclImpChChart3d::ReadChChart3d( XclImpStream& rStrm ) +{ + maData.mnRotation = rStrm.ReaduInt16(); + maData.mnElevation = rStrm.ReadInt16(); + maData.mnEyeDist = rStrm.ReaduInt16(); + maData.mnRelHeight = rStrm.ReaduInt16(); + maData.mnRelDepth = rStrm.ReaduInt16(); + maData.mnDepthGap = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChChart3d::Convert( ScfPropertySet& rPropSet, bool b3dWallChart ) const +{ + namespace cssd = ::com::sun::star::drawing; + +// #i104057# do not assert this, written by broken external generators +// OSL_ENSURE( ::get_flag( maData.mnFlags, EXC_CHCHART3D_HASWALLS ) == b3dWallChart, "XclImpChChart3d::Convert - wrong wall flag" ); + + sal_Int32 nRotationY = 0; + sal_Int32 nRotationX = 0; + sal_Int32 nPerspective = 15; + bool bRightAngled = false; + cssd::ProjectionMode eProjMode = cssd::ProjectionMode_PERSPECTIVE; + Color aAmbientColor, aLightColor; + + if( b3dWallChart ) + { + // Y rotation (Excel [0..359], Chart2 [-179,180]) + nRotationY = NormAngle180<sal_Int32>(maData.mnRotation); + // X rotation a.k.a. elevation (Excel [-90..90], Chart2 [-179,180]) + nRotationX = limit_cast< sal_Int32, sal_Int32 >( maData.mnElevation, -90, 90 ); + // perspective (Excel and Chart2 [0,100]) + nPerspective = limit_cast< sal_Int32, sal_Int32 >( maData.mnEyeDist, 0, 100 ); + // right-angled axes + bRightAngled = !::get_flag( maData.mnFlags, EXC_CHCHART3D_REAL3D ); + // projection mode (parallel axes, if right-angled, #i90360# or if perspective is at 0%) + bool bParallel = bRightAngled || (nPerspective == 0); + eProjMode = bParallel ? cssd::ProjectionMode_PARALLEL : cssd::ProjectionMode_PERSPECTIVE; + // ambient color (Gray 20%) + aAmbientColor = Color( 204, 204, 204 ); + // light color (Gray 60%) + aLightColor = Color( 102, 102, 102 ); + } + else + { + // Y rotation not used in pie charts, but 'first pie slice angle' + nRotationY = 0; + XclImpChRoot::ConvertPieRotation( rPropSet, maData.mnRotation ); + // X rotation a.k.a. elevation (map Excel [10..80] to Chart2 [-80,-10]) + nRotationX = limit_cast< sal_Int32, sal_Int32 >( maData.mnElevation, 10, 80 ) - 90; + // perspective (Excel and Chart2 [0,100]) + nPerspective = limit_cast< sal_Int32, sal_Int32 >( maData.mnEyeDist, 0, 100 ); + // no right-angled axes in pie charts, but parallel projection + bRightAngled = false; + eProjMode = cssd::ProjectionMode_PARALLEL; + // ambient color (Gray 30%) + aAmbientColor = Color( 179, 179, 179 ); + // light color (Gray 70%) + aLightColor = Color( 76, 76, 76 ); + } + + // properties + rPropSet.SetProperty( EXC_CHPROP_3DRELATIVEHEIGHT, static_cast<sal_Int32>(maData.mnRelHeight / 2)); // seems to be 200%, change to 100% + rPropSet.SetProperty( EXC_CHPROP_ROTATIONVERTICAL, nRotationY ); + rPropSet.SetProperty( EXC_CHPROP_ROTATIONHORIZONTAL, nRotationX ); + rPropSet.SetProperty( EXC_CHPROP_PERSPECTIVE, nPerspective ); + rPropSet.SetBoolProperty( EXC_CHPROP_RIGHTANGLEDAXES, bRightAngled ); + rPropSet.SetProperty( EXC_CHPROP_D3DSCENEPERSPECTIVE, eProjMode ); + + // light settings + rPropSet.SetProperty( EXC_CHPROP_D3DSCENESHADEMODE, cssd::ShadeMode_FLAT ); + rPropSet.SetColorProperty( EXC_CHPROP_D3DSCENEAMBIENTCOLOR, aAmbientColor ); + rPropSet.SetBoolProperty( EXC_CHPROP_D3DSCENELIGHTON1, false ); + rPropSet.SetBoolProperty( EXC_CHPROP_D3DSCENELIGHTON2, true ); + rPropSet.SetColorProperty( EXC_CHPROP_D3DSCENELIGHTCOLOR2, aLightColor ); + rPropSet.SetProperty( EXC_CHPROP_D3DSCENELIGHTDIR2, cssd::Direction3D( 0.2, 0.4, 1.0 ) ); +} + +XclImpChLegend::XclImpChLegend( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChLegend::ReadHeaderRecord( XclImpStream& rStrm ) +{ + rStrm >> maData.maRect; + maData.mnDockMode = rStrm.ReaduInt8(); + maData.mnSpacing = rStrm.ReaduInt8(); + maData.mnFlags = rStrm.ReaduInt16(); + + // trace unsupported features + if( GetTracer().IsEnabled() ) + { + if( maData.mnDockMode == EXC_CHLEGEND_NOTDOCKED ) + GetTracer().TraceChartLegendPosition(); + if( ::get_flag( maData.mnFlags, EXC_CHLEGEND_DATATABLE ) ) + GetTracer().TraceChartDataTable(); + } +} + +void XclImpChLegend::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared<XclImpChFramePos>(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHTEXT: + mxText = std::make_shared<XclImpChText>( GetChRoot() ); + mxText->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHFRAME: + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + mxFrame->ReadRecordGroup( rStrm ); + break; + } +} + +void XclImpChLegend::Finalize() +{ + // legend default formatting differs in OOChart and Excel, missing frame means automatic + if( !mxFrame ) + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + // Update text formatting. If mxText is empty, the passed default text is used. + lclUpdateText( mxText, GetChartData().GetDefaultText( EXC_CHTEXTTYPE_LEGEND ) ); +} + +Reference< XLegend > XclImpChLegend::CreateLegend() const +{ + Reference< XLegend > xLegend( ScfApiHelper::CreateInstance( SERVICE_CHART2_LEGEND ), UNO_QUERY ); + if( xLegend.is() ) + { + ScfPropertySet aLegendProp( xLegend ); + aLegendProp.SetBoolProperty( EXC_CHPROP_SHOW, true ); + + // frame properties + if( mxFrame ) + mxFrame->Convert( aLegendProp ); + // text properties + if( mxText ) + mxText->ConvertFont( aLegendProp ); + + /* Legend position and size. Default positions are used only if the + plot area is positioned automatically (Excel sets the plot area to + manual mode, if the legend is moved or resized). With manual plot + areas, Excel ignores the value in maData.mnDockMode completely. */ + cssc2::LegendPosition eApiPos = cssc2::LegendPosition_LINE_END; + cssc::ChartLegendExpansion eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + if( !GetChartData().IsManualPlotArea() ) switch( maData.mnDockMode ) + { + case EXC_CHLEGEND_LEFT: + eApiPos = cssc2::LegendPosition_LINE_START; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + break; + case EXC_CHLEGEND_RIGHT: + // top-right not supported + case EXC_CHLEGEND_CORNER: + eApiPos = cssc2::LegendPosition_LINE_END; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + break; + case EXC_CHLEGEND_TOP: + eApiPos = cssc2::LegendPosition_PAGE_START; + eApiExpand = cssc::ChartLegendExpansion_WIDE; + break; + case EXC_CHLEGEND_BOTTOM: + eApiPos = cssc2::LegendPosition_PAGE_END; + eApiExpand = cssc::ChartLegendExpansion_WIDE; + break; + } + + // no automatic position/size: try to find the correct position and size + if( GetChartData().IsManualPlotArea() || maData.mnDockMode == EXC_CHLEGEND_NOTDOCKED ) + { + const XclChFramePos* pFramePos = mxFramePos ? &mxFramePos->GetFramePosData() : nullptr; + + /* Legend position. Only the settings from the CHFRAMEPOS record + are used by Excel, the position in the CHLEGEND record will be + ignored. */ + if( pFramePos ) + { + RelativePosition aRelPos( + CalcRelativeFromChartX( pFramePos->maRect.mnX ), + CalcRelativeFromChartY( pFramePos->maRect.mnY ), + css::drawing::Alignment_TOP_LEFT ); + aLegendProp.SetProperty( EXC_CHPROP_RELATIVEPOSITION, aRelPos ); + } + else + { + // no manual position/size found, just go for the default + eApiPos = cssc2::LegendPosition_LINE_END; + } + + /* Legend size. The member mnBRMode specifies whether size is + automatic or changes manually. Manual size is given in points, + not in chart units. */ + if( pFramePos && (pFramePos->mnBRMode == EXC_CHFRAMEPOS_ABSSIZE_POINTS) && + (pFramePos->maRect.mnWidth > 0) && (pFramePos->maRect.mnHeight > 0) ) + { + eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + sal_Int32 nWidthHmm = o3tl::convert(pFramePos->maRect.mnWidth, o3tl::Length::pt, o3tl::Length::mm100); + sal_Int32 nHeightHmm = o3tl::convert(pFramePos->maRect.mnHeight, o3tl::Length::pt, o3tl::Length::mm100); + RelativeSize aRelSize( CalcRelativeFromHmmX( nWidthHmm ), CalcRelativeFromHmmY( nHeightHmm ) ); + aLegendProp.SetProperty( EXC_CHPROP_RELATIVESIZE, aRelSize ); + } + else + { + // automatic size: determine entry direction from flags + eApiExpand = ::get_flagvalue( maData.mnFlags, EXC_CHLEGEND_STACKED, + cssc::ChartLegendExpansion_HIGH, cssc::ChartLegendExpansion_WIDE ); + } + } + aLegendProp.SetProperty( EXC_CHPROP_ANCHORPOSITION, eApiPos ); + aLegendProp.SetProperty( EXC_CHPROP_EXPANSION, eApiExpand ); + } + return xLegend; +} + +XclImpChDropBar::XclImpChDropBar( sal_uInt16 nDropBar ) : + mnDropBar( nDropBar ), + mnBarDist( 0 ) +{ +} + +void XclImpChDropBar::ReadHeaderRecord( XclImpStream& rStrm ) +{ + mnBarDist = rStrm.ReaduInt16(); +} + +void XclImpChDropBar::Convert( const XclImpChRoot& rRoot, ScfPropertySet& rPropSet ) const +{ + XclChObjectType eObjType = EXC_CHOBJTYPE_BACKGROUND; + switch( mnDropBar ) + { + case EXC_CHDROPBAR_UP: eObjType = EXC_CHOBJTYPE_WHITEDROPBAR; break; + case EXC_CHDROPBAR_DOWN: eObjType = EXC_CHOBJTYPE_BLACKDROPBAR; break; + } + ConvertFrameBase( rRoot, rPropSet, eObjType ); +} + +XclImpChTypeGroup::XclImpChTypeGroup( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + maType( rRoot ), + maTypeInfo( maType.GetTypeInfo() ) +{ + // Initialize unused format indexes set. At this time, all formats are unused. + for( sal_uInt16 nFormatIdx = 0; nFormatIdx <= EXC_CHSERIES_MAXSERIES; ++nFormatIdx ) + maUnusedFormats.insert( maUnusedFormats.end(), nFormatIdx ); +} + +void XclImpChTypeGroup::ReadHeaderRecord( XclImpStream& rStrm ) +{ + rStrm.Ignore( 16 ); + maData.mnFlags = rStrm.ReaduInt16(); + maData.mnGroupIdx = rStrm.ReaduInt16(); +} + +void XclImpChTypeGroup::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHCHART3D: + mxChart3d = std::make_shared<XclImpChChart3d>(); + mxChart3d->ReadChChart3d( rStrm ); + break; + case EXC_ID_CHLEGEND: + mxLegend = std::make_shared<XclImpChLegend>( GetChRoot() ); + mxLegend->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHDEFAULTTEXT: + GetChartData().ReadChDefaultText( rStrm ); + break; + case EXC_ID_CHDROPBAR: + ReadChDropBar( rStrm ); + break; + case EXC_ID_CHCHARTLINE: + ReadChChartLine( rStrm ); + break; + case EXC_ID_CHDATAFORMAT: + ReadChDataFormat( rStrm ); + break; + default: + maType.ReadChType( rStrm ); + } +} + +void XclImpChTypeGroup::Finalize() +{ + // check and set valid chart type + bool bStockChart = + (maType.GetRecId() == EXC_ID_CHLINE) && // must be a line chart + !mxChart3d && // must be a 2d chart + m_ChartLines.find(EXC_CHCHARTLINE_HILO) != m_ChartLines.end() && // must contain hi-lo lines + (maSeries.size() == static_cast<XclImpChSeriesVec::size_type>(HasDropBars() ? 4 : 3)); // correct series count + maType.Finalize( bStockChart ); + + // extended type info + maTypeInfo.Set( maType.GetTypeInfo(), static_cast< bool >(mxChart3d), false ); + + // reverse series order for some unstacked 2D chart types + if( maTypeInfo.mbReverseSeries && !Is3dChart() && !maType.IsStacked() && !maType.IsPercent() ) + ::std::reverse( maSeries.begin(), maSeries.end() ); + + // update chart type group format, may depend on chart type finalized above + if( mxGroupFmt ) + mxGroupFmt->UpdateGroupFormat( maTypeInfo ); +} + +void XclImpChTypeGroup::AddSeries( XclImpChSeriesRef const & xSeries ) +{ + if( xSeries ) + maSeries.push_back( xSeries ); + // store first inserted series separately, series order may be reversed later + if( !mxFirstSeries ) + mxFirstSeries = xSeries; +} + +void XclImpChTypeGroup::SetUsedFormatIndex( sal_uInt16 nFormatIdx ) +{ + maUnusedFormats.erase( nFormatIdx ); +} + +sal_uInt16 XclImpChTypeGroup::PopUnusedFormatIndex() +{ + OSL_ENSURE( !maUnusedFormats.empty(), "XclImpChTypeGroup::PopUnusedFormatIndex - no more format indexes available" ); + sal_uInt16 nFormatIdx = maUnusedFormats.empty() ? 0 : *maUnusedFormats.begin(); + SetUsedFormatIndex( nFormatIdx ); + return nFormatIdx; +} + +bool XclImpChTypeGroup::HasVarPointFormat() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTYPEGROUP_VARIEDCOLORS ) && + ((maTypeInfo.meVarPointMode == EXC_CHVARPOINT_MULTI) || // multiple series allowed + ((maTypeInfo.meVarPointMode == EXC_CHVARPOINT_SINGLE) && // or exactly 1 series? + (maSeries.size() == 1))); +} + +bool XclImpChTypeGroup::HasConnectorLines() const +{ + // existence of connector lines (only in stacked bar charts) + if ( !(maType.IsStacked() || maType.IsPercent()) || (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_BAR) ) + return false; + XclImpChLineFormatMap::const_iterator aConLine = m_ChartLines.find(EXC_CHCHARTLINE_CONNECT); + return (aConLine != m_ChartLines.end() && aConLine->second.HasLine()); +} + +OUString XclImpChTypeGroup::GetSingleSeriesTitle() const +{ + // no automatic title for series with trendlines or error bars + // pie charts always show an automatic title, even if more series exist + return (mxFirstSeries && !mxFirstSeries->HasChildSeries() && (maTypeInfo.mbSingleSeriesVis || (maSeries.size() == 1))) ? + mxFirstSeries->GetTitle() : OUString(); +} + +void XclImpChTypeGroup::ConvertChart3d( ScfPropertySet& rPropSet ) const +{ + if( mxChart3d ) + mxChart3d->Convert( rPropSet, Is3dWallChart() ); +} + +Reference< XCoordinateSystem > XclImpChTypeGroup::CreateCoordSystem() const +{ + return maType.CreateCoordSystem( Is3dChart() ); +} + +Reference< XChartType > XclImpChTypeGroup::CreateChartType( Reference< XDiagram > const & xDiagram, sal_Int32 nApiAxesSetIdx ) const +{ + OSL_ENSURE( IsValidGroup(), "XclImpChTypeGroup::CreateChartType - type group without series" ); + + // create the chart type object + Reference< XChartType > xChartType = maType.CreateChartType( xDiagram, Is3dChart() ); + + // bar chart connector lines + if( HasConnectorLines() ) + { + ScfPropertySet aDiaProp( xDiagram ); + aDiaProp.SetBoolProperty( EXC_CHPROP_CONNECTBARS, true ); + } + + /* Stock chart needs special processing. Create one 'big' series with + data sequences of different roles. */ + if( maTypeInfo.meTypeId == EXC_CHTYPEID_STOCK ) + CreateStockSeries( xChartType, nApiAxesSetIdx ); + else + CreateDataSeries( xChartType, nApiAxesSetIdx ); + + return xChartType; +} + +Reference< XLabeledDataSequence > XclImpChTypeGroup::CreateCategSequence() const +{ + Reference< XLabeledDataSequence > xLabeledSeq; + // create category sequence from first visible series + if( mxFirstSeries ) + xLabeledSeq = mxFirstSeries->CreateCategSequence( EXC_CHPROP_ROLE_CATEG ); + return xLabeledSeq; +} + +void XclImpChTypeGroup::ReadChDropBar( XclImpStream& rStrm ) +{ + if (m_DropBars.find(EXC_CHDROPBAR_UP) == m_DropBars.end()) + { + unique_ptr<XclImpChDropBar> p(new XclImpChDropBar(EXC_CHDROPBAR_UP)); + p->ReadRecordGroup(rStrm); + m_DropBars.insert(std::make_pair(EXC_CHDROPBAR_UP, std::move(p))); + } + else if (m_DropBars.find(EXC_CHDROPBAR_DOWN) == m_DropBars.end()) + { + unique_ptr<XclImpChDropBar> p(new XclImpChDropBar(EXC_CHDROPBAR_DOWN)); + p->ReadRecordGroup(rStrm); + m_DropBars.insert(std::make_pair(EXC_CHDROPBAR_DOWN, std::move(p))); + } +} + +void XclImpChTypeGroup::ReadChChartLine( XclImpStream& rStrm ) +{ + sal_uInt16 nLineId = rStrm.ReaduInt16(); + if( (rStrm.GetNextRecId() == EXC_ID_CHLINEFORMAT) && rStrm.StartNextRecord() ) + { + XclImpChLineFormat aLineFmt; + aLineFmt.ReadChLineFormat( rStrm ); + m_ChartLines[ nLineId ] = aLineFmt; + } +} + +void XclImpChTypeGroup::ReadChDataFormat( XclImpStream& rStrm ) +{ + // global series and data point format + XclImpChDataFormatRef xDataFmt = std::make_shared<XclImpChDataFormat>( GetChRoot() ); + xDataFmt->ReadRecordGroup( rStrm ); + const XclChDataPointPos& rPos = xDataFmt->GetPointPos(); + if( (rPos.mnSeriesIdx == 0) && (rPos.mnPointIdx == 0) && + (xDataFmt->GetFormatIdx() == EXC_CHDATAFORMAT_DEFAULT) ) + mxGroupFmt = xDataFmt; +} + +void XclImpChTypeGroup::InsertDataSeries( Reference< XChartType > const & xChartType, + Reference< XDataSeries > const & xSeries, sal_Int32 nApiAxesSetIdx ) const +{ + Reference< XDataSeriesContainer > xSeriesCont( xChartType, UNO_QUERY ); + if( !(xSeriesCont.is() && xSeries.is()) ) + return; + + // series stacking mode + cssc2::StackingDirection eStacking = cssc2::StackingDirection_NO_STACKING; + // stacked overrides deep-3d + if( maType.IsStacked() || maType.IsPercent() ) + eStacking = cssc2::StackingDirection_Y_STACKING; + else if( Is3dDeepChart() ) + eStacking = cssc2::StackingDirection_Z_STACKING; + + // additional series properties + ScfPropertySet aSeriesProp( xSeries ); + aSeriesProp.SetProperty( EXC_CHPROP_STACKINGDIR, eStacking ); + aSeriesProp.SetProperty( EXC_CHPROP_ATTAXISINDEX, nApiAxesSetIdx ); + + // insert series into container + try + { + xSeriesCont->addDataSeries( xSeries ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChTypeGroup::InsertDataSeries - cannot add data series" ); + } +} + +void XclImpChTypeGroup::CreateDataSeries( Reference< XChartType > const & xChartType, sal_Int32 nApiAxesSetIdx ) const +{ + bool bSpline = false; + for (auto const& elem : maSeries) + { + Reference< XDataSeries > xDataSeries = elem->CreateDataSeries(); + InsertDataSeries( xChartType, xDataSeries, nApiAxesSetIdx ); + bSpline |= elem->HasSpline(); + } + // spline - TODO: set at single series (#i66858#) + if( bSpline && !maTypeInfo.IsSeriesFrameFormat() && (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_RADAR) ) + { + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetProperty( EXC_CHPROP_CURVESTYLE, css::chart2::CurveStyle_CUBIC_SPLINES ); + } +} + +void XclImpChTypeGroup::CreateStockSeries( Reference< XChartType > const & xChartType, sal_Int32 nApiAxesSetIdx ) const +{ + // create the data series object + Reference< XDataSeries > xDataSeries( ScfApiHelper::CreateInstance( SERVICE_CHART2_DATASERIES ), UNO_QUERY ); + Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY ); + if( !xDataSink.is() ) + return; + + // create a list of data sequences from all series + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + OSL_ENSURE( maSeries.size() >= 3, "XclImpChTypeGroup::CreateChartType - missing stock series" ); + int nRoleIdx = (maSeries.size() == 3) ? 1 : 0; + for( const auto& rxSeries : maSeries ) + { + // create a data sequence with a specific role + OUString aRole; + switch( nRoleIdx ) + { + case 0: aRole = EXC_CHPROP_ROLE_OPENVALUES; break; + case 1: aRole = EXC_CHPROP_ROLE_HIGHVALUES; break; + case 2: aRole = EXC_CHPROP_ROLE_LOWVALUES; break; + case 3: aRole = EXC_CHPROP_ROLE_CLOSEVALUES; break; + } + Reference< XLabeledDataSequence > xDataSeq = rxSeries->CreateValueSequence( aRole ); + if( xDataSeq.is() ) + aLabeledSeqVec.push_back( xDataSeq ); + ++nRoleIdx; + if (nRoleIdx >= 4) + break; + } + + // attach labeled data sequences to series and insert series into chart type + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + + // formatting of special stock chart elements + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetBoolProperty( EXC_CHPROP_JAPANESE, HasDropBars() ); + aTypeProp.SetBoolProperty( EXC_CHPROP_SHOWFIRST, HasDropBars() ); + aTypeProp.SetBoolProperty( EXC_CHPROP_SHOWHIGHLOW, true ); + // hi-lo line format + XclImpChLineFormatMap::const_iterator aHiLoLine = m_ChartLines.find( EXC_CHCHARTLINE_HILO ); + if (aHiLoLine != m_ChartLines.end()) + { + ScfPropertySet aSeriesProp( xDataSeries ); + aHiLoLine->second.Convert( GetChRoot(), aSeriesProp, EXC_CHOBJTYPE_HILOLINE ); + } + // white dropbar format + XclImpChDropBarMap::const_iterator itr = m_DropBars.find(EXC_CHDROPBAR_UP); + Reference<XPropertySet> xWhitePropSet; + if (itr != m_DropBars.end() && aTypeProp.GetProperty(xWhitePropSet, EXC_CHPROP_WHITEDAY)) + { + ScfPropertySet aBarProp( xWhitePropSet ); + itr->second->Convert(GetChRoot(), aBarProp); + } + // black dropbar format + itr = m_DropBars.find(EXC_CHDROPBAR_DOWN); + Reference<XPropertySet> xBlackPropSet; + if (itr != m_DropBars.end() && aTypeProp.GetProperty(xBlackPropSet, EXC_CHPROP_BLACKDAY)) + { + ScfPropertySet aBarProp( xBlackPropSet ); + itr->second->Convert(GetChRoot(), aBarProp); + } + + // insert the series into the chart type object + InsertDataSeries( xChartType, xDataSeries, nApiAxesSetIdx ); +} + +// Axes ======================================================================= + +XclImpChLabelRange::XclImpChLabelRange( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChLabelRange::ReadChLabelRange( XclImpStream& rStrm ) +{ + maLabelData.mnCross = rStrm.ReaduInt16(); + maLabelData.mnLabelFreq = rStrm.ReaduInt16(); + maLabelData.mnTickFreq = rStrm.ReaduInt16(); + maLabelData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChLabelRange::ReadChDateRange( XclImpStream& rStrm ) +{ + maDateData.mnMinDate = rStrm.ReaduInt16(); + maDateData.mnMaxDate = rStrm.ReaduInt16(); + maDateData.mnMajorStep = rStrm.ReaduInt16(); + maDateData.mnMajorUnit = rStrm.ReaduInt16(); + maDateData.mnMinorStep = rStrm.ReaduInt16(); + maDateData.mnMinorUnit = rStrm.ReaduInt16(); + maDateData.mnBaseUnit = rStrm.ReaduInt16(); + maDateData.mnCross = rStrm.ReaduInt16(); + maDateData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChLabelRange::Convert( ScfPropertySet& rPropSet, ScaleData& rScaleData, bool bMirrorOrient ) const +{ + // automatic axis type detection + rScaleData.AutoDateAxis = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTODATE ); + + // the flag EXC_CHDATERANGE_DATEAXIS specifies whether this is a date axis + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ) ) + { + /* Chart2 requires axis type CATEGORY for automatic category/date axis + (even if it is a date axis currently). */ + rScaleData.AxisType = rScaleData.AutoDateAxis ? cssc2::AxisType::CATEGORY : cssc2::AxisType::DATE; + rScaleData.Scaling = css::chart2::LinearScaling::create( comphelper::getProcessComponentContext() ); + /* Min/max values depend on base time unit, they specify the number of + days, months, or years starting from null date. */ + lclConvertTimeValue( GetRoot(), rScaleData.Minimum, maDateData.mnMinDate, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMIN ), maDateData.mnBaseUnit ); + lclConvertTimeValue( GetRoot(), rScaleData.Maximum, maDateData.mnMaxDate, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAX ), maDateData.mnBaseUnit ); + // increment + cssc::TimeIncrement& rTimeIncrement = rScaleData.TimeIncrement; + lclConvertTimeInterval( rTimeIncrement.MajorTimeInterval, maDateData.mnMajorStep, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAJOR ), maDateData.mnMajorUnit ); + lclConvertTimeInterval( rTimeIncrement.MinorTimeInterval, maDateData.mnMinorStep, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMINOR ), maDateData.mnMinorUnit ); + // base unit + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOBASE ) ) + rTimeIncrement.TimeResolution.clear(); + else + rTimeIncrement.TimeResolution <<= lclGetApiTimeUnit( maDateData.mnBaseUnit ); + } + else + { + // do not overlap text unless all labels are visible + rPropSet.SetBoolProperty( EXC_CHPROP_TEXTOVERLAP, maLabelData.mnLabelFreq == 1 ); + // do not break text into several lines unless all labels are visible + rPropSet.SetBoolProperty( EXC_CHPROP_TEXTBREAK, maLabelData.mnLabelFreq == 1 ); + // do not stagger labels in two lines + rPropSet.SetProperty( EXC_CHPROP_ARRANGEORDER, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE ); + } + + // reverse order + bool bReverse = ::get_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_REVERSE ) != bMirrorOrient; + rScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; + + //TODO #i58731# show n-th category +} + +void XclImpChLabelRange::ConvertAxisPosition( ScfPropertySet& rPropSet, bool b3dChart ) const +{ + /* Crossing mode (max-cross flag overrides other crossing settings). Excel + does not move the Y axis in 3D charts, regardless of actual settings. + But: the Y axis has to be moved to "end", if the X axis is mirrored, + to keep it at the left end of the chart. */ + bool bMaxCross = ::get_flag( maLabelData.mnFlags, b3dChart ? EXC_CHLABELRANGE_REVERSE : EXC_CHLABELRANGE_MAXCROSS ); + cssc::ChartAxisPosition eAxisPos = bMaxCross ? cssc::ChartAxisPosition_END : cssc::ChartAxisPosition_VALUE; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERPOSITION, eAxisPos ); + + // crossing position (depending on axis type text/date) + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ) ) + { + bool bAutoCross = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + /* Crossing position value depends on base time unit, it specifies the + number of days, months, or years from null date. Note that Excel + 2007/2010 write broken BIFF8 files, they always stores the number + of days regardless of the base time unit (and they are reading it + the same way, thus wrongly displaying files written by Excel + 97-2003). This filter sticks to the correct behaviour of Excel + 97-2003. */ + double fCrossingPos = bAutoCross ? 1.0 : lclGetSerialDay( GetRoot(), maDateData.mnCross, maDateData.mnBaseUnit ); + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); + } + else + { + double fCrossingPos = b3dChart ? 1.0 : maLabelData.mnCross; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); + } +} + +XclImpChValueRange::XclImpChValueRange( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChValueRange::ReadChValueRange( XclImpStream& rStrm ) +{ + maData.mfMin = rStrm.ReadDouble(); + maData.mfMax = rStrm.ReadDouble(); + maData.mfMajorStep = rStrm.ReadDouble(); + maData.mfMinorStep = rStrm.ReadDouble(); + maData.mfCross = rStrm.ReadDouble(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChValueRange::Convert( ScaleData& rScaleData, bool bMirrorOrient ) const +{ + // scaling algorithm + const bool bLogScale = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE ); + if( bLogScale ) + rScaleData.Scaling = css::chart2::LogarithmicScaling::create( comphelper::getProcessComponentContext() ); + else + rScaleData.Scaling = css::chart2::LinearScaling::create( comphelper::getProcessComponentContext() ); + + // min/max + lclSetExpValueOrClearAny( rScaleData.Minimum, maData.mfMin, bLogScale, ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMIN ) ); + lclSetExpValueOrClearAny( rScaleData.Maximum, maData.mfMax, bLogScale, ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAX ) ); + + // increment + bool bAutoMajor = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAJOR ); + bool bAutoMinor = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMINOR ); + // major increment + IncrementData& rIncrementData = rScaleData.IncrementData; + lclSetValueOrClearAny( rIncrementData.Distance, maData.mfMajorStep, bAutoMajor ); + // minor increment + Sequence< SubIncrement >& rSubIncrementSeq = rIncrementData.SubIncrements; + rSubIncrementSeq.realloc( 1 ); + Any& rIntervalCount = rSubIncrementSeq.getArray()[ 0 ].IntervalCount; + rIntervalCount.clear(); + if( bLogScale ) + { + if( !bAutoMinor ) + rIntervalCount <<= sal_Int32( 9 ); + } + else if( !bAutoMajor && !bAutoMinor && (0.0 < maData.mfMinorStep) && (maData.mfMinorStep <= maData.mfMajorStep) ) + { + double fCount = maData.mfMajorStep / maData.mfMinorStep + 0.5; + if( (1.0 <= fCount) && (fCount < 1001.0) ) + rIntervalCount <<= static_cast< sal_Int32 >( fCount ); + } + else if( bAutoMinor ) + { + // tdf#114168 If minor unit is not set then set interval to 5, as MS Excel do. + rIntervalCount <<= static_cast< sal_Int32 >( 5 ); + } + + // reverse order + bool bReverse = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_REVERSE ) != bMirrorOrient; + rScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; +} + +void XclImpChValueRange::ConvertAxisPosition( ScfPropertySet& rPropSet ) const +{ + bool bMaxCross = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_MAXCROSS ); + bool bAutoCross = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + bool bLogScale = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE ); + + // crossing mode (max-cross flag overrides other crossing settings) + cssc::ChartAxisPosition eAxisPos = bMaxCross ? cssc::ChartAxisPosition_END : cssc::ChartAxisPosition_VALUE; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERPOSITION, eAxisPos ); + + // crossing position + double fCrossingPos = bAutoCross ? 0.0 : maData.mfCross; + if( bLogScale ) fCrossingPos = pow( 10.0, fCrossingPos ); + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); +} + +namespace { + +sal_Int32 lclGetApiTickmarks( sal_uInt8 nXclTickPos ) +{ + using namespace ::com::sun::star::chart2::TickmarkStyle; + sal_Int32 nApiTickmarks = css::chart2::TickmarkStyle::NONE; + ::set_flag( nApiTickmarks, INNER, ::get_flag( nXclTickPos, EXC_CHTICK_INSIDE ) ); + ::set_flag( nApiTickmarks, OUTER, ::get_flag( nXclTickPos, EXC_CHTICK_OUTSIDE ) ); + return nApiTickmarks; +} + +cssc::ChartAxisLabelPosition lclGetApiLabelPosition( sal_Int8 nXclLabelPos ) +{ + using namespace ::com::sun::star::chart; + switch( nXclLabelPos ) + { + case EXC_CHTICK_LOW: return ChartAxisLabelPosition_OUTSIDE_START; + case EXC_CHTICK_HIGH: return ChartAxisLabelPosition_OUTSIDE_END; + case EXC_CHTICK_NEXT: return ChartAxisLabelPosition_NEAR_AXIS; + } + return ChartAxisLabelPosition_NEAR_AXIS; +} + +} // namespace + +XclImpChTick::XclImpChTick( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChTick::ReadChTick( XclImpStream& rStrm ) +{ + maData.mnMajor = rStrm.ReaduInt8(); + maData.mnMinor = rStrm.ReaduInt8(); + maData.mnLabelPos = rStrm.ReaduInt8(); + maData.mnBackMode = rStrm.ReaduInt8(); + rStrm.Ignore( 16 ); + rStrm >> maData.maTextColor; + maData.mnFlags = rStrm.ReaduInt16(); + + if( GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + maData.maTextColor = GetPalette().GetColor( rStrm.ReaduInt16() ); + // rotation + maData.mnRotation = rStrm.ReaduInt16(); + } + else + { + // BIFF2-BIFF7: get rotation from text orientation + sal_uInt8 nOrient = ::extract_value< sal_uInt8 >( maData.mnFlags, 2, 3 ); + maData.mnRotation = XclTools::GetXclRotFromOrient( nOrient ); + } +} + +Color XclImpChTick::GetFontColor() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTICK_AUTOCOLOR ) ? GetFontAutoColor() : maData.maTextColor; +} + +sal_uInt16 XclImpChTick::GetRotation() const +{ + /* n#720443: Ignore auto-rotation if there is a suggested rotation. + * Better fix would be to improve our axis auto rotation algorithm. + */ + if( maData.mnRotation != EXC_ROT_NONE ) + return maData.mnRotation; + return ::get_flag( maData.mnFlags, EXC_CHTICK_AUTOROT ) ? EXC_CHART_AUTOROTATION : maData.mnRotation; +} + +void XclImpChTick::Convert( ScfPropertySet& rPropSet ) const +{ + rPropSet.SetProperty( EXC_CHPROP_MAJORTICKS, lclGetApiTickmarks( maData.mnMajor ) ); + rPropSet.SetProperty( EXC_CHPROP_MINORTICKS, lclGetApiTickmarks( maData.mnMinor ) ); + rPropSet.SetProperty( EXC_CHPROP_LABELPOSITION, lclGetApiLabelPosition( maData.mnLabelPos ) ); + rPropSet.SetProperty( EXC_CHPROP_MARKPOSITION, cssc::ChartAxisMarkPosition_AT_AXIS ); +} + +XclImpChAxis::XclImpChAxis( const XclImpChRoot& rRoot, sal_uInt16 nAxisType ) : + XclImpChRoot( rRoot ), + mnNumFmtIdx( EXC_FORMAT_NOTFOUND ) +{ + maData.mnType = nAxisType; +} + +void XclImpChAxis::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnType = rStrm.ReaduInt16(); +} + +void XclImpChAxis::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHLABELRANGE: + mxLabelRange = std::make_shared<XclImpChLabelRange>( GetChRoot() ); + mxLabelRange->ReadChLabelRange( rStrm ); + break; + case EXC_ID_CHDATERANGE: + if( !mxLabelRange ) + mxLabelRange = std::make_shared<XclImpChLabelRange>( GetChRoot() ); + mxLabelRange->ReadChDateRange( rStrm ); + break; + case EXC_ID_CHVALUERANGE: + mxValueRange = std::make_shared<XclImpChValueRange>( GetChRoot() ); + mxValueRange->ReadChValueRange( rStrm ); + break; + case EXC_ID_CHFORMAT: + mnNumFmtIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHTICK: + mxTick = std::make_shared<XclImpChTick>( GetChRoot() ); + mxTick->ReadChTick( rStrm ); + break; + case EXC_ID_CHFONT: + mxFont = std::make_shared<XclImpChFont>(); + mxFont->ReadChFont( rStrm ); + break; + case EXC_ID_CHAXISLINE: + ReadChAxisLine( rStrm ); + break; + } +} + +void XclImpChAxis::Finalize() +{ + // add default scaling, needed e.g. to adjust rotation direction of pie and radar charts + if( !mxLabelRange ) + mxLabelRange = std::make_shared<XclImpChLabelRange>( GetChRoot() ); + if( !mxValueRange ) + mxValueRange = std::make_shared<XclImpChValueRange>( GetChRoot() ); + // remove invisible grid lines completely + if( mxMajorGrid && !mxMajorGrid->HasLine() ) + mxMajorGrid.clear(); + if( mxMinorGrid && !mxMinorGrid->HasLine() ) + mxMinorGrid.clear(); + // default tick settings different in OOChart and Excel + if( !mxTick ) + mxTick = std::make_shared<XclImpChTick>( GetChRoot() ); + // #i4140# different default axis line color + if( !mxAxisLine ) + { + XclChLineFormat aLineFmt; + // set "show axis" flag, default if line format record is missing + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_SHOWAXIS ); + mxAxisLine = new XclImpChLineFormat( aLineFmt ); + } + // add wall/floor frame for 3d charts + if( !mxWallFrame ) + CreateWallFrame(); +} + +sal_uInt16 XclImpChAxis::GetFontIndex() const +{ + return mxFont ? mxFont->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +Color XclImpChAxis::GetFontColor() const +{ + return mxTick ? mxTick->GetFontColor() : GetFontAutoColor(); +} + +sal_uInt16 XclImpChAxis::GetRotation() const +{ + return mxTick ? mxTick->GetRotation() : EXC_CHART_AUTOROTATION; +} + +Reference< XAxis > XclImpChAxis::CreateAxis( const XclImpChTypeGroup& rTypeGroup, const XclImpChAxis* pCrossingAxis ) const +{ + // create the axis object (always) + Reference< XAxis > xAxis( ScfApiHelper::CreateInstance( SERVICE_CHART2_AXIS ), UNO_QUERY ); + if( xAxis.is() ) + { + ScfPropertySet aAxisProp( xAxis ); + // #i58688# axis enabled + aAxisProp.SetBoolProperty( EXC_CHPROP_SHOW, !mxAxisLine || mxAxisLine->IsShowAxis() ); + + // axis line properties + if( mxAxisLine ) + mxAxisLine->Convert( GetChRoot(), aAxisProp, EXC_CHOBJTYPE_AXISLINE ); + // axis ticks properties + if( mxTick ) + mxTick->Convert( aAxisProp ); + + // axis caption text -------------------------------------------------- + + // radar charts disable their category labels via chart type, not via axis + bool bHasLabels = (!mxTick || mxTick->HasLabels()) && + ((GetAxisType() != EXC_CHAXIS_X) || rTypeGroup.HasCategoryLabels()); + aAxisProp.SetBoolProperty( EXC_CHPROP_DISPLAYLABELS, bHasLabels ); + if( bHasLabels ) + { + // font settings from CHFONT record or from default text + if( mxFont ) + ConvertFontBase( GetChRoot(), aAxisProp ); + else if( const XclImpChText* pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_AXISLABEL ) ) + pDefText->ConvertFont( aAxisProp ); + // label text rotation + ConvertRotationBase( aAxisProp, true ); + // number format + bool bLinkNumberFmtToSource = true; + if ( mnNumFmtIdx != EXC_FORMAT_NOTFOUND ) + { + sal_uInt32 nScNumFmt = GetNumFmtBuffer().GetScFormat( mnNumFmtIdx ); + if( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + aAxisProp.SetProperty( EXC_CHPROP_NUMBERFORMAT, static_cast< sal_Int32 >( nScNumFmt ) ); + bLinkNumberFmtToSource = false; + } + } + + aAxisProp.SetProperty( EXC_CHPROP_NUMBERFORMAT_LINKSRC, bLinkNumberFmtToSource ); + } + + // axis scaling and increment ----------------------------------------- + + const XclChExtTypeInfo& rTypeInfo = rTypeGroup.GetTypeInfo(); + ScaleData aScaleData = xAxis->getScaleData(); + // set axis type + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + if( rTypeInfo.mbCategoryAxis ) + { + aScaleData.AxisType = cssc2::AxisType::CATEGORY; + aScaleData.Categories = rTypeGroup.CreateCategSequence(); + } + else + aScaleData.AxisType = cssc2::AxisType::REALNUMBER; + break; + case EXC_CHAXIS_Y: + aScaleData.AxisType = rTypeGroup.IsPercent() ? + cssc2::AxisType::PERCENT : cssc2::AxisType::REALNUMBER; + break; + case EXC_CHAXIS_Z: + aScaleData.AxisType = cssc2::AxisType::SERIES; + break; + } + // axis scaling settings, dependent on axis type + switch( aScaleData.AxisType ) + { + case cssc2::AxisType::CATEGORY: + case cssc2::AxisType::SERIES: + // #i71684# radar charts have reversed rotation direction + if (mxLabelRange) + mxLabelRange->Convert( aAxisProp, aScaleData, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR ); + else + SAL_WARN("sc.filter", "missing LabelRange"); + break; + case cssc2::AxisType::REALNUMBER: + case cssc2::AxisType::PERCENT: + // #i85167# pie/donut charts have reversed rotation direction (at Y axis!) + if (mxValueRange) + mxValueRange->Convert( aScaleData, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ); + else + SAL_WARN("sc.filter", "missing ValueRange"); + break; + default: + OSL_FAIL( "XclImpChAxis::CreateAxis - unknown axis type" ); + } + + /* Do not set a value to the Origin member anymore (will be done via + new axis properties 'CrossoverPosition' and 'CrossoverValue'). */ + aScaleData.Origin.clear(); + + // write back + xAxis->setScaleData( aScaleData ); + + // grid --------------------------------------------------------------- + + // main grid + ScfPropertySet aGridProp( xAxis->getGridProperties() ); + aGridProp.SetBoolProperty( EXC_CHPROP_SHOW, static_cast<bool>(mxMajorGrid) ); + if( mxMajorGrid ) + mxMajorGrid->Convert( GetChRoot(), aGridProp, EXC_CHOBJTYPE_GRIDLINE ); + // sub grid + Sequence< Reference< XPropertySet > > aSubGridPropSeq = xAxis->getSubGridProperties(); + if( aSubGridPropSeq.hasElements() ) + { + ScfPropertySet aSubGridProp( aSubGridPropSeq[ 0 ] ); + aSubGridProp.SetBoolProperty( EXC_CHPROP_SHOW, static_cast<bool>(mxMinorGrid) ); + if( mxMinorGrid ) + mxMinorGrid->Convert( GetChRoot(), aSubGridProp, EXC_CHOBJTYPE_GRIDLINE ); + } + + // position of crossing axis ------------------------------------------ + + if( pCrossingAxis ) + pCrossingAxis->ConvertAxisPosition( aAxisProp, rTypeGroup ); + } + return xAxis; +} + +void XclImpChAxis::ConvertWall( ScfPropertySet& rPropSet ) const +{ + // #i71810# walls and floor in 3D charts use the CHPICFORMAT record for bitmap mode + if( mxWallFrame ) + mxWallFrame->Convert( rPropSet, true ); +} + +void XclImpChAxis::ConvertAxisPosition( ScfPropertySet& rPropSet, const XclImpChTypeGroup& rTypeGroup ) const +{ + if( ((GetAxisType() == EXC_CHAXIS_X) && rTypeGroup.GetTypeInfo().mbCategoryAxis) || (GetAxisType() == EXC_CHAXIS_Z) ) + { + if (mxLabelRange) + mxLabelRange->ConvertAxisPosition( rPropSet, rTypeGroup.Is3dChart() ); + else + SAL_WARN("sc.filter", "missing LabelRange"); + } + else + { + if (mxValueRange) + mxValueRange->ConvertAxisPosition( rPropSet ); + else + SAL_WARN("sc.filter", "missing ValueRange"); + } +} + +void XclImpChAxis::ReadChAxisLine( XclImpStream& rStrm ) +{ + XclImpChLineFormatRef* pxLineFmt = nullptr; + bool bWallFrame = false; + switch( rStrm.ReaduInt16() ) + { + case EXC_CHAXISLINE_AXISLINE: pxLineFmt = &mxAxisLine; break; + case EXC_CHAXISLINE_MAJORGRID: pxLineFmt = &mxMajorGrid; break; + case EXC_CHAXISLINE_MINORGRID: pxLineFmt = &mxMinorGrid; break; + case EXC_CHAXISLINE_WALLS: bWallFrame = true; break; + } + if( bWallFrame ) + CreateWallFrame(); + + bool bLoop = pxLineFmt || bWallFrame; + while( bLoop ) + { + sal_uInt16 nRecId = rStrm.GetNextRecId(); + bLoop = ((nRecId == EXC_ID_CHLINEFORMAT) || + (nRecId == EXC_ID_CHAREAFORMAT) || + (nRecId == EXC_ID_CHESCHERFORMAT)) + && rStrm.StartNextRecord(); + if( bLoop ) + { + if( pxLineFmt && (nRecId == EXC_ID_CHLINEFORMAT) ) + { + (*pxLineFmt) = new XclImpChLineFormat(); + (*pxLineFmt)->ReadChLineFormat( rStrm ); + } + else if( bWallFrame && mxWallFrame ) + { + mxWallFrame->ReadSubRecord( rStrm ); + } + } + } +} + +void XclImpChAxis::CreateWallFrame() +{ + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + mxWallFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_WALL3D ); + break; + case EXC_CHAXIS_Y: + mxWallFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_FLOOR3D ); + break; + default: + mxWallFrame.reset(); + } +} + +XclImpChAxesSet::XclImpChAxesSet( const XclImpChRoot& rRoot, sal_uInt16 nAxesSetId ) : + XclImpChRoot( rRoot ) +{ + maData.mnAxesSetId = nAxesSetId; +} + +void XclImpChAxesSet::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnAxesSetId = rStrm.ReaduInt16(); + rStrm >> maData.maRect; +} + +void XclImpChAxesSet::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared<XclImpChFramePos>(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHAXIS: + ReadChAxis( rStrm ); + break; + case EXC_ID_CHTEXT: + ReadChText( rStrm ); + break; + case EXC_ID_CHPLOTFRAME: + ReadChPlotFrame( rStrm ); + break; + case EXC_ID_CHTYPEGROUP: + ReadChTypeGroup( rStrm ); + break; + } +} + +void XclImpChAxesSet::Finalize() +{ + if( IsValidAxesSet() ) + { + // finalize chart type groups, erase empty groups without series + XclImpChTypeGroupMap aValidGroups; + for (auto const& typeGroup : maTypeGroups) + { + XclImpChTypeGroupRef xTypeGroup = typeGroup.second; + xTypeGroup->Finalize(); + if( xTypeGroup->IsValidGroup() ) + aValidGroups.emplace(typeGroup.first, xTypeGroup); + } + maTypeGroups.swap( aValidGroups ); + } + + // invalid chart type groups are deleted now, check again with IsValidAxesSet() + if( !IsValidAxesSet() ) + return; + + // always create missing axis objects + if( !mxXAxis ) + mxXAxis = std::make_shared<XclImpChAxis>( GetChRoot(), EXC_CHAXIS_X ); + if( !mxYAxis ) + mxYAxis = std::make_shared<XclImpChAxis>( GetChRoot(), EXC_CHAXIS_Y ); + if( !mxZAxis && GetFirstTypeGroup()->Is3dDeepChart() ) + mxZAxis = std::make_shared<XclImpChAxis>( GetChRoot(), EXC_CHAXIS_Z ); + + // finalize axes + if( mxXAxis ) mxXAxis->Finalize(); + if( mxYAxis ) mxYAxis->Finalize(); + if( mxZAxis ) mxZAxis->Finalize(); + + // finalize axis titles + const XclImpChText* pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_AXISTITLE ); + OUString aAutoTitle(ScResId(STR_AXISTITLE)); + lclFinalizeTitle( mxXAxisTitle, pDefText, aAutoTitle ); + lclFinalizeTitle( mxYAxisTitle, pDefText, aAutoTitle ); + lclFinalizeTitle( mxZAxisTitle, pDefText, aAutoTitle ); + + // #i47745# missing plot frame -> invisible border and area + if( !mxPlotFrame ) + mxPlotFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_PLOTFRAME ); +} + +XclImpChTypeGroupRef XclImpChAxesSet::GetTypeGroup( sal_uInt16 nGroupIdx ) const +{ + XclImpChTypeGroupMap::const_iterator itr = maTypeGroups.find(nGroupIdx); + return itr == maTypeGroups.end() ? XclImpChTypeGroupRef() : itr->second; +} + +XclImpChTypeGroupRef XclImpChAxesSet::GetFirstTypeGroup() const +{ + XclImpChTypeGroupRef xTypeGroup; + if( !maTypeGroups.empty() ) + xTypeGroup = maTypeGroups.begin()->second; + return xTypeGroup; +} + +XclImpChLegendRef XclImpChAxesSet::GetLegend() const +{ + XclImpChLegendRef xLegend; + for( const auto& rEntry : maTypeGroups ) + { + xLegend = rEntry.second->GetLegend(); + if (xLegend) + break; + } + return xLegend; +} + +OUString XclImpChAxesSet::GetSingleSeriesTitle() const +{ + return (maTypeGroups.size() == 1) ? maTypeGroups.begin()->second->GetSingleSeriesTitle() : OUString(); +} + +void XclImpChAxesSet::Convert( Reference< XDiagram > const & xDiagram ) const +{ + if( !(IsValidAxesSet() && xDiagram.is()) ) + return; + + // diagram background formatting + if( GetAxesSetId() == EXC_CHAXESSET_PRIMARY ) + ConvertBackground( xDiagram ); + + // create the coordinate system, this inserts all chart types and series + Reference< XCoordinateSystem > xCoordSystem = CreateCoordSystem( xDiagram ); + if( !xCoordSystem.is() ) + return; + + // insert coordinate system, if not already done + try + { + Reference< XCoordinateSystemContainer > xCoordSystemCont( xDiagram, UNO_QUERY_THROW ); + Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems(); + if( !aCoordSystems.hasElements() ) + xCoordSystemCont->addCoordinateSystem( xCoordSystem ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::Convert - cannot insert coordinate system" ); + } + + // create the axes with grids and axis titles and insert them into the diagram + ConvertAxis( mxXAxis, mxXAxisTitle, xCoordSystem, mxYAxis.get() ); + ConvertAxis( mxYAxis, mxYAxisTitle, xCoordSystem, mxXAxis.get() ); + ConvertAxis( mxZAxis, mxZAxisTitle, xCoordSystem, nullptr ); +} + +void XclImpChAxesSet::ConvertTitlePositions() const +{ + if( mxXAxisTitle ) + mxXAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_X ) ); + if( mxYAxisTitle ) + mxYAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_Y ) ); + if( mxZAxisTitle ) + mxZAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_Z ) ); +} + +void XclImpChAxesSet::ReadChAxis( XclImpStream& rStrm ) +{ + XclImpChAxisRef xAxis = std::make_shared<XclImpChAxis>( GetChRoot() ); + xAxis->ReadRecordGroup( rStrm ); + + switch( xAxis->GetAxisType() ) + { + case EXC_CHAXIS_X: mxXAxis = xAxis; break; + case EXC_CHAXIS_Y: mxYAxis = xAxis; break; + case EXC_CHAXIS_Z: mxZAxis = xAxis; break; + } +} + +void XclImpChAxesSet::ReadChText( XclImpStream& rStrm ) +{ + XclImpChTextRef xText = std::make_shared<XclImpChText>( GetChRoot() ); + xText->ReadRecordGroup( rStrm ); + + switch( xText->GetLinkTarget() ) + { + case EXC_CHOBJLINK_XAXIS: mxXAxisTitle = xText; break; + case EXC_CHOBJLINK_YAXIS: mxYAxisTitle = xText; break; + case EXC_CHOBJLINK_ZAXIS: mxZAxisTitle = xText; break; + } +} + +void XclImpChAxesSet::ReadChPlotFrame( XclImpStream& rStrm ) +{ + if( (rStrm.GetNextRecId() == EXC_ID_CHFRAME) && rStrm.StartNextRecord() ) + { + mxPlotFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_PLOTFRAME ); + mxPlotFrame->ReadRecordGroup( rStrm ); + } +} + +void XclImpChAxesSet::ReadChTypeGroup( XclImpStream& rStrm ) +{ + XclImpChTypeGroupRef xTypeGroup = std::make_shared<XclImpChTypeGroup>( GetChRoot() ); + xTypeGroup->ReadRecordGroup( rStrm ); + sal_uInt16 nGroupIdx = xTypeGroup->GetGroupIdx(); + XclImpChTypeGroupMap::iterator itr = maTypeGroups.lower_bound(nGroupIdx); + if (itr != maTypeGroups.end() && !maTypeGroups.key_comp()(nGroupIdx, itr->first)) + // Overwrite the existing element. + itr->second = xTypeGroup; + else + maTypeGroups.insert( + itr, XclImpChTypeGroupMap::value_type(nGroupIdx, xTypeGroup)); +} + +Reference< XCoordinateSystem > XclImpChAxesSet::CreateCoordSystem( Reference< XDiagram > const & xDiagram ) const +{ + Reference< XCoordinateSystem > xCoordSystem; + + /* Try to get existing coordinate system. For now, all series from primary + and secondary axes sets are inserted into one coordinate system. Later, + this should be changed to use one coordinate system for each axes set. */ + Reference< XCoordinateSystemContainer > xCoordSystemCont( xDiagram, UNO_QUERY ); + if( xCoordSystemCont.is() ) + { + Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems(); + OSL_ENSURE( aCoordSystems.getLength() <= 1, "XclImpChAxesSet::CreateCoordSystem - too many existing coordinate systems" ); + if( aCoordSystems.hasElements() ) + xCoordSystem = aCoordSystems[ 0 ]; + } + + // create the coordinate system according to the first chart type + if( !xCoordSystem.is() ) + { + XclImpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup ) + { + xCoordSystem = xTypeGroup->CreateCoordSystem(); + // convert 3d chart settings + ScfPropertySet aDiaProp( xDiagram ); + xTypeGroup->ConvertChart3d( aDiaProp ); + } + } + + /* Create XChartType objects for all chart type groups. Each group will + add its series to the data provider attached to the chart document. */ + Reference< XChartTypeContainer > xChartTypeCont( xCoordSystem, UNO_QUERY ); + if( xChartTypeCont.is() ) + { + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + for( const auto& rEntry : maTypeGroups ) + { + try + { + Reference< XChartType > xChartType = rEntry.second->CreateChartType( xDiagram, nApiAxesSetIdx ); + if( xChartType.is() ) + xChartTypeCont->addChartType( xChartType ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::CreateCoordSystem - cannot add chart type" ); + } + } + } + + return xCoordSystem; +} + +void XclImpChAxesSet::ConvertAxis( + XclImpChAxisRef const & xChAxis, XclImpChTextRef const & xChAxisTitle, + Reference< XCoordinateSystem > const & xCoordSystem, const XclImpChAxis* pCrossingAxis ) const +{ + if( !xChAxis ) + return; + + // create and attach the axis object + Reference< XAxis > xAxis = CreateAxis( *xChAxis, pCrossingAxis ); + if( !xAxis.is() ) + return; + + // create and attach the axis title + if( xChAxisTitle ) try + { + Reference< XTitled > xTitled( xAxis, UNO_QUERY_THROW ); + Reference< XTitle > xTitle( xChAxisTitle->CreateTitle(), UNO_SET_THROW ); + xTitled->setTitleObject( xTitle ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::ConvertAxis - cannot set axis title" ); + } + + // insert axis into coordinate system + try + { + sal_Int32 nApiAxisDim = xChAxis->GetApiAxisDimension(); + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + xCoordSystem->setAxisByDimension( nApiAxisDim, xAxis, nApiAxesSetIdx ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::ConvertAxis - cannot set axis" ); + } +} + +Reference< XAxis > XclImpChAxesSet::CreateAxis( const XclImpChAxis& rChAxis, const XclImpChAxis* pCrossingAxis ) const +{ + Reference< XAxis > xAxis; + if( const XclImpChTypeGroup* pTypeGroup = GetFirstTypeGroup().get() ) + xAxis = rChAxis.CreateAxis( *pTypeGroup, pCrossingAxis ); + return xAxis; +} + +void XclImpChAxesSet::ConvertBackground( Reference< XDiagram > const & xDiagram ) const +{ + XclImpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup && xTypeGroup->Is3dWallChart() ) + { + // wall/floor formatting (3D charts) + if( mxXAxis ) + { + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxXAxis->ConvertWall( aWallProp ); + } + if( mxYAxis ) + { + ScfPropertySet aFloorProp( xDiagram->getFloor() ); + mxYAxis->ConvertWall( aFloorProp ); + } + } + else if( mxPlotFrame ) + { + // diagram background formatting + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxPlotFrame->Convert( aWallProp ); + } +} + +// The chart object =========================================================== + +XclImpChChart::XclImpChChart( const XclImpRoot& rRoot ) : + XclImpChRoot( rRoot, *this ) +{ + mxPrimAxesSet = std::make_shared<XclImpChAxesSet>( GetChRoot(), EXC_CHAXESSET_PRIMARY ); + mxSecnAxesSet = std::make_shared<XclImpChAxesSet>( GetChRoot(), EXC_CHAXESSET_SECONDARY ); +} + +XclImpChChart::~XclImpChChart() +{ +} + +void XclImpChChart::ReadHeaderRecord( XclImpStream& rStrm ) +{ + // coordinates are stored as 16.16 fixed point + rStrm >> maRect; +} + +void XclImpChChart::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAME: + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + mxFrame->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHSERIES: + ReadChSeries( rStrm ); + break; + case EXC_ID_CHPROPERTIES: + ReadChProperties( rStrm ); + break; + case EXC_ID_CHDEFAULTTEXT: + ReadChDefaultText( rStrm ); + break; + case EXC_ID_CHAXESSET: + ReadChAxesSet( rStrm ); + break; + case EXC_ID_CHTEXT: + ReadChText( rStrm ); + break; + case EXC_ID_CHEND: + Finalize(); // finalize the entire chart object + break; + } +} + +void XclImpChChart::ReadChDefaultText( XclImpStream& rStrm ) +{ + sal_uInt16 nTextId = rStrm.ReaduInt16(); + if( (rStrm.GetNextRecId() == EXC_ID_CHTEXT) && rStrm.StartNextRecord() ) + { + unique_ptr<XclImpChText> pText(new XclImpChText(GetChRoot())); + pText->ReadRecordGroup(rStrm); + m_DefTexts.insert(std::make_pair(nTextId, std::move(pText))); + } +} + +void XclImpChChart::ReadChDataFormat( XclImpStream& rStrm ) +{ + XclImpChDataFormatRef xDataFmt = std::make_shared<XclImpChDataFormat>( GetChRoot() ); + xDataFmt->ReadRecordGroup( rStrm ); + if( xDataFmt->GetPointPos().mnSeriesIdx <= EXC_CHSERIES_MAXSERIES ) + { + const XclChDataPointPos& rPos = xDataFmt->GetPointPos(); + XclImpChDataFormatMap::iterator itr = maDataFmts.lower_bound(rPos); + if (itr == maDataFmts.end() || maDataFmts.key_comp()(rPos, itr->first)) + // No element exists for this data point. Insert it. + maDataFmts.insert( + itr, XclImpChDataFormatMap::value_type(rPos, xDataFmt)); + + /* Do not overwrite existing data format group, Excel always uses the + first data format group occurring in any CHSERIES group. */ + } +} + +void XclImpChChart::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + if( !mxFrame ) + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + mxFrame->UpdateObjFrame( rLineData, rFillData ); +} + +XclImpChTypeGroupRef XclImpChChart::GetTypeGroup( sal_uInt16 nGroupIdx ) const +{ + XclImpChTypeGroupRef xTypeGroup = mxPrimAxesSet->GetTypeGroup( nGroupIdx ); + if( !xTypeGroup ) xTypeGroup = mxSecnAxesSet->GetTypeGroup( nGroupIdx ); + if( !xTypeGroup ) xTypeGroup = mxPrimAxesSet->GetFirstTypeGroup(); + return xTypeGroup; +} + +const XclImpChText* XclImpChChart::GetDefaultText( XclChTextType eTextType ) const +{ + sal_uInt16 nDefTextId = EXC_CHDEFTEXT_GLOBAL; + bool bBiff8 = GetBiff() == EXC_BIFF8; + switch( eTextType ) + { + case EXC_CHTEXTTYPE_TITLE: nDefTextId = EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_LEGEND: nDefTextId = EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_AXISTITLE: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_AXISLABEL: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_DATALABEL: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + } + + XclImpChTextMap::const_iterator const itr = m_DefTexts.find(nDefTextId); + return itr == m_DefTexts.end() ? nullptr : itr->second.get(); +} + +bool XclImpChChart::IsManualPlotArea() const +{ + // there is no real automatic mode in BIFF5 charts + return (GetBiff() <= EXC_BIFF5) || ::get_flag( maProps.mnFlags, EXC_CHPROPS_USEMANPLOTAREA ); +} + +void XclImpChChart::Convert( const Reference<XChartDocument>& xChartDoc, + XclImpDffConverter& rDffConv, const OUString& rObjName, const tools::Rectangle& rChartRect ) const +{ + // initialize conversion (locks the model to suppress any internal updates) + InitConversion( xChartDoc, rChartRect ); + + // chart frame formatting + if( mxFrame ) + { + ScfPropertySet aFrameProp( xChartDoc->getPageBackground() ); + mxFrame->Convert( aFrameProp ); + } + + // chart title + if( mxTitle ) try + { + Reference< XTitled > xTitled( xChartDoc, UNO_QUERY_THROW ); + Reference< XTitle > xTitle( mxTitle->CreateTitle(), UNO_SET_THROW ); + xTitled->setTitleObject( xTitle ); + } + catch( Exception& ) + { + } + + /* Create the diagram object and attach it to the chart document. Currently, + one diagram is used to carry all coordinate systems and data series. */ + Reference< XDiagram > xDiagram = CreateDiagram(); + xChartDoc->setFirstDiagram( xDiagram ); + + // coordinate systems and chart types, convert axis settings + mxPrimAxesSet->Convert( xDiagram ); + mxSecnAxesSet->Convert( xDiagram ); + + // legend + if( xDiagram.is() && mxLegend ) + xDiagram->setLegend( mxLegend->CreateLegend() ); + + /* Following all conversions needing the old Chart1 API that involves full + initialization of the chart view. */ + Reference< cssc::XChartDocument > xChart1Doc( xChartDoc, UNO_QUERY ); + if( xChart1Doc.is() ) + { + Reference< cssc::XDiagram > xDiagram1 = xChart1Doc->getDiagram(); + + /* Set the 'IncludeHiddenCells' property via the old API as only this + ensures that the data provider and all created sequences get this + flag correctly. */ + ScfPropertySet aDiaProp( xDiagram1 ); + bool bShowVisCells = ::get_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY ); + aDiaProp.SetBoolProperty( EXC_CHPROP_INCLUDEHIDDENCELLS, !bShowVisCells ); + + // plot area position and size (there is no real automatic mode in BIFF5 charts) + XclImpChFramePosRef xPlotAreaPos = mxPrimAxesSet->GetPlotAreaFramePos(); + if( IsManualPlotArea() && xPlotAreaPos ) try + { + const XclChFramePos& rFramePos = xPlotAreaPos->GetFramePosData(); + if( (rFramePos.mnTLMode == EXC_CHFRAMEPOS_PARENT) && (rFramePos.mnBRMode == EXC_CHFRAMEPOS_PARENT) ) + { + Reference< cssc::XDiagramPositioning > xPositioning( xDiagram1, UNO_QUERY_THROW ); + css::awt::Rectangle aDiagramRect = CalcHmmFromChartRect( rFramePos.maRect ); + // for pie charts, always set inner plot area size to exclude the data labels as Excel does + const XclImpChTypeGroup* pFirstTypeGroup = mxPrimAxesSet->GetFirstTypeGroup().get(); + if( pFirstTypeGroup && (pFirstTypeGroup->GetTypeInfo().meTypeCateg == EXC_CHTYPECATEG_PIE) ) + xPositioning->setDiagramPositionExcludingAxes( aDiagramRect ); + else if( pFirstTypeGroup && pFirstTypeGroup->Is3dChart() ) + xPositioning->setDiagramPositionIncludingAxesAndAxisTitles( aDiagramRect ); + else + xPositioning->setDiagramPositionIncludingAxes( aDiagramRect ); + } + } + catch( Exception& ) + { + } + + // positions of all title objects + if( mxTitle ) + mxTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_TITLE ) ); + mxPrimAxesSet->ConvertTitlePositions(); + mxSecnAxesSet->ConvertTitlePositions(); + } + + // unlock the model + FinishConversion( rDffConv ); + + // start listening to this chart + ScDocument& rDoc = GetRoot().GetDoc(); + ScChartListenerCollection* pChartCollection = rDoc.GetChartListenerCollection(); + if(!pChartCollection) + return; + + std::vector< ScTokenRef > aRefTokens; + for( const auto& rxSeries : maSeries ) + rxSeries->FillAllSourceLinks( aRefTokens ); + if( !aRefTokens.empty() ) + { + ::std::unique_ptr< ScChartListener > xListener( new ScChartListener( rObjName, rDoc, std::move(aRefTokens) ) ); + xListener->SetUsed( true ); + xListener->StartListeningTo(); + pChartCollection->insert( xListener.release() ); + } +} + +void XclImpChChart::ReadChSeries( XclImpStream& rStrm ) +{ + sal_uInt16 nNewSeriesIdx = static_cast< sal_uInt16 >( maSeries.size() ); + XclImpChSeriesRef xSeries = std::make_shared<XclImpChSeries>( GetChRoot(), nNewSeriesIdx ); + xSeries->ReadRecordGroup( rStrm ); + maSeries.push_back( xSeries ); +} + +void XclImpChChart::ReadChProperties( XclImpStream& rStrm ) +{ + maProps.mnFlags = rStrm.ReaduInt16(); + maProps.mnEmptyMode = rStrm.ReaduInt8(); +} + +void XclImpChChart::ReadChAxesSet( XclImpStream& rStrm ) +{ + XclImpChAxesSetRef xAxesSet = std::make_shared<XclImpChAxesSet>( GetChRoot(), EXC_CHAXESSET_NONE ); + xAxesSet->ReadRecordGroup( rStrm ); + switch( xAxesSet->GetAxesSetId() ) + { + case EXC_CHAXESSET_PRIMARY: mxPrimAxesSet = xAxesSet; break; + case EXC_CHAXESSET_SECONDARY: mxSecnAxesSet = xAxesSet; break; + } +} + +void XclImpChChart::ReadChText( XclImpStream& rStrm ) +{ + XclImpChTextRef xText = std::make_shared<XclImpChText>( GetChRoot() ); + xText->ReadRecordGroup( rStrm ); + switch( xText->GetLinkTarget() ) + { + case EXC_CHOBJLINK_TITLE: + mxTitle = xText; + break; + case EXC_CHOBJLINK_DATA: + { + sal_uInt16 nSeriesIdx = xText->GetPointPos().mnSeriesIdx; + if( nSeriesIdx < maSeries.size() ) + maSeries[ nSeriesIdx ]->SetDataLabel( xText ); + } + break; + } +} + +void XclImpChChart::Finalize() +{ + // finalize series (must be done first) + FinalizeSeries(); + // #i49218# legend may be attached to primary or secondary axes set + mxLegend = mxPrimAxesSet->GetLegend(); + if( !mxLegend ) + mxLegend = mxSecnAxesSet->GetLegend(); + if( mxLegend ) + mxLegend->Finalize(); + // axes sets, updates chart type group default formats -> must be called before FinalizeDataFormats() + mxPrimAxesSet->Finalize(); + mxSecnAxesSet->Finalize(); + // formatting of all series + FinalizeDataFormats(); + // #i47745# missing frame -> invisible border and area + if( !mxFrame ) + mxFrame = std::make_shared<XclImpChFrame>( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + // chart title + FinalizeTitle(); +} + +void XclImpChChart::FinalizeSeries() +{ + for( const XclImpChSeriesRef& xSeries : maSeries ) + { + if( xSeries->HasParentSeries() ) + { + /* Process child series (trend lines and error bars). Data of + child series will be set at the connected parent series. */ + if( xSeries->GetParentIdx() < maSeries.size() ) + maSeries[ xSeries->GetParentIdx() ]->AddChildSeries( *xSeries ); + } + else + { + // insert the series into the related chart type group + if( XclImpChTypeGroup* pTypeGroup = GetTypeGroup( xSeries->GetGroupIdx() ).get() ) + pTypeGroup->AddSeries( xSeries ); + } + } +} + +void XclImpChChart::FinalizeDataFormats() +{ + /* #i51639# (part 1): CHDATAFORMAT groups are part of CHSERIES groups. + Each CHDATAFORMAT group specifies the series and data point it is + assigned to. This makes it possible to have a data format that is + related to another series, e.g. a CHDATAFORMAT group for series 2 is + part of a CHSERIES group that describes series 1. Therefore the chart + itself has collected all CHDATAFORMAT groups to be able to store data + format groups for series that have not been imported at that time. This + loop finally assigns these groups to the related series. */ + for( const auto& [rPos, rDataFmt] : maDataFmts ) + { + sal_uInt16 nSeriesIdx = rPos.mnSeriesIdx; + if( nSeriesIdx < maSeries.size() ) + maSeries[ nSeriesIdx ]->SetDataFormat( rDataFmt ); + } + + /* #i51639# (part 2): Finalize data formats of all series. This adds for + example missing CHDATAFORMAT groups for entire series that are needed + for automatic colors of lines and areas. */ + for( auto& rxSeries : maSeries ) + rxSeries->FinalizeDataFormats(); +} + +void XclImpChChart::FinalizeTitle() +{ + // special handling for auto-generated title + OUString aAutoTitle; + if( !mxTitle || (!mxTitle->IsDeleted() && !mxTitle->HasString()) ) + { + // automatic title from first series name (if there are no series on secondary axes set) + if( !mxSecnAxesSet->IsValidAxesSet() ) + aAutoTitle = mxPrimAxesSet->GetSingleSeriesTitle(); + if( mxTitle || (!aAutoTitle.isEmpty()) ) + { + if( !mxTitle ) + mxTitle = std::make_shared<XclImpChText>( GetChRoot() ); + if( aAutoTitle.isEmpty() ) + aAutoTitle = ScResId(STR_CHARTTITLE); + } + } + + // will reset mxTitle, if it does not contain a string and no auto title exists + lclFinalizeTitle( mxTitle, GetDefaultText( EXC_CHTEXTTYPE_TITLE ), aAutoTitle ); +} + +Reference< XDiagram > XclImpChChart::CreateDiagram() const +{ + // create a diagram object + Reference< XDiagram > xDiagram( ScfApiHelper::CreateInstance( SERVICE_CHART2_DIAGRAM ), UNO_QUERY ); + + // convert global chart settings + ScfPropertySet aDiaProp( xDiagram ); + + // treatment of missing values + using namespace cssc::MissingValueTreatment; + sal_Int32 nMissingValues = LEAVE_GAP; + switch( maProps.mnEmptyMode ) + { + case EXC_CHPROPS_EMPTY_SKIP: nMissingValues = LEAVE_GAP; break; + case EXC_CHPROPS_EMPTY_ZERO: nMissingValues = USE_ZERO; break; + case EXC_CHPROPS_EMPTY_INTERPOLATE: nMissingValues = CONTINUE; break; + } + aDiaProp.SetProperty( EXC_CHPROP_MISSINGVALUETREATMENT, nMissingValues ); + + return xDiagram; +} + +XclImpChartDrawing::XclImpChartDrawing( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpDrawing( rRoot, bOwnTab ), // sheet charts may contain OLE objects + mnScTab( rRoot.GetCurrScTab() ), + mbOwnTab( bOwnTab ) +{ +} + +void XclImpChartDrawing::ConvertObjects( XclImpDffConverter& rDffConv, + const Reference< XModel >& rxModel, const tools::Rectangle& rChartRect ) +{ + maChartRect = rChartRect; // needed in CalcAnchorRect() callback + + SdrModel* pSdrModel = nullptr; + SdrPage* pSdrPage = nullptr; + if( mbOwnTab ) + { + // chart sheet: insert all shapes into the sheet, not into the chart object + pSdrModel = GetDoc().GetDrawLayer(); + pSdrPage = GetSdrPage( mnScTab ); + } + else + { + // embedded chart object: insert all shapes into the chart + try + { + Reference< XDrawPageSupplier > xDrawPageSupp( rxModel, UNO_QUERY_THROW ); + Reference< XDrawPage > xDrawPage( xDrawPageSupp->getDrawPage(), UNO_SET_THROW ); + pSdrPage = ::GetSdrPageFromXDrawPage( xDrawPage ); + pSdrModel = pSdrPage ? &pSdrPage->getSdrModelFromSdrPage() : nullptr; + } + catch( Exception& ) + { + } + } + + if( pSdrModel && pSdrPage ) + ImplConvertObjects( rDffConv, *pSdrModel, *pSdrPage ); +} + +tools::Rectangle XclImpChartDrawing::CalcAnchorRect( const XclObjAnchor& rAnchor, bool bDffAnchor ) const +{ + /* In objects with DFF client anchor, the position of the shape is stored + in the cell address components of the client anchor. In old BIFF3-BIFF5 + objects, the position is stored in the offset components of the anchor. */ + tools::Rectangle aRect( + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maFirst.mnCol : rAnchor.mnLX ) / EXC_CHART_TOTALUNITS * maChartRect.GetWidth() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maFirst.mnRow : rAnchor.mnTY ) / EXC_CHART_TOTALUNITS * maChartRect.GetHeight() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maLast.mnCol : rAnchor.mnRX ) / EXC_CHART_TOTALUNITS * maChartRect.GetWidth() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maLast.mnRow : rAnchor.mnBY ) / EXC_CHART_TOTALUNITS * maChartRect.GetHeight() + 0.5 ) ); + aRect.Justify(); + // move shapes into chart area for sheet charts + if( mbOwnTab ) + aRect.Move( maChartRect.Left(), maChartRect.Top() ); + return aRect; +} + +void XclImpChartDrawing::OnObjectInserted( const XclImpDrawObjBase& ) +{ +} + +XclImpChart::XclImpChart( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpRoot( rRoot ), + mbOwnTab( bOwnTab ), + mbIsPivotChart( false ) +{ +} + +XclImpChart::~XclImpChart() +{ +} + +void XclImpChart::ReadChartSubStream( XclImpStream& rStrm ) +{ + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + // page settings - only for charts in entire sheet + if( mbOwnTab ) switch( rStrm.GetRecId() ) + { + case EXC_ID_HORPAGEBREAKS: + case EXC_ID_VERPAGEBREAKS: rPageSett.ReadPageBreaks( rStrm ); break; + case EXC_ID_HEADER: + case EXC_ID_FOOTER: rPageSett.ReadHeaderFooter( rStrm ); break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: rPageSett.ReadMargin( rStrm ); break; + case EXC_ID_PRINTHEADERS: rPageSett.ReadPrintHeaders( rStrm ); break; + case EXC_ID_PRINTGRIDLINES: rPageSett.ReadPrintGridLines( rStrm ); break; + case EXC_ID_HCENTER: + case EXC_ID_VCENTER: rPageSett.ReadCenter( rStrm ); break; + case EXC_ID_SETUP: rPageSett.ReadSetup( rStrm ); break; + case EXC_ID8_IMGDATA: rPageSett.ReadImgData( rStrm ); break; + + case EXC_ID_WINDOW2: rTabViewSett.ReadWindow2( rStrm, true );break; + case EXC_ID_SCL: rTabViewSett.ReadScl( rStrm ); break; + + case EXC_ID_SHEETEXT: //0x0862 + { + // FIXME: do not need to pass palette, XclImpTabVieSettings is derived from root + XclImpPalette& rPal = GetPalette(); + rTabViewSett.ReadTabBgColor( rStrm, rPal); + } + break; + + case EXC_ID_CODENAME: ReadCodeName( rStrm, false ); break; + } + + // common records + switch( rStrm.GetRecId() ) + { + case EXC_ID_EOF: bLoop = false; break; + + // #i31882# ignore embedded chart objects + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( rStrm ); break; + + case EXC_ID_CHCHART: ReadChChart( rStrm ); break; + + case EXC_ID8_CHPIVOTREF: + GetTracer().TracePivotChartExists(); + mbIsPivotChart = true; + break; + + // BIFF specific records + default: switch( GetBiff() ) + { + case EXC_BIFF5: switch( rStrm.GetRecId() ) + { + case EXC_ID_OBJ: GetChartDrawing().ReadObj( rStrm ); break; + } + break; + case EXC_BIFF8: switch( rStrm.GetRecId() ) + { + case EXC_ID_MSODRAWING: GetChartDrawing().ReadMsoDrawing( rStrm ); break; + // #i61786# weird documents: OBJ without MSODRAWING -> read in BIFF5 format + case EXC_ID_OBJ: GetChartDrawing().ReadObj( rStrm ); break; + } + break; + default:; + } + } + } +} + +void XclImpChart::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + if( !mxChartData ) + mxChartData = std::make_shared<XclImpChChart>( GetRoot() ); + mxChartData->UpdateObjFrame( rLineData, rFillData ); +} + +std::size_t XclImpChart::GetProgressSize() const +{ + return + (mxChartData ? XclImpChChart::GetProgressSize() : 0) + + (mxChartDrawing ? mxChartDrawing->GetProgressSize() : 0); +} + +void XclImpChart::Convert( Reference< XModel > const & xModel, XclImpDffConverter& rDffConv, const OUString& rObjName, const tools::Rectangle& rChartRect ) const +{ + Reference< XChartDocument > xChartDoc( xModel, UNO_QUERY ); + if( xChartDoc.is() ) + { + if( mxChartData ) + mxChartData->Convert( xChartDoc, rDffConv, rObjName, rChartRect ); + if( mxChartDrawing ) + mxChartDrawing->ConvertObjects( rDffConv, xModel, rChartRect ); + } +} + +XclImpChartDrawing& XclImpChart::GetChartDrawing() +{ + if( !mxChartDrawing ) + mxChartDrawing = std::make_shared<XclImpChartDrawing>( GetRoot(), mbOwnTab ); + return *mxChartDrawing; +} + +void XclImpChart::ReadChChart( XclImpStream& rStrm ) +{ + mxChartData = std::make_shared<XclImpChChart>( GetRoot() ); + mxChartData->ReadRecordGroup( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xicontent.cxx b/sc/source/filter/excel/xicontent.cxx new file mode 100644 index 000000000..872632a1c --- /dev/null +++ b/sc/source/filter/excel/xicontent.cxx @@ -0,0 +1,1442 @@ +/* -*- 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 <com/sun/star/sheet/TableValidationVisibility.hpp> +#include <xicontent.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <sfx2/linkmgr.hxx> +#include <svl/itemset.hxx> +#include <scitems.hxx> +#include <editeng/eeitem.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/editobj.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <stringutil.hxx> +#include <cellform.hxx> +#include <cellvalue.hxx> +#include <document.hxx> +#include <editutil.hxx> +#include <validat.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <rangenam.hxx> +#include <arealink.hxx> +#include <stlsheet.hxx> +#include <xlcontent.hxx> +#include <xlformula.hxx> +#include <xltracer.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xistyle.hxx> +#include <xiescher.hxx> +#include <xiname.hxx> + +#include <excform.hxx> +#include <tabprotection.hxx> +#include <documentimport.hxx> + +#include <memory> +#include <utility> +#include <oox/helper/helper.hxx> +#include <sal/log.hxx> + +using ::com::sun::star::uno::Sequence; +using ::std::unique_ptr; + +// Shared string table ======================================================== + +XclImpSst::XclImpSst( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpSst::ReadSst( XclImpStream& rStrm ) +{ + rStrm.Ignore( 4 ); + sal_uInt32 nStrCount = rStrm.ReaduInt32(); + auto nBytesAvailable = rStrm.GetRecLeft(); + if (nStrCount > nBytesAvailable) + { + SAL_WARN("sc.filter", "xls claimed to have " << nStrCount << " strings, but only " << nBytesAvailable << " bytes available, truncating"); + nStrCount = nBytesAvailable; + } + maStrings.clear(); + maStrings.reserve(nStrCount); + while( (nStrCount > 0) && rStrm.IsValid() ) + { + XclImpString aString; + aString.Read( rStrm ); + maStrings.push_back( aString ); + --nStrCount; + } +} + +const XclImpString* XclImpSst::GetString( sal_uInt32 nSstIndex ) const +{ + return (nSstIndex < maStrings.size()) ? &maStrings[ nSstIndex ] : nullptr; +} + +// Hyperlinks ================================================================= + +namespace { + +/** Reads character array and stores it into rString. + @param nChars Number of following characters (not byte count!). + @param b16Bit true = 16-bit characters, false = 8-bit characters. */ +void lclAppendString32( OUString& rString, XclImpStream& rStrm, sal_uInt32 nChars, bool b16Bit ) +{ + sal_uInt16 nReadChars = ulimit_cast< sal_uInt16 >( nChars ); + rString += rStrm.ReadRawUniString( nReadChars, b16Bit ); + // ignore remaining chars + std::size_t nIgnore = nChars - nReadChars; + if( b16Bit ) + nIgnore *= 2; + rStrm.Ignore( nIgnore ); +} + +/** Reads 32-bit string length and the character array and stores it into rString. + @param b16Bit true = 16-bit characters, false = 8-bit characters. */ +void lclAppendString32( OUString& rString, XclImpStream& rStrm, bool b16Bit ) +{ + lclAppendString32( rString, rStrm, rStrm.ReaduInt32(), b16Bit ); +} + +/** Reads 32-bit string length and ignores following 16-bit character array. */ +void lclIgnoreString32( XclImpStream& rStrm ) +{ + sal_uInt32 nChars = rStrm.ReaduInt32(); + nChars *= 2; + rStrm.Ignore( nChars ); +} + +/** Converts a path to an absolute path. + @param rPath The source path. The resulting path is returned here. + @param nLevel Number of parent directories to add in front of the path. */ +void lclGetAbsPath( OUString& rPath, sal_uInt16 nLevel, const SfxObjectShell* pDocShell ) +{ + OUStringBuffer aTmpStr; + while( nLevel ) + { + aTmpStr.append( "../" ); + --nLevel; + } + aTmpStr.append( rPath ); + + if( pDocShell ) + { + bool bWasAbs = false; + rPath = pDocShell->GetMedium()->GetURLObject().smartRel2Abs( aTmpStr.makeStringAndClear(), bWasAbs ).GetMainURL( INetURLObject::DecodeMechanism::NONE ); + // full path as stored in SvxURLField must be encoded + } + else + rPath = aTmpStr.makeStringAndClear(); +} + +/** Inserts the URL into a text cell. Does not modify value or formula cells. */ +void lclInsertUrl( XclImpRoot& rRoot, const OUString& rUrl, SCCOL nScCol, SCROW nScRow, SCTAB nScTab ) +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + ScAddress aScPos( nScCol, nScRow, nScTab ); + ScRefCellValue aCell(rDoc.getDoc(), aScPos); + switch( aCell.meType ) + { + // #i54261# hyperlinks in string cells + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + { + sal_uInt32 nNumFmt = rDoc.getDoc().GetNumberFormat(rDoc.getDoc().GetNonThreadedContext(), aScPos); + SvNumberFormatter* pFormatter = rDoc.getDoc().GetFormatTable(); + const Color* pColor; + OUString aDisplText = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, rDoc.getDoc()); + if (aDisplText.isEmpty()) + aDisplText = rUrl; + + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + SvxURLField aUrlField( rUrl, aDisplText, SvxURLFormat::AppDefault ); + + if( aCell.meType == CELLTYPE_EDIT ) + { + const EditTextObject* pEditObj = aCell.mpEditText; + rEE.SetTextCurrentDefaults( *pEditObj ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), ESelection( 0, 0, EE_PARA_ALL, 0 ) ); + } + else + { + rEE.SetTextCurrentDefaults( OUString() ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), ESelection() ); + if( const ScPatternAttr* pPattern = rDoc.getDoc().GetPattern( aScPos.Col(), aScPos.Row(), nScTab ) ) + { + SfxItemSet aItemSet( rEE.GetEmptyItemSet() ); + pPattern->FillEditItemSet( &aItemSet ); + rEE.QuickSetAttribs( aItemSet, ESelection( 0, 0, EE_PARA_ALL, 0 ) ); + } + } + + // The cell will own the text object instance. + rDoc.setEditCell(aScPos, rEE.CreateTextObject()); + } + break; + + default: + // Handle other cell types e.g. formulas ( and ? ) that have associated + // hyperlinks. + // Ideally all hyperlinks should be treated as below. For the moment, + // given the current absence of ods support lets just handle what we + // previously didn't handle the new way. + // Unfortunately we won't be able to preserve such hyperlinks when + // saving to ods. Note: when we are able to save such hyperlinks to ods + // we should handle *all* imported hyperlinks as below ( e.g. as cell + // attribute ) for better interoperability. + { + SfxStringItem aItem( ATTR_HYPERLINK, rUrl ); + rDoc.getDoc().ApplyAttr(nScCol, nScRow, nScTab, aItem); + break; + } + } +} + +} // namespace + +void XclImpHyperlink::ReadHlink( XclImpStream& rStrm ) +{ + XclRange aXclRange( ScAddress::UNINITIALIZED ); + rStrm >> aXclRange; + // #i80006# Excel silently ignores invalid hi-byte of column index (TODO: everywhere?) + aXclRange.maFirst.mnCol &= 0xFF; + aXclRange.maLast.mnCol &= 0xFF; + OUString aString = ReadEmbeddedData( rStrm ); + if ( !aString.isEmpty() ) + rStrm.GetRoot().GetXFRangeBuffer().SetHyperlink( aXclRange, aString ); +} + +OUString XclImpHyperlink::ReadEmbeddedData( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + SfxObjectShell* pDocShell = rRoot.GetDocShell(); + + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + XclGuid aGuid; + rStrm >> aGuid; + rStrm.Ignore( 4 ); + sal_uInt32 nFlags = rStrm.ReaduInt32(); + + OSL_ENSURE( aGuid == XclTools::maGuidStdLink, "XclImpHyperlink::ReadEmbeddedData - unknown header GUID" ); + + ::std::unique_ptr< OUString > xLongName; // link / file name + ::std::unique_ptr< OUString > xShortName; // 8.3-representation of file name + ::std::unique_ptr< OUString > xTextMark; // text mark + + // description (ignore) + if( ::get_flag( nFlags, EXC_HLINK_DESCR ) ) + lclIgnoreString32( rStrm ); + // target frame (ignore) !! DESCR/FRAME - is this the right order? (never seen them together) + if( ::get_flag( nFlags, EXC_HLINK_FRAME ) ) + lclIgnoreString32( rStrm ); + + // URL fields are zero-terminated - do not let the stream replace them + // in the lclAppendString32() with the '?' character. + rStrm.SetNulSubstChar( '\0' ); + + // UNC path + if( ::get_flag( nFlags, EXC_HLINK_UNC ) ) + { + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, true ); + lclGetAbsPath( *xLongName, 0, pDocShell ); + } + // file link or URL + else if( ::get_flag( nFlags, EXC_HLINK_BODY ) ) + { + rStrm >> aGuid; + + if( aGuid == XclTools::maGuidFileMoniker ) + { + sal_uInt16 nLevel = rStrm.ReaduInt16(); // counter for level to climb down in path + xShortName.reset( new OUString ); + lclAppendString32( *xShortName, rStrm, false ); + rStrm.Ignore( 24 ); + + sal_uInt32 nStrLen = rStrm.ReaduInt32(); + if( nStrLen ) + { + nStrLen = rStrm.ReaduInt32(); + nStrLen /= 2; // it's byte count here... + rStrm.Ignore( 2 ); + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, nStrLen, true ); + lclGetAbsPath( *xLongName, nLevel, pDocShell ); + } + else + lclGetAbsPath( *xShortName, nLevel, pDocShell ); + } + else if( aGuid == XclTools::maGuidUrlMoniker ) + { + sal_uInt32 nStrLen = rStrm.ReaduInt32(); + nStrLen /= 2; // it's byte count here... + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, nStrLen, true ); + if( !::get_flag( nFlags, EXC_HLINK_ABS ) ) + lclGetAbsPath( *xLongName, 0, pDocShell ); + } + else + { + OSL_FAIL( "XclImpHyperlink::ReadEmbeddedData - unknown content GUID" ); + } + } + + // text mark + if( ::get_flag( nFlags, EXC_HLINK_MARK ) ) + { + xTextMark.reset( new OUString ); + lclAppendString32( *xTextMark, rStrm, true ); + } + + rStrm.SetNulSubstChar(); // back to default + + OSL_ENSURE( rStrm.GetRecLeft() == 0, "XclImpHyperlink::ReadEmbeddedData - record size mismatch" ); + + if (!xLongName && xShortName) + xLongName = std::move(xShortName); + else if (!xLongName && xTextMark) + xLongName.reset( new OUString ); + + if (xLongName) + { + if (xTextMark) + { + if( xLongName->isEmpty() ) + { + sal_Int32 nSepPos = xTextMark->lastIndexOf( '!' ); + if( nSepPos > 0 ) + { + // Do not attempt to blindly convert '#SheetName!A1' to + // '#SheetName.A1', it can be #SheetName!R1C1 as well. + // Hyperlink handler has to handle all, but prefer + // '#SheetName.A1' if possible. + if (nSepPos < xTextMark->getLength() - 1) + { + ScDocument& rDoc = rRoot.GetDoc(); + ScRange aRange; + if ((aRange.ParseAny( xTextMark->copy( nSepPos + 1 ), rDoc, formula::FormulaGrammar::CONV_XL_R1C1) + & ScRefFlags::VALID) == ScRefFlags::ZERO) + xTextMark.reset( new OUString( xTextMark->replaceAt( nSepPos, 1, rtl::OUStringChar( '.' )))); + } + } + } + xLongName.reset( new OUString( *xLongName + "#" + *xTextMark ) ); + } + return( *xLongName ); + } + return( OUString() ); +} + +void XclImpHyperlink::ConvertToValidTabName(OUString& rUrl) +{ + sal_Int32 n = rUrl.getLength(); + if (n < 4) + // Needs at least 4 characters. + return; + + if (rUrl[0] != '#') + // the 1st character must be '#'. + return; + + OUStringBuffer aNewUrl("#"); + OUStringBuffer aTabName; + + bool bInQuote = false; + bool bQuoteTabName = false; + for( sal_Int32 i = 1; i < n; ++i ) + { + sal_Unicode c = rUrl[i]; + if (c == '\'') + { + if (bInQuote && i+1 < n && rUrl[i+1] == '\'') + { + // Two consecutive single quotes ('') signify a single literal + // quite. When this occurs, the whole table name needs to be + // quoted. + bQuoteTabName = true; + aTabName.append(c).append(c); + ++i; + continue; + } + + bInQuote = !bInQuote; + if (!bInQuote && !aTabName.isEmpty()) + { + if (bQuoteTabName) + aNewUrl.append("'"); + aNewUrl.append(aTabName); + if (bQuoteTabName) + aNewUrl.append("'"); + } + } + else if (bInQuote) + aTabName.append(c); + else + aNewUrl.append(c); + } + + if (bInQuote) + // It should be outside the quotes! + return; + + // All is good. Pass the new URL. + rUrl = aNewUrl.makeStringAndClear(); +} + +void XclImpHyperlink::InsertUrl( XclImpRoot& rRoot, const XclRange& rXclRange, const OUString& rUrl ) +{ + OUString aUrl(rUrl); + ConvertToValidTabName(aUrl); + + SCTAB nScTab = rRoot.GetCurrScTab(); + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( rRoot.GetAddressConverter().ConvertRange( aScRange, rXclRange, nScTab, nScTab, true ) ) + { + SCCOL nScCol1, nScCol2; + SCROW nScRow1, nScRow2; + aScRange.GetVars( nScCol1, nScRow1, nScTab, nScCol2, nScRow2, nScTab ); + + if (utl::ConfigManager::IsFuzzing()) + { + SCROW nRows = nScRow2 - nScRow1; + if (nRows > 1024) + { + SAL_WARN("sc.filter", "for fuzzing performance, clamped hyperlink apply range end row from " << nScRow2 << " to " << nScRow1 + 1024); + nScRow2 = nScRow1 + 1024; + } + } + + for( SCCOL nScCol = nScCol1; nScCol <= nScCol2; ++nScCol ) + for( SCROW nScRow = nScRow1; nScRow <= nScRow2; ++nScRow ) + lclInsertUrl( rRoot, aUrl, nScCol, nScRow, nScTab ); + } +} + +// Label ranges =============================================================== + +void XclImpLabelranges::ReadLabelranges( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + ScDocument& rDoc = rRoot.GetDoc(); + SCTAB nScTab = rRoot.GetCurrScTab(); + XclImpAddressConverter& rAddrConv = rRoot.GetAddressConverter(); + ScRangePairListRef xLabelRangesRef; + + XclRangeList aRowXclRanges, aColXclRanges; + rStrm >> aRowXclRanges >> aColXclRanges; + + // row label ranges + ScRangeList aRowScRanges; + rAddrConv.ConvertRangeList( aRowScRanges, aRowXclRanges, nScTab, false ); + xLabelRangesRef = rDoc.GetRowNameRangesRef(); + for ( size_t i = 0, nRanges = aRowScRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = aRowScRanges[ i ]; + ScRange aDataRange( rScRange ); + if( aDataRange.aEnd.Col() < rDoc.MaxCol() ) + { + aDataRange.aStart.SetCol( aDataRange.aEnd.Col() + 1 ); + aDataRange.aEnd.SetCol( rDoc.MaxCol() ); + } + else if( aDataRange.aStart.Col() > 0 ) + { + aDataRange.aEnd.SetCol( aDataRange.aStart.Col() - 1 ); + aDataRange.aStart.SetCol( 0 ); + } + xLabelRangesRef->Append( ScRangePair( rScRange, aDataRange ) ); + } + + // column label ranges + ScRangeList aColScRanges; + rAddrConv.ConvertRangeList( aColScRanges, aColXclRanges, nScTab, false ); + xLabelRangesRef = rDoc.GetColNameRangesRef(); + + for ( size_t i = 0, nRanges = aColScRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = aColScRanges[ i ]; + ScRange aDataRange( rScRange ); + if( aDataRange.aEnd.Row() < rDoc.MaxRow() ) + { + aDataRange.aStart.SetRow( aDataRange.aEnd.Row() + 1 ); + aDataRange.aEnd.SetRow( rDoc.MaxRow() ); + } + else if( aDataRange.aStart.Row() > 0 ) + { + aDataRange.aEnd.SetRow( aDataRange.aStart.Row() - 1 ); + aDataRange.aStart.SetRow( 0 ); + } + xLabelRangesRef->Append( ScRangePair( rScRange, aDataRange ) ); + } +} + +// Conditional formatting ===================================================== + +XclImpCondFormat::XclImpCondFormat( const XclImpRoot& rRoot, sal_uInt32 nFormatIndex ) : + XclImpRoot( rRoot ), + mnFormatIndex( nFormatIndex ), + mnCondCount( 0 ), + mnCondIndex( 0 ) +{ +} + +XclImpCondFormat::~XclImpCondFormat() +{ +} + +void XclImpCondFormat::ReadCondfmt( XclImpStream& rStrm ) +{ + OSL_ENSURE( !mnCondCount, "XclImpCondFormat::ReadCondfmt - already initialized" ); + XclRangeList aXclRanges; + mnCondCount = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + rStrm >> aXclRanges; + GetAddressConverter().ConvertRangeList( maRanges, aXclRanges, GetCurrScTab(), true ); +} + +void XclImpCondFormat::ReadCF( XclImpStream& rStrm ) +{ + if( mnCondIndex >= mnCondCount ) + { + OSL_FAIL( "XclImpCondFormat::ReadCF - CF without leading CONDFMT" ); + return; + } + + // entire conditional format outside of valid range? + if( maRanges.empty() ) + return; + + sal_uInt8 nType = rStrm.ReaduInt8(); + sal_uInt8 nOperator = rStrm.ReaduInt8(); + sal_uInt16 nFmlaSize1 = rStrm.ReaduInt16(); + sal_uInt16 nFmlaSize2 = rStrm.ReaduInt16(); + sal_uInt32 nFlags = rStrm.ReaduInt32(); + rStrm.Ignore( 2 ); //nFlagsExtended + + // *** mode and comparison operator *** + + ScConditionMode eMode = ScConditionMode::NONE; + switch( nType ) + { + case EXC_CF_TYPE_CELL: + { + switch( nOperator ) + { + case EXC_CF_CMP_BETWEEN: eMode = ScConditionMode::Between; break; + case EXC_CF_CMP_NOT_BETWEEN: eMode = ScConditionMode::NotBetween; break; + case EXC_CF_CMP_EQUAL: eMode = ScConditionMode::Equal; break; + case EXC_CF_CMP_NOT_EQUAL: eMode = ScConditionMode::NotEqual; break; + case EXC_CF_CMP_GREATER: eMode = ScConditionMode::Greater; break; + case EXC_CF_CMP_LESS: eMode = ScConditionMode::Less; break; + case EXC_CF_CMP_GREATER_EQUAL: eMode = ScConditionMode::EqGreater; break; + case EXC_CF_CMP_LESS_EQUAL: eMode = ScConditionMode::EqLess; break; + default: + SAL_INFO( + "sc.filter", "unknown CF comparison " << nOperator); + } + } + break; + + case EXC_CF_TYPE_FMLA: + eMode = ScConditionMode::Direct; + break; + + default: + SAL_INFO("sc.filter", "unknown CF mode " << nType); + return; + } + + // *** create style sheet *** + + OUString aStyleName( XclTools::GetCondFormatStyleName( GetCurrScTab(), mnFormatIndex, mnCondIndex ) ); + SfxItemSet& rStyleItemSet = ScfTools::MakeCellStyleSheet( GetStyleSheetPool(), aStyleName, true ).GetItemSet(); + + const XclImpPalette& rPalette = GetPalette(); + + // number format + + if( get_flag( nFlags, EXC_CF_BLOCK_NUMFMT ) ) + { + XclImpNumFmtBuffer& rNumFmtBuffer = GetRoot().GetNumFmtBuffer(); + bool bIFmt = get_flag( nFlags, EXC_CF_IFMT_USER ); + sal_uInt16 nFormat = rNumFmtBuffer.ReadCFFormat( rStrm, bIFmt ); + rNumFmtBuffer.FillToItemSet( rStyleItemSet, nFormat ); + } + + // *** font block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_FONT ) ) + { + XclImpFont aFont( GetRoot() ); + aFont.ReadCFFontBlock( rStrm ); + aFont.FillToItemSet( rStyleItemSet, XclFontItemType::Cell ); + } + + // alignment + if( get_flag( nFlags, EXC_CF_BLOCK_ALIGNMENT ) ) + { + XclImpCellAlign aAlign; + sal_uInt16 nAlign(0); + sal_uInt16 nAlignMisc(0); + nAlign = rStrm.ReaduInt16(); + nAlignMisc = rStrm.ReaduInt16(); + aAlign.FillFromCF( nAlign, nAlignMisc ); + aAlign.FillToItemSet( rStyleItemSet, nullptr ); + rStrm.Ignore(4); + } + + // *** border block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_BORDER ) ) + { + sal_uInt16 nLineStyle(0); + sal_uInt32 nLineColor(0); + nLineStyle = rStrm.ReaduInt16(); + nLineColor = rStrm.ReaduInt32(); + rStrm.Ignore( 2 ); + + XclImpCellBorder aBorder; + aBorder.FillFromCF8( nLineStyle, nLineColor, nFlags ); + aBorder.FillToItemSet( rStyleItemSet, rPalette ); + } + + // *** pattern block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_AREA ) ) + { + sal_uInt16 nPattern(0), nColor(0); + nPattern = rStrm.ReaduInt16(); + nColor = rStrm.ReaduInt16(); + + XclImpCellArea aArea; + aArea.FillFromCF8( nPattern, nColor, nFlags ); + aArea.FillToItemSet( rStyleItemSet, rPalette ); + } + + if( get_flag( nFlags, EXC_CF_BLOCK_PROTECTION ) ) + { + sal_uInt16 nCellProt; + nCellProt = rStrm.ReaduInt16(); + XclImpCellProt aCellProt; + aCellProt.FillFromXF3(nCellProt); + aCellProt.FillToItemSet( rStyleItemSet ); + } + + // *** formulas *** + + const ScAddress& rPos = maRanges.front().aStart; // assured above that maRanges is not empty + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + + ::std::unique_ptr< ScTokenArray > xTokArr1; + if( nFmlaSize1 > 0 ) + { + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset( rPos ); + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize1, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + { + xTokArr1 = std::move( pTokArr ); + GetDoc().CheckLinkFormulaNeedingCheck( *xTokArr1); + } + } + + ::std::unique_ptr< ScTokenArray > xTokArr2; + if( nFmlaSize2 > 0 ) + { + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset( rPos ); + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize2, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + { + xTokArr2 = std::move( pTokArr ); + GetDoc().CheckLinkFormulaNeedingCheck( *xTokArr2); + } + } + + // *** create the Calc conditional formatting *** + + const ScAddress aPos(rPos); //in case maRanges.Join invalidates it + + if( !mxScCondFmt ) + { + mxScCondFmt.reset( new ScConditionalFormat( 0/*nKey*/, &GetDoc() ) ); + if(maRanges.size() > 1) + maRanges.Join(maRanges[0], true); + mxScCondFmt->SetRange(maRanges); + } + + ScCondFormatEntry* pEntry = new ScCondFormatEntry(eMode, xTokArr1.get(), xTokArr2.get(), GetDoc(), aPos, aStyleName); + mxScCondFmt->AddEntry( pEntry ); + ++mnCondIndex; +} + +void XclImpCondFormat::Apply() +{ + if( mxScCondFmt ) + { + ScDocument& rDoc = GetDoc(); + + SCTAB nTab = maRanges.front().aStart.Tab(); + sal_uLong nKey = rDoc.AddCondFormat( mxScCondFmt->Clone(), nTab ); + + rDoc.AddCondFormatData( maRanges, nTab, nKey ); + } +} + +XclImpCondFormatManager::XclImpCondFormatManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpCondFormatManager::ReadCondfmt( XclImpStream& rStrm ) +{ + XclImpCondFormat* pFmt = new XclImpCondFormat( GetRoot(), maCondFmtList.size() ); + pFmt->ReadCondfmt( rStrm ); + maCondFmtList.push_back( std::unique_ptr<XclImpCondFormat>(pFmt) ); +} + +void XclImpCondFormatManager::ReadCF( XclImpStream& rStrm ) +{ + OSL_ENSURE( !maCondFmtList.empty(), "XclImpCondFormatManager::ReadCF - CF without leading CONDFMT" ); + if( !maCondFmtList.empty() ) + maCondFmtList.back()->ReadCF( rStrm ); +} + +void XclImpCondFormatManager::Apply() +{ + for( auto& rxFmt : maCondFmtList ) + rxFmt->Apply(); + maCondFmtList.clear(); +} + +// Data Validation ============================================================ + +XclImpValidationManager::DVItem::DVItem( const ScRangeList& rRanges, const ScValidationData& rValidData ) : + maRanges(rRanges), maValidData(rValidData) {} + +XclImpValidationManager::XclImpValidationManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpValidationManager::ReadDval( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + sal_uInt32 nObjId(0); + rStrm.Ignore( 10 ); + nObjId = rStrm.ReaduInt32(); + if( nObjId != EXC_DVAL_NOOBJ ) + { + OSL_ENSURE( nObjId <= 0xFFFF, "XclImpValidation::ReadDval - invalid object ID" ); + rRoot.GetCurrSheetDrawing().SetSkipObj( static_cast< sal_uInt16 >( nObjId ) ); + } +} + +void XclImpValidationManager::ReadDV( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + ScDocument& rDoc = rRoot.GetDoc(); + SCTAB nScTab = rRoot.GetCurrScTab(); + ExcelToSc& rFmlaConv = rRoot.GetOldFmlaConverter(); + + // flags + sal_uInt32 nFlags = rStrm.ReaduInt32(); + + // message strings + /* Empty strings are single NUL characters in Excel (string length is 1). + -> Do not let the stream replace them with '?' characters. */ + rStrm.SetNulSubstChar( '\0' ); + OUString aPromptTitle( rStrm.ReadUniString() ); + OUString aErrorTitle( rStrm.ReadUniString() ); + OUString aPromptMessage( rStrm.ReadUniString() ); + OUString aErrorMessage( rStrm.ReadUniString() ); + rStrm.SetNulSubstChar(); // back to default + + // formula(s) + if ( rStrm.GetRecLeft() <= 8 ) + // Not enough bytes left in the record. Bail out. + return; + + // first formula + // string list is single tStr token with NUL separators -> replace them with LF + rStrm.SetNulSubstChar( '\n' ); + ::std::unique_ptr< ScTokenArray > xTokArr1; + + // We can't import the formula directly because we need the range + sal_uInt16 nLenFormula1 = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + XclImpStreamPos aPosFormula1; + rStrm.StorePosition(aPosFormula1); + rStrm.Ignore(nLenFormula1); + + // second formula + ::std::unique_ptr< ScTokenArray > xTokArr2; + + sal_uInt16 nLenFormula2 = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + XclImpStreamPos aPosFormula2; + rStrm.StorePosition(aPosFormula2); + rStrm.Ignore(nLenFormula2); + + // read all cell ranges + XclRangeList aXclRanges; + rStrm >> aXclRanges; + + // convert to Calc range list + ScRangeList aScRanges; + rRoot.GetAddressConverter().ConvertRangeList( aScRanges, aXclRanges, nScTab, true ); + + // only continue if there are valid ranges + if ( aScRanges.empty() ) + return; + + ScRange aCombinedRange = aScRanges.Combine(); + + XclImpStreamPos aCurrentPos; + rStrm.StorePosition(aCurrentPos); + rStrm.RestorePosition(aPosFormula1); + if( nLenFormula1 > 0 ) + { + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset(aCombinedRange.aStart); + rFmlaConv.Convert( pTokArr, rStrm, nLenFormula1, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + xTokArr1 = std::move( pTokArr ); + } + rStrm.SetNulSubstChar(); // back to default + if (nLenFormula2 > 0) + { + rStrm.RestorePosition(aPosFormula2); + std::unique_ptr<ScTokenArray> pTokArr; + rFmlaConv.Reset(aCombinedRange.aStart); + rFmlaConv.Convert( pTokArr, rStrm, nLenFormula2, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + xTokArr2 = std::move( pTokArr ); + } + + rStrm.RestorePosition(aCurrentPos); + + bool bIsValid = true; // valid settings in flags field + + ScValidationMode eValMode = SC_VALID_ANY; + switch( nFlags & EXC_DV_MODE_MASK ) + { + case EXC_DV_MODE_ANY: eValMode = SC_VALID_ANY; break; + case EXC_DV_MODE_WHOLE: eValMode = SC_VALID_WHOLE; break; + case EXC_DV_MODE_DECIMAL: eValMode = SC_VALID_DECIMAL; break; + case EXC_DV_MODE_LIST: eValMode = SC_VALID_LIST; break; + case EXC_DV_MODE_DATE: eValMode = SC_VALID_DATE; break; + case EXC_DV_MODE_TIME: eValMode = SC_VALID_TIME; break; + case EXC_DV_MODE_TEXTLEN: eValMode = SC_VALID_TEXTLEN; break; + case EXC_DV_MODE_CUSTOM: eValMode = SC_VALID_CUSTOM; break; + default: bIsValid = false; + } + rRoot.GetTracer().TraceDVType(eValMode == SC_VALID_CUSTOM); + + ScConditionMode eCondMode = ScConditionMode::Between; + switch( nFlags & EXC_DV_COND_MASK ) + { + case EXC_DV_COND_BETWEEN: eCondMode = ScConditionMode::Between; break; + case EXC_DV_COND_NOTBETWEEN:eCondMode = ScConditionMode::NotBetween; break; + case EXC_DV_COND_EQUAL: eCondMode = ScConditionMode::Equal; break; + case EXC_DV_COND_NOTEQUAL: eCondMode = ScConditionMode::NotEqual; break; + case EXC_DV_COND_GREATER: eCondMode = ScConditionMode::Greater; break; + case EXC_DV_COND_LESS: eCondMode = ScConditionMode::Less; break; + case EXC_DV_COND_EQGREATER: eCondMode = ScConditionMode::EqGreater; break; + case EXC_DV_COND_EQLESS: eCondMode = ScConditionMode::EqLess; break; + default: bIsValid = false; + } + + if ( !bIsValid ) + // No valid validation found. Bail out. + return; + + // The default value for comparison is _BETWEEN. However, custom + // rules are a formula, and thus the comparator should be ignored + // and only a true or false from the formula is evaluated. In Calc, + // formulas use comparison SC_COND_DIRECT. + if( eValMode == SC_VALID_CUSTOM ) + { + eCondMode = ScConditionMode::Direct; + } + + // first range for base address for relative references + const ScRange& rScRange = aScRanges.front(); // aScRanges is not empty + + // process string list of a list validity (convert to list of string tokens) + if( xTokArr1 && (eValMode == SC_VALID_LIST) && ::get_flag( nFlags, EXC_DV_STRINGLIST ) ) + XclTokenArrayHelper::ConvertStringToList(*xTokArr1, rDoc.GetSharedStringPool(), '\n'); + + maDVItems.push_back( + std::make_unique<DVItem>(aScRanges, ScValidationData(eValMode, eCondMode, xTokArr1.get(), xTokArr2.get(), rDoc, rScRange.aStart))); + DVItem& rItem = *maDVItems.back(); + + rItem.maValidData.SetIgnoreBlank( ::get_flag( nFlags, EXC_DV_IGNOREBLANK ) ); + rItem.maValidData.SetListType( ::get_flagvalue( nFlags, EXC_DV_SUPPRESSDROPDOWN, css::sheet::TableValidationVisibility::INVISIBLE, css::sheet::TableValidationVisibility::UNSORTED ) ); + + // *** prompt box *** + if( !aPromptTitle.isEmpty() || !aPromptMessage.isEmpty() ) + { + // set any text stored in the record + rItem.maValidData.SetInput( aPromptTitle, aPromptMessage ); + if( !::get_flag( nFlags, EXC_DV_SHOWPROMPT ) ) + rItem.maValidData.ResetInput(); + } + + // *** error box *** + ScValidErrorStyle eErrStyle = SC_VALERR_STOP; + switch( nFlags & EXC_DV_ERROR_MASK ) + { + case EXC_DV_ERROR_WARNING: eErrStyle = SC_VALERR_WARNING; break; + case EXC_DV_ERROR_INFO: eErrStyle = SC_VALERR_INFO; break; + } + // set texts and error style + rItem.maValidData.SetError( aErrorTitle, aErrorMessage, eErrStyle ); + if( !::get_flag( nFlags, EXC_DV_SHOWERROR ) ) + rItem.maValidData.ResetError(); +} + +void XclImpValidationManager::Apply() +{ + ScDocument& rDoc = GetRoot().GetDoc(); + for (const auto& rxDVItem : maDVItems) + { + DVItem& rItem = *rxDVItem; + // set the handle ID + sal_uLong nHandle = rDoc.AddValidationEntry( rItem.maValidData ); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALIDDATA, nHandle ) ); + + // apply all ranges + for ( size_t i = 0, nRanges = rItem.maRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = rItem.maRanges[ i ]; + rDoc.ApplyPatternAreaTab( rScRange.aStart.Col(), rScRange.aStart.Row(), + rScRange.aEnd.Col(), rScRange.aEnd.Row(), rScRange.aStart.Tab(), aPattern ); + } + } + maDVItems.clear(); +} + +// Web queries ================================================================ + +XclImpWebQuery::XclImpWebQuery( const ScRange& rDestRange ) : + maDestRange( rDestRange ), + meMode( xlWQUnknown ), + mnRefresh( 0 ) +{ +} + +void XclImpWebQuery::ReadParamqry( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags = rStrm.ReaduInt16(); + sal_uInt16 nType = ::extract_value< sal_uInt16 >( nFlags, 0, 3 ); + if( !((nType == EXC_PQRYTYPE_WEBQUERY) && ::get_flag( nFlags, EXC_PQRY_WEBQUERY )) ) + return; + + if( ::get_flag( nFlags, EXC_PQRY_TABLES ) ) + { + meMode = xlWQAllTables; + maTables = ScfTools::GetHTMLTablesName(); + } + else + { + meMode = xlWQDocument; + maTables = ScfTools::GetHTMLDocName(); + } +} + +void XclImpWebQuery::ReadWqstring( XclImpStream& rStrm ) +{ + maURL = rStrm.ReadUniString(); +} + +void XclImpWebQuery::ReadWqsettings( XclImpStream& rStrm ) +{ + rStrm.Ignore( 10 ); + sal_uInt16 nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnRefresh = rStrm.ReaduInt16(); + + if( ::get_flag( nFlags, EXC_WQSETT_SPECTABLES ) && (meMode == xlWQAllTables) ) + meMode = xlWQSpecTables; +} + +void XclImpWebQuery::ReadWqtables( XclImpStream& rStrm ) +{ + if( meMode != xlWQSpecTables ) + return; + + rStrm.Ignore( 4 ); + OUString aTables( rStrm.ReadUniString() ); + + const sal_Unicode cSep = ';'; + static const OUStringLiteral aQuotedPairs( u"\"\"" ); + maTables.clear(); + for ( sal_Int32 nStringIx {aTables.isEmpty() ? -1 : 0}; nStringIx>=0; ) + { + OUString aToken( ScStringUtil::GetQuotedToken( aTables, 0, aQuotedPairs, ',', nStringIx ) ); + sal_Int32 nTabNum = CharClass::isAsciiNumeric( aToken ) ? aToken.toInt32() : 0; + if( nTabNum > 0 ) + maTables = ScGlobal::addToken( maTables, ScfTools::GetNameFromHTMLIndex( static_cast< sal_uInt32 >( nTabNum ) ), cSep ); + else + { + ScGlobal::EraseQuotes( aToken, '"', false ); + if( !aToken.isEmpty() ) + maTables = ScGlobal::addToken( maTables, ScfTools::GetNameFromHTMLName( aToken ), cSep ); + } + } +} + +void XclImpWebQuery::Apply( ScDocument& rDoc, const OUString& rFilterName ) +{ + if( !maURL.isEmpty() && (meMode != xlWQUnknown) && rDoc.GetDocumentShell() ) + { + ScAreaLink* pLink = new ScAreaLink( rDoc.GetDocumentShell(), + maURL, rFilterName, OUString(), maTables, maDestRange, mnRefresh * 60UL ); + rDoc.GetLinkManager()->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, + maURL, &rFilterName, &maTables ); + } +} + +XclImpWebQueryBuffer::XclImpWebQueryBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpWebQueryBuffer::ReadQsi( XclImpStream& rStrm ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + rStrm.Ignore( 10 ); + OUString aXclName( rStrm.ReadUniString() ); + + // #i64794# Excel replaces spaces with underscores + aXclName = aXclName.replaceAll( " ", "_" ); + + // find the defined name used in Calc + if( const XclImpName* pName = GetNameManager().FindName( aXclName, GetCurrScTab() ) ) + { + if( const ScRangeData* pRangeData = pName->GetScRangeData() ) + { + ScRange aRange; + if( pRangeData->IsReference( aRange ) ) + maWQList.emplace_back( aRange ); + } + } + } + else + { + DBG_ERROR_BIFF(); + } +} + +void XclImpWebQueryBuffer::ReadParamqry( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadParamqry( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqstring( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqstring( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqsettings( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqsettings( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqtables( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqtables( rStrm ); +} + +void XclImpWebQueryBuffer::Apply() +{ + ScDocument& rDoc = GetDoc(); + for( auto& rQuery : maWQList ) + rQuery.Apply( rDoc, EXC_WEBQRY_FILTER ); +} + +// Decryption ================================================================= + +namespace { + +XclImpDecrypterRef lclReadFilepass5( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + OSL_ENSURE( rStrm.GetRecLeft() == 4, "lclReadFilepass5 - wrong record size" ); + if( rStrm.GetRecLeft() == 4 ) + { + sal_uInt16 nKey(0), nHash(0); + nKey = rStrm.ReaduInt16(); + nHash = rStrm.ReaduInt16(); + xDecr = std::make_shared<XclImpBiff5Decrypter>( nKey, nHash ); + } + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8_Standard( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + OSL_ENSURE( rStrm.GetRecLeft() == 48, "lclReadFilepass8 - wrong record size" ); + if( rStrm.GetRecLeft() == 48 ) + { + std::vector<sal_uInt8> aSalt(16); + std::vector<sal_uInt8> aVerifier(16); + std::vector<sal_uInt8> aVerifierHash(16); + rStrm.Read(aSalt.data(), 16); + rStrm.Read(aVerifier.data(), 16); + rStrm.Read(aVerifierHash.data(), 16); + xDecr = std::make_shared<XclImpBiff8StdDecrypter>(std::move(aSalt), std::move(aVerifier), std::move(aVerifierHash)); + } + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8_Strong(XclImpStream& rStream) +{ + //It is possible there are other variants in existence but these + //are the defaults I get with Excel 2013 + XclImpDecrypterRef xDecr; + + msfilter::RC4EncryptionInfo info; + + info.header.flags = rStream.ReaduInt32(); + if (oox::getFlag( info.header.flags, msfilter::ENCRYPTINFO_EXTERNAL)) + return xDecr; + + sal_uInt32 nHeaderSize = rStream.ReaduInt32(); + sal_uInt32 actualHeaderSize = sizeof(info.header); + + if( nHeaderSize < actualHeaderSize ) + return xDecr; + + info.header.flags = rStream.ReaduInt32(); + info.header.sizeExtra = rStream.ReaduInt32(); + info.header.algId = rStream.ReaduInt32(); + info.header.algIdHash = rStream.ReaduInt32(); + info.header.keyBits = rStream.ReaduInt32(); + info.header.providedType = rStream.ReaduInt32(); + info.header.reserved1 = rStream.ReaduInt32(); + info.header.reserved2 = rStream.ReaduInt32(); + + rStream.Ignore(nHeaderSize - actualHeaderSize); + + info.verifier.saltSize = rStream.ReaduInt32(); + if (info.verifier.saltSize != msfilter::SALT_LENGTH) + return xDecr; + rStream.Read(&info.verifier.salt, sizeof(info.verifier.salt)); + rStream.Read(&info.verifier.encryptedVerifier, sizeof(info.verifier.encryptedVerifier)); + + info.verifier.encryptedVerifierHashSize = rStream.ReaduInt32(); + if (info.verifier.encryptedVerifierHashSize != RTL_DIGEST_LENGTH_SHA1) + return xDecr; + rStream.Read(&info.verifier.encryptedVerifierHash, info.verifier.encryptedVerifierHashSize); + + // check flags and algorithm IDs, required are AES128 and SHA-1 + if (!oox::getFlag(info.header.flags, msfilter::ENCRYPTINFO_CRYPTOAPI)) + return xDecr; + + if (oox::getFlag(info.header.flags, msfilter::ENCRYPTINFO_AES)) + return xDecr; + + if (info.header.algId != msfilter::ENCRYPT_ALGO_RC4) + return xDecr; + + // hash algorithm ID 0 defaults to SHA-1 too + if (info.header.algIdHash != 0 && info.header.algIdHash != msfilter::ENCRYPT_HASH_SHA1) + return xDecr; + + xDecr = std::make_shared<XclImpBiff8CryptoAPIDecrypter>( + std::vector<sal_uInt8>(info.verifier.salt, + info.verifier.salt + SAL_N_ELEMENTS(info.verifier.salt)), + std::vector<sal_uInt8>(info.verifier.encryptedVerifier, + info.verifier.encryptedVerifier + SAL_N_ELEMENTS(info.verifier.encryptedVerifier)), + std::vector<sal_uInt8>(info.verifier.encryptedVerifierHash, + info.verifier.encryptedVerifierHash + SAL_N_ELEMENTS(info.verifier.encryptedVerifierHash))); + + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + + sal_uInt16 nMode = rStrm.ReaduInt16(); + switch( nMode ) + { + case EXC_FILEPASS_BIFF5: + xDecr = lclReadFilepass5( rStrm ); + break; + + case EXC_FILEPASS_BIFF8: + { + sal_uInt32 nVersion = rStrm.ReaduInt32(); + if (nVersion == msfilter::VERSION_INFO_1997_FORMAT) + { + //A Version structure where Version.vMajor MUST be 0x0001, + //and Version.vMinor MUST be 0x0001. + xDecr = lclReadFilepass8_Standard(rStrm); + } + else if (nVersion == msfilter::VERSION_INFO_2007_FORMAT || + nVersion == msfilter::VERSION_INFO_2007_FORMAT_SP2) + { + //Version.vMajor MUST be 0x0002, 0x0003 or 0x0004 and + //Version.vMinor MUST be 0x0002. + xDecr = lclReadFilepass8_Strong(rStrm); + } + else + OSL_FAIL("lclReadFilepass8 - unknown BIFF8 encryption sub mode"); + } + break; + + default: + OSL_FAIL( "lclReadFilepass8 - unknown encryption mode" ); + } + + return xDecr; +} + +} // namespace + +const ErrCode& XclImpDecryptHelper::ReadFilepass( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + rStrm.DisableDecryption(); + + // read the FILEPASS record and create a new decrypter object + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: xDecr = lclReadFilepass5( rStrm ); break; + case EXC_BIFF8: xDecr = lclReadFilepass8( rStrm ); break; + default: DBG_ERROR_BIFF(); + }; + + // set decrypter at import stream + rStrm.SetDecrypter( xDecr ); + + // request and verify a password (decrypter implements IDocPasswordVerifier) + if( xDecr ) + rStrm.GetRoot().RequestEncryptionData( *xDecr ); + + // return error code (success, wrong password, etc.) + return xDecr ? xDecr->GetError() : EXC_ENCR_ERROR_UNSUPP_CRYPT; +} + +// Document protection ======================================================== + +XclImpDocProtectBuffer::XclImpDocProtectBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnPassHash(0x0000), + mbDocProtect(false), + mbWinProtect(false) +{ +} + +void XclImpDocProtectBuffer::ReadDocProtect( XclImpStream& rStrm ) +{ + mbDocProtect = rStrm.ReaduInt16() != 0; +} + +void XclImpDocProtectBuffer::ReadWinProtect( XclImpStream& rStrm ) +{ + mbWinProtect = rStrm.ReaduInt16() != 0; +} + +void XclImpDocProtectBuffer::ReadPasswordHash( XclImpStream& rStrm ) +{ + rStrm.EnableDecryption(); + mnPassHash = rStrm.ReaduInt16(); +} + +void XclImpDocProtectBuffer::Apply() const +{ + if (!mbDocProtect && !mbWinProtect) + // Excel requires either the structure or windows protection is set. + // If neither is set then the document is not protected at all. + return; + + unique_ptr<ScDocProtection> pProtect(new ScDocProtection); + pProtect->setProtected(true); + + if (mnPassHash) + { + // 16-bit password hash. + Sequence<sal_Int8> aPass{sal_Int8(mnPassHash >> 8), sal_Int8(mnPassHash & 0xFF)}; + pProtect->setPasswordHash(aPass, PASSHASH_XL); + } + + // document protection options + pProtect->setOption(ScDocProtection::STRUCTURE, mbDocProtect); + pProtect->setOption(ScDocProtection::WINDOWS, mbWinProtect); + + GetDoc().SetDocProtection(pProtect.get()); +} + +// Sheet Protection =========================================================== + +XclImpSheetProtectBuffer::Sheet::Sheet() : + mbProtected(false), + mnPasswordHash(0x0000), + mnOptions(0x4400) +{ +} + +XclImpSheetProtectBuffer::Sheet::Sheet(const Sheet& r) : + mbProtected(r.mbProtected), + mnPasswordHash(r.mnPasswordHash), + mnOptions(r.mnOptions) +{ +} + +XclImpSheetProtectBuffer::XclImpSheetProtectBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpSheetProtectBuffer::ReadProtect( XclImpStream& rStrm, SCTAB nTab ) +{ + if ( rStrm.ReaduInt16() ) + { + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mbProtected = true; + } +} + +void XclImpSheetProtectBuffer::ReadOptions( XclImpStream& rStrm, SCTAB nTab ) +{ + // The flag size specifies the size of bytes that follows that stores + // feature data. If -1 it depends on the feature type imported earlier. + // For enhanced protection data, the size is always 4. For the most xls + // documents out there this value is almost always -1. + sal_Int32 nFlagSize = rStrm.ReadInt32(); + if (nFlagSize != -1) + return; + + // There are actually 4 bytes to read, but the upper 2 bytes currently + // don't store any bits. + sal_uInt16 nOptions = rStrm.ReaduInt16(); + + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mnOptions = nOptions; +} + +void XclImpSheetProtectBuffer::AppendEnhancedProtection( const ScEnhancedProtection & rProt, SCTAB nTab ) +{ + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->maEnhancedProtections.push_back( rProt); +} + +void XclImpSheetProtectBuffer::ReadPasswordHash( XclImpStream& rStrm, SCTAB nTab ) +{ + sal_uInt16 nHash = rStrm.ReaduInt16(); + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mnPasswordHash = nHash; +} + +void XclImpSheetProtectBuffer::Apply() const +{ + for (const auto& [rTab, rSheet] : maProtectedSheets) + { + if (!rSheet.mbProtected) + // This sheet is (for whatever reason) not protected. + continue; + + unique_ptr<ScTableProtection> pProtect(new ScTableProtection); + pProtect->setProtected(true); + + // 16-bit hash password + const sal_uInt16 nHash = rSheet.mnPasswordHash; + if (nHash) + { + Sequence<sal_Int8> aPass{sal_Int8(nHash >> 8), sal_Int8(nHash & 0xFF)}; + pProtect->setPasswordHash(aPass, PASSHASH_XL); + } + + // sheet protection options + const sal_uInt16 nOptions = rSheet.mnOptions; + pProtect->setOption( ScTableProtection::OBJECTS, (nOptions & 0x0001) ); + pProtect->setOption( ScTableProtection::SCENARIOS, (nOptions & 0x0002) ); + pProtect->setOption( ScTableProtection::FORMAT_CELLS, (nOptions & 0x0004) ); + pProtect->setOption( ScTableProtection::FORMAT_COLUMNS, (nOptions & 0x0008) ); + pProtect->setOption( ScTableProtection::FORMAT_ROWS, (nOptions & 0x0010) ); + pProtect->setOption( ScTableProtection::INSERT_COLUMNS, (nOptions & 0x0020) ); + pProtect->setOption( ScTableProtection::INSERT_ROWS, (nOptions & 0x0040) ); + pProtect->setOption( ScTableProtection::INSERT_HYPERLINKS, (nOptions & 0x0080) ); + pProtect->setOption( ScTableProtection::DELETE_COLUMNS, (nOptions & 0x0100) ); + pProtect->setOption( ScTableProtection::DELETE_ROWS, (nOptions & 0x0200) ); + pProtect->setOption( ScTableProtection::SELECT_LOCKED_CELLS, (nOptions & 0x0400) ); + pProtect->setOption( ScTableProtection::SORT, (nOptions & 0x0800) ); + pProtect->setOption( ScTableProtection::AUTOFILTER, (nOptions & 0x1000) ); + pProtect->setOption( ScTableProtection::PIVOT_TABLES, (nOptions & 0x2000) ); + pProtect->setOption( ScTableProtection::SELECT_UNLOCKED_CELLS, (nOptions & 0x4000) ); + + // Enhanced protection containing editable ranges and permissions. + pProtect->setEnhancedProtection( std::vector(rSheet.maEnhancedProtections) ); + + // all done. now commit. + GetDoc().SetTabProtection(rTab, pProtect.get()); + } +} + +XclImpSheetProtectBuffer::Sheet* XclImpSheetProtectBuffer::GetSheetItem( SCTAB nTab ) +{ + ProtectedSheetMap::iterator itr = maProtectedSheets.find(nTab); + if (itr == maProtectedSheets.end()) + { + // new sheet + if ( !maProtectedSheets.emplace( nTab, Sheet() ).second ) + return nullptr; + + itr = maProtectedSheets.find(nTab); + } + + return &itr->second; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiescher.cxx b/sc/source/filter/excel/xiescher.cxx new file mode 100644 index 000000000..1de9da95d --- /dev/null +++ b/sc/source/filter/excel/xiescher.cxx @@ -0,0 +1,4453 @@ +/* -*- 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 <xiescher.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XEmbedPersist.hpp> +#include <com/sun/star/awt/PushButtonType.hpp> +#include <com/sun/star/awt/ScrollBarOrientation.hpp> +#include <com/sun/star/awt/VisualEffect.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/form/XFormsSupplier.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/form/binding/XListEntrySource.hpp> +#include <com/sun/star/script/ScriptEventDescriptor.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <sfx2/objsh.hxx> +#include <unotools/moduleoptions.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/fltrcfg.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/outdev.hxx> +#include <vcl/wmf.hxx> +#include <comphelper/classids.hxx> +#include <comphelper/documentinfo.hxx> +#include <o3tl/safeint.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <sal/log.hxx> + +#include <svx/svdopath.hxx> +#include <svx/svdocirc.hxx> +#include <svx/svdoedge.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdpage.hxx> +#include <editeng/editobj.hxx> +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <svx/svditer.hxx> +#include <editeng/writingmodeitem.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlndsit.hxx> +#include <svx/xlnedcit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstcit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/sdasitm.hxx> +#include <svx/sdshcitm.hxx> +#include <svx/sdshitm.hxx> +#include <svx/sdsxyitm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtditm.hxx> + +#include <editeng/eeitem.hxx> +#include <svx/xflclit.hxx> +#include <sal/macros.h> +#include <editeng/adjustitem.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xlineit0.hxx> +#include <svx/xlinjoit.hxx> +#include <svx/xlntrit.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xbitmap.hxx> +#include <svtools/embedhlp.hxx> +#include <sot/storage.hxx> + +#include <document.hxx> +#include <drwlayer.hxx> +#include <userdat.hxx> +#include <unonames.hxx> +#include <convuno.hxx> +#include <postit.hxx> +#include <globstr.hrc> +#include <scresid.hxx> + +#include <fprogressbar.hxx> +#include <xltracer.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xiformula.hxx> +#include <xilink.hxx> +#include <xistyle.hxx> +#include <xipage.hxx> +#include <xichart.hxx> +#include <xicontent.hxx> +#include <scextopt.hxx> + +#include <namebuff.hxx> +#include <sfx2/docfile.hxx> +#include <memory> +#include <numeric> +#include <string_view> +#include <utility> + +using namespace com::sun::star; +using ::com::sun::star::uno::Any; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::beans::NamedValue; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::container::XIndexContainer; +using ::com::sun::star::container::XNameContainer; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::embed::XEmbeddedObject; +using ::com::sun::star::embed::XEmbedPersist; +using ::com::sun::star::drawing::XControlShape; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::form::XFormComponent; +using ::com::sun::star::form::XFormsSupplier; +using ::com::sun::star::form::binding::XBindableValue; +using ::com::sun::star::form::binding::XValueBinding; +using ::com::sun::star::form::binding::XListEntrySink; +using ::com::sun::star::form::binding::XListEntrySource; +using ::com::sun::star::script::ScriptEventDescriptor; +using ::com::sun::star::script::XEventAttacherManager; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; + +// Drawing objects ============================================================ + +XclImpDrawObjBase::XclImpDrawObjBase( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnObjId( EXC_OBJ_INVALID_ID ), + mnTab( 0 ), + mnObjType( EXC_OBJTYPE_UNKNOWN ), + mnDffShapeId( 0 ), + mnDffFlags( ShapeFlag::NONE ), + mbHasAnchor( false ), + mbHidden( false ), + mbVisible( true ), + mbPrintable( true ), + mbAreaObj( false ), + mbAutoMargin( true ), + mbSimpleMacro( true ), + mbProcessSdr( true ), + mbInsertSdr( true ), + mbCustomDff( false ), + mbNotifyMacroEventRead( false ) +{ +} + +XclImpDrawObjBase::~XclImpDrawObjBase() +{ +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj3( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 30 ) + { + sal_uInt16 nObjType; + rStrm.Ignore( 4 ); + nObjType = rStrm.ReaduInt16(); + switch( nObjType ) + { + case EXC_OBJTYPE_GROUP: xDrawObj= std::make_shared<XclImpGroupObj>( rRoot ); break; + case EXC_OBJTYPE_LINE: xDrawObj= std::make_shared<XclImpLineObj>( rRoot ); break; + case EXC_OBJTYPE_RECTANGLE: xDrawObj= std::make_shared<XclImpRectObj>( rRoot ); break; + case EXC_OBJTYPE_OVAL: xDrawObj= std::make_shared<XclImpOvalObj>( rRoot ); break; + case EXC_OBJTYPE_ARC: xDrawObj= std::make_shared<XclImpArcObj>( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj= std::make_shared<XclImpChartObj>( rRoot ); break; + case EXC_OBJTYPE_TEXT: xDrawObj= std::make_shared<XclImpTextObj>( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj= std::make_shared<XclImpButtonObj>( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj= std::make_shared<XclImpPictureObj>( rRoot ); break; + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj3 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + } + } + + if (!xDrawObj) + { + xDrawObj = std::make_shared<XclImpPhObj>(rRoot); + } + + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj3( rStrm ); + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj4( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 30 ) + { + sal_uInt16 nObjType; + rStrm.Ignore( 4 ); + nObjType = rStrm.ReaduInt16(); + switch( nObjType ) + { + case EXC_OBJTYPE_GROUP: xDrawObj = std::make_shared<XclImpGroupObj>( rRoot ); break; + case EXC_OBJTYPE_LINE: xDrawObj = std::make_shared<XclImpLineObj>( rRoot ); break; + case EXC_OBJTYPE_RECTANGLE: xDrawObj = std::make_shared<XclImpRectObj>( rRoot ); break; + case EXC_OBJTYPE_OVAL: xDrawObj = std::make_shared<XclImpOvalObj>( rRoot ); break; + case EXC_OBJTYPE_ARC: xDrawObj = std::make_shared<XclImpArcObj>( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj = std::make_shared<XclImpChartObj>( rRoot ); break; + case EXC_OBJTYPE_TEXT: xDrawObj = std::make_shared<XclImpTextObj>( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj = std::make_shared<XclImpButtonObj>( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj = std::make_shared<XclImpPictureObj>( rRoot ); break; + case EXC_OBJTYPE_POLYGON: xDrawObj = std::make_shared<XclImpPolygonObj>( rRoot ); break; + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj4 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + } + } + + if (!xDrawObj) + { + xDrawObj = std::make_shared<XclImpPhObj>(rRoot); + } + + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj4( rStrm ); + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj5( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 34 ) + { + sal_uInt16 nObjType(EXC_OBJTYPE_UNKNOWN); + rStrm.Ignore( 4 ); + nObjType = rStrm.ReaduInt16(); + switch( nObjType ) + { + case EXC_OBJTYPE_GROUP: xDrawObj = std::make_shared<XclImpGroupObj>( rRoot ); break; + case EXC_OBJTYPE_LINE: xDrawObj = std::make_shared<XclImpLineObj>( rRoot ); break; + case EXC_OBJTYPE_RECTANGLE: xDrawObj = std::make_shared<XclImpRectObj>( rRoot ); break; + case EXC_OBJTYPE_OVAL: xDrawObj = std::make_shared<XclImpOvalObj>( rRoot ); break; + case EXC_OBJTYPE_ARC: xDrawObj = std::make_shared<XclImpArcObj>( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj = std::make_shared<XclImpChartObj>( rRoot ); break; + case EXC_OBJTYPE_TEXT: xDrawObj = std::make_shared<XclImpTextObj>( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj = std::make_shared<XclImpButtonObj>( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj = std::make_shared<XclImpPictureObj>( rRoot ); break; + case EXC_OBJTYPE_POLYGON: xDrawObj = std::make_shared<XclImpPolygonObj>( rRoot ); break; + case EXC_OBJTYPE_CHECKBOX: xDrawObj = std::make_shared<XclImpCheckBoxObj>( rRoot ); break; + case EXC_OBJTYPE_OPTIONBUTTON: xDrawObj = std::make_shared<XclImpOptionButtonObj>( rRoot ); break; + case EXC_OBJTYPE_EDIT: xDrawObj = std::make_shared<XclImpEditObj>( rRoot ); break; + case EXC_OBJTYPE_LABEL: xDrawObj = std::make_shared<XclImpLabelObj>( rRoot ); break; + case EXC_OBJTYPE_DIALOG: xDrawObj = std::make_shared<XclImpDialogObj>( rRoot ); break; + case EXC_OBJTYPE_SPIN: xDrawObj = std::make_shared<XclImpSpinButtonObj>( rRoot ); break; + case EXC_OBJTYPE_SCROLLBAR: xDrawObj = std::make_shared<XclImpScrollBarObj>( rRoot ); break; + case EXC_OBJTYPE_LISTBOX: xDrawObj = std::make_shared<XclImpListBoxObj>( rRoot ); break; + case EXC_OBJTYPE_GROUPBOX: xDrawObj = std::make_shared<XclImpGroupBoxObj>( rRoot ); break; + case EXC_OBJTYPE_DROPDOWN: xDrawObj = std::make_shared<XclImpDropDownObj>( rRoot ); break; + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj5 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + xDrawObj = std::make_shared<XclImpPhObj>( rRoot ); + } + } + + OSL_ENSURE(xDrawObj, "object import failed"); + + if (xDrawObj) + { + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj5( rStrm ); + } + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj8( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 10 ) + { + sal_uInt16 nSubRecId(0), nSubRecSize(0), nObjType(0); + nSubRecId = rStrm.ReaduInt16(); + nSubRecSize = rStrm.ReaduInt16(); + nObjType = rStrm.ReaduInt16(); + OSL_ENSURE( nSubRecId == EXC_ID_OBJCMO, "XclImpDrawObjBase::ReadObj8 - OBJCMO subrecord expected" ); + if( (nSubRecId == EXC_ID_OBJCMO) && (nSubRecSize >= 6) ) + { + switch( nObjType ) + { + // in BIFF8, all simple objects support text + case EXC_OBJTYPE_LINE: + case EXC_OBJTYPE_ARC: + xDrawObj = std::make_shared<XclImpTextObj>( rRoot ); + // lines and arcs may be 2-dimensional + xDrawObj->SetAreaObj( false ); + break; + + // in BIFF8, all simple objects support text + case EXC_OBJTYPE_RECTANGLE: + case EXC_OBJTYPE_OVAL: + case EXC_OBJTYPE_POLYGON: + case EXC_OBJTYPE_DRAWING: + case EXC_OBJTYPE_TEXT: + xDrawObj = std::make_shared<XclImpTextObj>( rRoot ); + break; + + case EXC_OBJTYPE_GROUP: xDrawObj = std::make_shared<XclImpGroupObj>( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj = std::make_shared<XclImpChartObj>( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj = std::make_shared<XclImpButtonObj>( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj = std::make_shared<XclImpPictureObj>( rRoot ); break; + case EXC_OBJTYPE_CHECKBOX: xDrawObj = std::make_shared<XclImpCheckBoxObj>( rRoot ); break; + case EXC_OBJTYPE_OPTIONBUTTON: xDrawObj = std::make_shared<XclImpOptionButtonObj>( rRoot ); break; + case EXC_OBJTYPE_EDIT: xDrawObj = std::make_shared<XclImpEditObj>( rRoot ); break; + case EXC_OBJTYPE_LABEL: xDrawObj = std::make_shared<XclImpLabelObj>( rRoot ); break; + case EXC_OBJTYPE_DIALOG: xDrawObj = std::make_shared<XclImpDialogObj>( rRoot ); break; + case EXC_OBJTYPE_SPIN: xDrawObj = std::make_shared<XclImpSpinButtonObj>( rRoot ); break; + case EXC_OBJTYPE_SCROLLBAR: xDrawObj = std::make_shared<XclImpScrollBarObj>( rRoot ); break; + case EXC_OBJTYPE_LISTBOX: xDrawObj = std::make_shared<XclImpListBoxObj>( rRoot ); break; + case EXC_OBJTYPE_GROUPBOX: xDrawObj = std::make_shared<XclImpGroupBoxObj>( rRoot ); break; + case EXC_OBJTYPE_DROPDOWN: xDrawObj = std::make_shared<XclImpDropDownObj>( rRoot ); break; + case EXC_OBJTYPE_NOTE: xDrawObj = std::make_shared<XclImpNoteObj>( rRoot ); break; + + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj8 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + } + } + } + + if (!xDrawObj) //ensure placeholder for unknown or broken records + { + SAL_WARN( "sc.filter", "XclImpDrawObjBase::ReadObj8 import failed, substituting placeholder"); + xDrawObj = std::make_shared<XclImpPhObj>( rRoot ); + } + + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj8( rStrm ); + return xDrawObj; +} + +void XclImpDrawObjBase::SetAnchor( const XclObjAnchor& rAnchor ) +{ + maAnchor = rAnchor; + mbHasAnchor = true; +} + +void XclImpDrawObjBase::SetDffData( + const DffObjData& rDffObjData, const OUString& rObjName, const OUString& rHyperlink, + bool bVisible, bool bAutoMargin ) +{ + mnDffShapeId = rDffObjData.nShapeId; + mnDffFlags = rDffObjData.nSpFlags; + maObjName = rObjName; + maHyperlink = rHyperlink; + mbVisible = bVisible; + mbAutoMargin = bAutoMargin; +} + +OUString XclImpDrawObjBase::GetObjName() const +{ + /* #i51348# Always return a non-empty name. Create English + default names depending on the object type. This is not implemented as + virtual functions in derived classes, as class type and object type may + not match. */ + return maObjName.isEmpty() ? GetObjectManager().GetDefaultObjName(*this) : maObjName; +} + +const XclObjAnchor* XclImpDrawObjBase::GetAnchor() const +{ + return mbHasAnchor ? &maAnchor : nullptr; +} + +bool XclImpDrawObjBase::IsValidSize( const tools::Rectangle& rAnchorRect ) const +{ + // XclObjAnchor rounds up the width, width of 3 is the result of an Excel width of 0 + return mbAreaObj ? + ((rAnchorRect.GetWidth() > 3) && (rAnchorRect.GetHeight() > 1)) : + ((rAnchorRect.GetWidth() > 3) || (rAnchorRect.GetHeight() > 1)); +} + +ScRange XclImpDrawObjBase::GetUsedArea( SCTAB nScTab ) const +{ + ScRange aScUsedArea( ScAddress::INITIALIZE_INVALID ); + // #i44077# object inserted -> update used area for OLE object import + if( mbHasAnchor && GetAddressConverter().ConvertRange( aScUsedArea, maAnchor, nScTab, nScTab, false ) ) + { + // reduce range, if object ends directly on borders between two columns or rows + if( (maAnchor.mnRX == 0) && (aScUsedArea.aStart.Col() < aScUsedArea.aEnd.Col()) ) + aScUsedArea.aEnd.IncCol( -1 ); + if( (maAnchor.mnBY == 0) && (aScUsedArea.aStart.Row() < aScUsedArea.aEnd.Row()) ) + aScUsedArea.aEnd.IncRow( -1 ); + } + return aScUsedArea; +} + +std::size_t XclImpDrawObjBase::GetProgressSize() const +{ + return DoGetProgressSize(); +} + +SdrObjectUniquePtr XclImpDrawObjBase::CreateSdrObject( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect, bool bIsDff ) const +{ + SdrObjectUniquePtr xSdrObj; + if( bIsDff && !mbCustomDff ) + { + rDffConv.Progress( GetProgressSize() ); + } + else + { + xSdrObj = DoCreateSdrObj( rDffConv, rAnchorRect ); + + //added for exporting OCX control + /* mnObjType value set should be as below table: + 0x0000 Group 0x0001 Line + 0x0002 Rectangle 0x0003 Oval + 0x0004 Arc 0x0005 Chart + 0x0006 Text 0x0009 Polygon + +-----------------------------------------------------+ + OCX ==>| 0x0008 Picture | + +-----------------------------------------------------+ + | 0x0007 Button | + | 0x000B Checkbox 0x000C Radio button | + | 0x000D Edit box 0x000E Label | + TBX ==> | 0x000F Dialog box 0x0010 Spin control | + | 0x0011 Scrollbar 0x0012 List | + | 0x0013 Group box 0x0014 Dropdown list | + +-----------------------------------------------------+ + 0x0019 Note 0x001E OfficeArt object + */ + if( xSdrObj && xSdrObj->IsUnoObj() && + ( (mnObjType < 25 && mnObjType > 10) || mnObjType == 7 || mnObjType == 8 ) ) + { + SdrUnoObj* pSdrUnoObj = dynamic_cast< SdrUnoObj* >( xSdrObj.get() ); + if( pSdrUnoObj != nullptr ) + { + const Reference< XControlModel >& xCtrlModel = pSdrUnoObj->GetUnoControlModel(); + Reference< XPropertySet > xPropSet(xCtrlModel,UNO_QUERY); + static constexpr OUStringLiteral sPropertyName(u"ControlTypeinMSO"); + + enum { eCreateFromOffice = 0, eCreateFromMSTBXControl, eCreateFromMSOCXControl }; + + if( mnObjType == 7 || (mnObjType < 25 && mnObjType > 10) )//TBX + { + try + { + //Need summary type for export. Detail type(checkbox, button ...) has been contained by mnObjType + const sal_Int16 nTBXControlType = eCreateFromMSTBXControl ; + xPropSet->setPropertyValue(sPropertyName, Any(nTBXControlType)); + } + catch(const Exception&) + { + SAL_WARN("sc.filter", "XclImpDrawObjBase::CreateSdrObject, this control can't be set the property ControlTypeinMSO!"); + } + } + if( mnObjType == 8 )//OCX + { + //Need summary type for export + static constexpr OUStringLiteral sObjIdPropertyName(u"ObjIDinMSO"); + const XclImpPictureObj* const pObj = dynamic_cast< const XclImpPictureObj* const >(this); + if( pObj != nullptr && pObj->IsOcxControl() ) + { + try + { + const sal_Int16 nOCXControlType = eCreateFromMSOCXControl; + xPropSet->setPropertyValue(sPropertyName, Any(nOCXControlType)); + //Detail type(checkbox, button ...) + xPropSet->setPropertyValue(sObjIdPropertyName, Any(sal_uInt16(mnObjId))); + } + catch(const Exception&) + { + SAL_WARN("sc.filter", "XclImpDrawObjBase::CreateSdrObject, this control can't be set the property ObjIDinMSO!"); + } + } + } + + } + } + } + return xSdrObj; +} + +void XclImpDrawObjBase::NotifyMacroEventRead() +{ + if (mbNotifyMacroEventRead) + return; + SfxObjectShell* pDocShell = GetDocShell(); + if (!pDocShell) + return; + comphelper::DocumentInfo::notifyMacroEventRead(pDocShell->GetModel()); + mbNotifyMacroEventRead = true; +} + +void XclImpDrawObjBase::PreProcessSdrObject( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) +{ + // default: front layer, derived classes may have to set other layer in DoPreProcessSdrObj() + rSdrObj.NbcSetLayer( SC_LAYER_FRONT ); + + // set object name (GetObjName() will always return a non-empty name) + rSdrObj.SetName( GetObjName() ); + + // #i39167# full width for all objects regardless of horizontal alignment + rSdrObj.SetMergedItem( SdrTextHorzAdjustItem( SDRTEXTHORZADJUST_BLOCK ) ); + + // automatic text margin + if( mbAutoMargin ) + { + sal_Int32 nMargin = rDffConv.GetDefaultTextMargin(); + rSdrObj.SetMergedItem( makeSdrTextLeftDistItem( nMargin ) ); + rSdrObj.SetMergedItem( makeSdrTextRightDistItem( nMargin ) ); + rSdrObj.SetMergedItem( makeSdrTextUpperDistItem( nMargin ) ); + rSdrObj.SetMergedItem( makeSdrTextLowerDistItem( nMargin ) ); + } + + // macro and hyperlink + // removed oracle/sun check for mbSimpleMacro ( no idea what its for ) + if (!maMacroName.isEmpty()) + { + if( ScMacroInfo* pInfo = ScDrawLayer::GetMacroInfo( &rSdrObj, true ) ) + { + OUString sMacro = XclTools::GetSbMacroUrl(maMacroName, GetDocShell()); + if (!sMacro.isEmpty()) + NotifyMacroEventRead(); + pInfo->SetMacro(sMacro); + } + } + if (!maHyperlink.isEmpty()) + rSdrObj.setHyperlink(maHyperlink); + + // call virtual function for object type specific processing + DoPreProcessSdrObj( rDffConv, rSdrObj ); +} + +void XclImpDrawObjBase::PostProcessSdrObject( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + // call virtual function for object type specific processing + DoPostProcessSdrObj( rDffConv, rSdrObj ); +} + +// protected ------------------------------------------------------------------ + +void XclImpDrawObjBase::ReadName5( XclImpStream& rStrm, sal_uInt16 nNameLen ) +{ + maObjName.clear(); + if( nNameLen > 0 ) + { + // name length field is repeated before the name + maObjName = rStrm.ReadByteString( false ); + // skip padding byte for word boundaries + if( rStrm.GetRecPos() & 1 ) rStrm.Ignore( 1 ); + } +} + +void XclImpDrawObjBase::ReadMacro3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + maMacroName.clear(); + rStrm.Ignore( nMacroSize ); + // skip padding byte for word boundaries, not contained in nMacroSize + if( rStrm.GetRecPos() & 1 ) rStrm.Ignore( 1 ); +} + +void XclImpDrawObjBase::ReadMacro4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + maMacroName.clear(); + rStrm.Ignore( nMacroSize ); +} + +void XclImpDrawObjBase::ReadMacro5( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + maMacroName.clear(); + rStrm.Ignore( nMacroSize ); +} + +void XclImpDrawObjBase::ReadMacro8( XclImpStream& rStrm ) +{ + maMacroName.clear(); + if( rStrm.GetRecLeft() <= 6 ) + return; + + // macro is stored in a tNameXR token containing a link to a defined name + sal_uInt16 nFmlaSize; + nFmlaSize = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + OSL_ENSURE( nFmlaSize == 7, "XclImpDrawObjBase::ReadMacro - unexpected formula size" ); + if( nFmlaSize == 7 ) + { + sal_uInt8 nTokenId; + sal_uInt16 nExtSheet, nExtName; + nTokenId = rStrm.ReaduInt8(); + nExtSheet = rStrm.ReaduInt16(); + nExtName = rStrm.ReaduInt16(); + OSL_ENSURE( nTokenId == XclTokenArrayHelper::GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ), + "XclImpDrawObjBase::ReadMacro - tNameXR token expected" ); + if( nTokenId == XclTokenArrayHelper::GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ) ) + maMacroName = GetLinkManager().GetMacroName( nExtSheet, nExtName ); + } +} + +void XclImpDrawObjBase::ConvertLineStyle( SdrObject& rSdrObj, const XclObjLineData& rLineData ) const +{ + if( rLineData.IsAuto() ) + { + XclObjLineData aAutoData; + aAutoData.mnAuto = 0; + ConvertLineStyle( rSdrObj, aAutoData ); + } + else + { + tools::Long nLineWidth = 35 * ::std::min( rLineData.mnWidth, EXC_OBJ_LINE_THICK ); + rSdrObj.SetMergedItem( XLineWidthItem( nLineWidth ) ); + rSdrObj.SetMergedItem( XLineColorItem( OUString(), GetPalette().GetColor( rLineData.mnColorIdx ) ) ); + rSdrObj.SetMergedItem( XLineJointItem( css::drawing::LineJoint_MITER ) ); + + sal_uLong nDotLen = ::std::max< sal_uLong >( 70 * rLineData.mnWidth, 35 ); + sal_uLong nDashLen = 3 * nDotLen; + sal_uLong nDist = 2 * nDotLen; + + switch( rLineData.mnStyle ) + { + default: + case EXC_OBJ_LINE_SOLID: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + break; + case EXC_OBJ_LINE_DASH: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 0, nDotLen, 1, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_DOT: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 1, nDotLen, 0, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_DASHDOT: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 1, nDotLen, 1, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_DASHDOTDOT: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 2, nDotLen, 1, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_MEDTRANS: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + rSdrObj.SetMergedItem( XLineTransparenceItem( 50 ) ); + break; + case EXC_OBJ_LINE_DARKTRANS: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + rSdrObj.SetMergedItem( XLineTransparenceItem( 25 ) ); + break; + case EXC_OBJ_LINE_LIGHTTRANS: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + rSdrObj.SetMergedItem( XLineTransparenceItem( 75 ) ); + break; + case EXC_OBJ_LINE_NONE: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_NONE ) ); + break; + } + } +} + +void XclImpDrawObjBase::ConvertFillStyle( SdrObject& rSdrObj, const XclObjFillData& rFillData ) const +{ + if( rFillData.IsAuto() ) + { + XclObjFillData aAutoData; + aAutoData.mnAuto = 0; + ConvertFillStyle( rSdrObj, aAutoData ); + } + else if( rFillData.mnPattern == EXC_PATT_NONE ) + { + rSdrObj.SetMergedItem( XFillStyleItem( drawing::FillStyle_NONE ) ); + } + else + { + Color aPattColor = GetPalette().GetColor( rFillData.mnPattColorIdx ); + Color aBackColor = GetPalette().GetColor( rFillData.mnBackColorIdx ); + if( (rFillData.mnPattern == EXC_PATT_SOLID) || (aPattColor == aBackColor) ) + { + rSdrObj.SetMergedItem( XFillStyleItem( drawing::FillStyle_SOLID ) ); + rSdrObj.SetMergedItem( XFillColorItem( OUString(), aPattColor ) ); + } + else + { + static const sal_uInt8 sppnPatterns[][ 8 ] = + { + { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, + { 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD }, + { 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22 }, + { 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00 }, + { 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC }, + { 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99 }, + { 0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99 }, + { 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33 }, + { 0xCC, 0xFF, 0x33, 0xFF, 0xCC, 0xFF, 0x33, 0xFF }, + { 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00 }, + { 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88 }, + { 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88 }, + { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 }, + { 0xFF, 0x11, 0x11, 0x11, 0xFF, 0x11, 0x11, 0x11 }, + { 0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11 }, + { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, + { 0x80, 0x00, 0x08, 0x00, 0x80, 0x00, 0x08, 0x00 } + }; + const sal_uInt8* const pnPattern = sppnPatterns[std::min<size_t>(rFillData.mnPattern - 2, SAL_N_ELEMENTS(sppnPatterns) - 1)]; + // create 2-colored 8x8 DIB + SvMemoryStream aMemStrm; + aMemStrm.WriteUInt32( 12 ).WriteInt16( 8 ).WriteInt16( 8 ).WriteUInt16( 1 ).WriteUInt16( 1 ); + aMemStrm.WriteUChar( 0xFF ).WriteUChar( 0xFF ).WriteUChar( 0xFF ); + aMemStrm.WriteUChar( 0x00 ).WriteUChar( 0x00 ).WriteUChar( 0x00 ); + for( size_t nIdx = 0; nIdx < 8; ++nIdx ) + aMemStrm.WriteUInt32( pnPattern[ nIdx ] ); // 32-bit little-endian + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + Bitmap aBitmap; + (void)ReadDIB(aBitmap, aMemStrm, false); + + XOBitmap aXOBitmap(( BitmapEx(aBitmap) )); + aXOBitmap.Bitmap2Array(); + if( aXOBitmap.GetBackgroundColor() == COL_BLACK ) + ::std::swap( aPattColor, aBackColor ); + aXOBitmap.SetPixelColor( aPattColor ); + aXOBitmap.SetBackgroundColor( aBackColor ); + aXOBitmap.Array2Bitmap(); + aBitmap = aXOBitmap.GetBitmap().GetBitmap(); + + rSdrObj.SetMergedItem(XFillStyleItem(drawing::FillStyle_BITMAP)); + rSdrObj.SetMergedItem(XFillBitmapItem(OUString(), Graphic(BitmapEx(aBitmap)))); + } + } +} + +void XclImpDrawObjBase::ConvertFrameStyle( SdrObject& rSdrObj, sal_uInt16 nFrameFlags ) const +{ + if( ::get_flag( nFrameFlags, EXC_OBJ_FRAME_SHADOW ) ) + { + rSdrObj.SetMergedItem( makeSdrShadowItem( true ) ); + rSdrObj.SetMergedItem( makeSdrShadowXDistItem( 35 ) ); + rSdrObj.SetMergedItem( makeSdrShadowYDistItem( 35 ) ); + rSdrObj.SetMergedItem( makeSdrShadowColorItem( GetPalette().GetColor( EXC_COLOR_WINDOWTEXT ) ) ); + } +} + +Color XclImpDrawObjBase::GetSolidLineColor( const XclObjLineData& rLineData ) const +{ + Color aColor( COL_TRANSPARENT ); + if( rLineData.IsAuto() ) + { + XclObjLineData aAutoData; + aAutoData.mnAuto = 0; + aColor = GetSolidLineColor( aAutoData ); + } + else if( rLineData.mnStyle != EXC_OBJ_LINE_NONE ) + { + aColor = GetPalette().GetColor( rLineData.mnColorIdx ); + } + return aColor; +} + +Color XclImpDrawObjBase::GetSolidFillColor( const XclObjFillData& rFillData ) const +{ + Color aColor( COL_TRANSPARENT ); + if( rFillData.IsAuto() ) + { + XclObjFillData aAutoData; + aAutoData.mnAuto = 0; + aColor = GetSolidFillColor( aAutoData ); + } + else if( rFillData.mnPattern != EXC_PATT_NONE ) + { + Color aPattColor = GetPalette().GetColor( rFillData.mnPattColorIdx ); + Color aBackColor = GetPalette().GetColor( rFillData.mnBackColorIdx ); + aColor = XclTools::GetPatternColor( aPattColor, aBackColor, rFillData.mnPattern ); + } + return aColor; +} + +void XclImpDrawObjBase::DoReadObj3( XclImpStream&, sal_uInt16 ) +{ +} + +void XclImpDrawObjBase::DoReadObj4( XclImpStream&, sal_uInt16 ) +{ +} + +void XclImpDrawObjBase::DoReadObj5( XclImpStream&, sal_uInt16, sal_uInt16 ) +{ +} + +void XclImpDrawObjBase::DoReadObj8SubRec( XclImpStream&, sal_uInt16, sal_uInt16 ) +{ +} + +std::size_t XclImpDrawObjBase::DoGetProgressSize() const +{ + return 1; +} + +SdrObjectUniquePtr XclImpDrawObjBase::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& ) const +{ + rDffConv.Progress( GetProgressSize() ); + return nullptr; +} + +void XclImpDrawObjBase::DoPreProcessSdrObj( XclImpDffConverter&, SdrObject& ) const +{ + // trace if object is not printable + if( !IsPrintable() ) + GetTracer().TraceObjectNotPrintable(); +} + +void XclImpDrawObjBase::DoPostProcessSdrObj( XclImpDffConverter&, SdrObject& ) const +{ +} + +void XclImpDrawObjBase::ImplReadObj3( XclImpStream& rStrm ) +{ + // back to offset 4 (ignore object count field) + rStrm.Seek( 4 ); + + sal_uInt16 nObjFlags, nMacroSize; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16(); + nObjFlags = rStrm.ReaduInt16(); + rStrm >> maAnchor; + nMacroSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + + mbHasAnchor = true; + mbHidden = ::get_flag( nObjFlags, EXC_OBJ_HIDDEN ); + mbVisible = ::get_flag( nObjFlags, EXC_OBJ_VISIBLE ); + DoReadObj3( rStrm, nMacroSize ); +} + +void XclImpDrawObjBase::ImplReadObj4( XclImpStream& rStrm ) +{ + // back to offset 4 (ignore object count field) + rStrm.Seek( 4 ); + + sal_uInt16 nObjFlags, nMacroSize; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16(); + nObjFlags = rStrm.ReaduInt16(); + rStrm >> maAnchor; + nMacroSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + + mbHasAnchor = true; + mbHidden = ::get_flag( nObjFlags, EXC_OBJ_HIDDEN ); + mbVisible = ::get_flag( nObjFlags, EXC_OBJ_VISIBLE ); + mbPrintable = ::get_flag( nObjFlags, EXC_OBJ_PRINTABLE ); + DoReadObj4( rStrm, nMacroSize ); +} + +void XclImpDrawObjBase::ImplReadObj5( XclImpStream& rStrm ) +{ + // back to offset 4 (ignore object count field) + rStrm.Seek( 4 ); + + sal_uInt16 nObjFlags, nMacroSize, nNameLen; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16(); + nObjFlags = rStrm.ReaduInt16(); + rStrm >> maAnchor; + nMacroSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + nNameLen = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + + mbHasAnchor = true; + mbHidden = ::get_flag( nObjFlags, EXC_OBJ_HIDDEN ); + mbVisible = ::get_flag( nObjFlags, EXC_OBJ_VISIBLE ); + mbPrintable = ::get_flag( nObjFlags, EXC_OBJ_PRINTABLE ); + DoReadObj5( rStrm, nNameLen, nMacroSize ); +} + +void XclImpDrawObjBase::ImplReadObj8( XclImpStream& rStrm ) +{ + // back to beginning + rStrm.Seek( EXC_REC_SEEK_TO_BEGIN ); + + bool bLoop = true; + while (bLoop) + { + if (rStrm.GetRecLeft() < 4) + break; + + sal_uInt16 nSubRecId = rStrm.ReaduInt16(); + sal_uInt16 nSubRecSize = rStrm.ReaduInt16(); + rStrm.PushPosition(); + // sometimes the last subrecord has an invalid length (OBJLBSDATA) -> min() + nSubRecSize = static_cast< sal_uInt16 >( ::std::min< std::size_t >( nSubRecSize, rStrm.GetRecLeft() ) ); + + switch( nSubRecId ) + { + case EXC_ID_OBJCMO: + OSL_ENSURE( rStrm.GetRecPos() == 4, "XclImpDrawObjBase::ImplReadObj8 - unexpected OBJCMO subrecord" ); + if( (rStrm.GetRecPos() == 4) && (nSubRecSize >= 6) ) + { + sal_uInt16 nObjFlags; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16( ); + nObjFlags = rStrm.ReaduInt16( ); + mbPrintable = ::get_flag( nObjFlags, EXC_OBJCMO_PRINTABLE ); + } + break; + case EXC_ID_OBJMACRO: + ReadMacro8( rStrm ); + break; + case EXC_ID_OBJEND: + bLoop = false; + break; + default: + DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } + + rStrm.PopPosition(); + rStrm.Ignore( nSubRecSize ); + } + + /* Call DoReadObj8SubRec() with EXC_ID_OBJEND for further stream + processing (e.g. charts), even if the OBJEND subrecord is missing. */ + DoReadObj8SubRec( rStrm, EXC_ID_OBJEND, 0 ); + + /* Pictures that Excel reads from BIFF5 and writes to BIFF8 still have the + IMGDATA record following the OBJ record (but they use the image data + stored in DFF). The IMGDATA record may be continued by several CONTINUE + records. But the last CONTINUE record may be in fact an MSODRAWING + record that contains the DFF data of the next drawing object! So we + have to skip just enough CONTINUE records to look at the next + MSODRAWING/CONTINUE record. */ + if( !((rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord()) ) + return; + + rStrm.Ignore( 4 ); + sal_uInt32 nDataSize = rStrm.ReaduInt32(); + nDataSize -= rStrm.GetRecLeft(); + // skip following CONTINUE records until IMGDATA ends + while (true) + { + if (!nDataSize) + break; + if (rStrm.GetNextRecId() != EXC_ID_CONT) + break; + if (!rStrm.StartNextRecord()) + break; + OSL_ENSURE( nDataSize >= rStrm.GetRecLeft(), "XclImpDrawObjBase::ImplReadObj8 - CONTINUE too long" ); + nDataSize -= ::std::min< sal_uInt32 >( rStrm.GetRecLeft(), nDataSize ); + } + OSL_ENSURE( nDataSize == 0, "XclImpDrawObjBase::ImplReadObj8 - missing CONTINUE records" ); + // next record may be MSODRAWING or CONTINUE or anything else +} + +void XclImpDrawObjVector::InsertGrouped( XclImpDrawObjRef const & xDrawObj ) +{ + if( !mObjs.empty() ) + if( XclImpGroupObj* pGroupObj = dynamic_cast< XclImpGroupObj* >( mObjs.back().get() ) ) + if( pGroupObj->TryInsert( xDrawObj ) ) + return; + mObjs.push_back( xDrawObj ); +} + +std::size_t XclImpDrawObjVector::GetProgressSize() const +{ + return std::accumulate(mObjs.begin(), mObjs.end(), std::size_t(0), + [](const std::size_t& rSum, const XclImpDrawObjRef& rxObj) { return rSum + rxObj->GetProgressSize(); }); +} + +XclImpPhObj::XclImpPhObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ) +{ + SetProcessSdrObj( false ); +} + +XclImpGroupObj::XclImpGroupObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnFirstUngrouped( 0 ) +{ +} + +bool XclImpGroupObj::TryInsert( XclImpDrawObjRef const & xDrawObj ) +{ + if( xDrawObj->GetObjId() == mnFirstUngrouped ) + return false; + // insert into own list or into nested group + maChildren.InsertGrouped( xDrawObj ); + return true; +} + +void XclImpGroupObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm.Ignore( 4 ); + mnFirstUngrouped = rStrm.ReaduInt16(); + rStrm.Ignore( 16 ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpGroupObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm.Ignore( 4 ); + mnFirstUngrouped = rStrm.ReaduInt16(); + rStrm.Ignore( 16 ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpGroupObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + rStrm.Ignore( 4 ); + mnFirstUngrouped = rStrm.ReaduInt16(); + rStrm.Ignore( 16 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +std::size_t XclImpGroupObj::DoGetProgressSize() const +{ + return XclImpDrawObjBase::DoGetProgressSize() + maChildren.GetProgressSize(); +} + +SdrObjectUniquePtr XclImpGroupObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& /*rAnchorRect*/ ) const +{ + std::unique_ptr<SdrObjGroup, SdrObjectFreeOp> xSdrObj( + new SdrObjGroup( + *GetDoc().GetDrawLayer())); + // child objects in BIFF2-BIFF5 have absolute size, not needed to pass own anchor rectangle + SdrObjList& rObjList = *xSdrObj->GetSubList(); // SdrObjGroup always returns existing sublist + for( const auto& rxChild : maChildren ) + rDffConv.ProcessObject( rObjList, *rxChild ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpLineObj::XclImpLineObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnArrows( 0 ), + mnStartPoint( EXC_OBJ_LINE_TL ) +{ + SetAreaObj( false ); +} + +void XclImpLineObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maLineData; + mnArrows = rStrm.ReaduInt16(); + mnStartPoint = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpLineObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maLineData; + mnArrows = rStrm.ReaduInt16(); + mnStartPoint = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpLineObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + rStrm >> maLineData; + mnArrows = rStrm.ReaduInt16(); + mnStartPoint = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +SdrObjectUniquePtr XclImpLineObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + ::basegfx::B2DPolygon aB2DPolygon; + switch( mnStartPoint ) + { + default: + case EXC_OBJ_LINE_TL: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Top() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Bottom() ) ); + break; + case EXC_OBJ_LINE_TR: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Top() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Bottom() ) ); + break; + case EXC_OBJ_LINE_BR: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Bottom() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Top() ) ); + break; + case EXC_OBJ_LINE_BL: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Bottom() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Top() ) ); + break; + } + SdrObjectUniquePtr xSdrObj( + new SdrPathObj( + *GetDoc().GetDrawLayer(), + SdrObjKind::Line, + ::basegfx::B2DPolyPolygon(aB2DPolygon))); + ConvertLineStyle( *xSdrObj, maLineData ); + + // line ends + sal_uInt8 nArrowType = ::extract_value< sal_uInt8 >( mnArrows, 0, 4 ); + bool bLineStart = false; + bool bLineEnd = false; + bool bFilled = false; + switch( nArrowType ) + { + case EXC_OBJ_ARROW_OPEN: bLineStart = false; bLineEnd = true; bFilled = false; break; + case EXC_OBJ_ARROW_OPENBOTH: bLineStart = true; bLineEnd = true; bFilled = false; break; + case EXC_OBJ_ARROW_FILLED: bLineStart = false; bLineEnd = true; bFilled = true; break; + case EXC_OBJ_ARROW_FILLEDBOTH: bLineStart = true; bLineEnd = true; bFilled = true; break; + } + if( bLineStart || bLineEnd ) + { + sal_uInt8 nArrowWidth = ::extract_value< sal_uInt8 >( mnArrows, 4, 4 ); + double fArrowWidth = 3.0; + switch( nArrowWidth ) + { + case EXC_OBJ_ARROW_NARROW: fArrowWidth = 2.0; break; + case EXC_OBJ_ARROW_MEDIUM: fArrowWidth = 3.0; break; + case EXC_OBJ_ARROW_WIDE: fArrowWidth = 5.0; break; + } + + sal_uInt8 nArrowLength = ::extract_value< sal_uInt8 >( mnArrows, 8, 4 ); + double fArrowLength = 3.0; + switch( nArrowLength ) + { + case EXC_OBJ_ARROW_NARROW: fArrowLength = 2.5; break; + case EXC_OBJ_ARROW_MEDIUM: fArrowLength = 3.5; break; + case EXC_OBJ_ARROW_WIDE: fArrowLength = 6.0; break; + } + + ::basegfx::B2DPolygon aArrowPoly; +#define EXC_ARROW_POINT( x, y ) ::basegfx::B2DPoint( fArrowWidth * (x), fArrowLength * (y) ) + if( bFilled ) + { + aArrowPoly.append( EXC_ARROW_POINT( 0, 100 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 50, 0 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 100, 100 ) ); + } + else + { + sal_uInt8 nLineWidth = ::limit_cast< sal_uInt8 >( maLineData.mnWidth, EXC_OBJ_LINE_THIN, EXC_OBJ_LINE_THICK ); + aArrowPoly.append( EXC_ARROW_POINT( 50, 0 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 100, 100 - 3 * nLineWidth ) ); + aArrowPoly.append( EXC_ARROW_POINT( 100 - 5 * nLineWidth, 100 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 50, 12 * nLineWidth ) ); + aArrowPoly.append( EXC_ARROW_POINT( 5 * nLineWidth, 100 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 0, 100 - 3 * nLineWidth ) ); + } +#undef EXC_ARROW_POINT + + ::basegfx::B2DPolyPolygon aArrowPolyPoly( aArrowPoly ); + tools::Long nWidth = static_cast< tools::Long >( 125 * fArrowWidth ); + if( bLineStart ) + { + xSdrObj->SetMergedItem( XLineStartItem( OUString(), aArrowPolyPoly ) ); + xSdrObj->SetMergedItem( XLineStartWidthItem( nWidth ) ); + xSdrObj->SetMergedItem( XLineStartCenterItem( false ) ); + } + if( bLineEnd ) + { + xSdrObj->SetMergedItem( XLineEndItem( OUString(), aArrowPolyPoly ) ); + xSdrObj->SetMergedItem( XLineEndWidthItem( nWidth ) ); + xSdrObj->SetMergedItem( XLineEndCenterItem( false ) ); + } + } + rDffConv.Progress(); + return xSdrObj; +} + +XclImpRectObj::XclImpRectObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnFrameFlags( 0 ) +{ + SetAreaObj( true ); +} + +void XclImpRectObj::ReadFrameData( XclImpStream& rStrm ) +{ + rStrm >> maFillData >> maLineData; + mnFrameFlags = rStrm.ReaduInt16(); +} + +void XclImpRectObj::ConvertRectStyle( SdrObject& rSdrObj ) const +{ + ConvertLineStyle( rSdrObj, maLineData ); + ConvertFillStyle( rSdrObj, maFillData ); + ConvertFrameStyle( rSdrObj, mnFrameFlags ); +} + +void XclImpRectObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpRectObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpRectObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +SdrObjectUniquePtr XclImpRectObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj( + new SdrRectObj( + *GetDoc().GetDrawLayer(), + rAnchorRect)); + ConvertRectStyle( *xSdrObj ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpOvalObj::XclImpOvalObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ) +{ +} + +SdrObjectUniquePtr XclImpOvalObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj( + new SdrCircObj( + *GetDoc().GetDrawLayer(), + SdrCircKind::Full, + rAnchorRect)); + ConvertRectStyle( *xSdrObj ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpArcObj::XclImpArcObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnQuadrant( EXC_OBJ_ARC_TR ) +{ + SetAreaObj( false ); // arc may be 2-dimensional +} + +void XclImpArcObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maFillData >> maLineData; + mnQuadrant = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpArcObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maFillData >> maLineData; + mnQuadrant = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpArcObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + rStrm >> maFillData >> maLineData; + mnQuadrant = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +SdrObjectUniquePtr XclImpArcObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + tools::Rectangle aNewRect = rAnchorRect; + Degree100 nStartAngle; + Degree100 nEndAngle; + switch( mnQuadrant ) + { + default: + case EXC_OBJ_ARC_TR: + nStartAngle = 0_deg100; + nEndAngle = 9000_deg100; + aNewRect.AdjustLeft( -(rAnchorRect.GetWidth()) ); + aNewRect.AdjustBottom(rAnchorRect.GetHeight() ); + break; + case EXC_OBJ_ARC_TL: + nStartAngle = 9000_deg100; + nEndAngle = 18000_deg100; + aNewRect.AdjustRight(rAnchorRect.GetWidth() ); + aNewRect.AdjustBottom(rAnchorRect.GetHeight() ); + break; + case EXC_OBJ_ARC_BL: + nStartAngle = 18000_deg100; + nEndAngle = 27000_deg100; + aNewRect.AdjustRight(rAnchorRect.GetWidth() ); + aNewRect.AdjustTop( -(rAnchorRect.GetHeight()) ); + break; + case EXC_OBJ_ARC_BR: + nStartAngle = 27000_deg100; + nEndAngle = 0_deg100; + aNewRect.AdjustLeft( -(rAnchorRect.GetWidth()) ); + aNewRect.AdjustTop( -(rAnchorRect.GetHeight()) ); + break; + } + SdrCircKind eObjKind = maFillData.IsFilled() ? SdrCircKind::Section : SdrCircKind::Arc; + SdrObjectUniquePtr xSdrObj( + new SdrCircObj( + *GetDoc().GetDrawLayer(), + eObjKind, + aNewRect, + nStartAngle, + nEndAngle)); + ConvertFillStyle( *xSdrObj, maFillData ); + ConvertLineStyle( *xSdrObj, maLineData ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpPolygonObj::XclImpPolygonObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ), + mnPolyFlags( 0 ), + mnPointCount( 0 ) +{ + SetAreaObj( false ); // polygon may be 2-dimensional +} + +void XclImpPolygonObj::ReadCoordList( XclImpStream& rStrm ) +{ + if( (rStrm.GetNextRecId() == EXC_ID_COORDLIST) && rStrm.StartNextRecord() ) + { + OSL_ENSURE( rStrm.GetRecLeft() / 4 == mnPointCount, "XclImpPolygonObj::ReadCoordList - wrong polygon point count" ); + while (true) + { + if (rStrm.GetRecLeft() < 4) + break; + sal_uInt16 nX = rStrm.ReaduInt16(); + sal_uInt16 nY = rStrm.ReaduInt16(); + maCoords.emplace_back( nX, nY ); + } + } +} + +void XclImpPolygonObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + mnPolyFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnPointCount = rStrm.ReaduInt16(); + rStrm.Ignore( 8 ); + ReadMacro4( rStrm, nMacroSize ); + ReadCoordList( rStrm ); +} + +void XclImpPolygonObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + mnPolyFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnPointCount = rStrm.ReaduInt16(); + rStrm.Ignore( 8 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + ReadCoordList( rStrm ); +} + +namespace { + +::basegfx::B2DPoint lclGetPolyPoint( const tools::Rectangle& rAnchorRect, const Point& rPoint ) +{ + return ::basegfx::B2DPoint( + rAnchorRect.Left() + static_cast< sal_Int32 >( ::std::min< double >( rPoint.X(), 16384.0 ) / 16384.0 * rAnchorRect.GetWidth() + 0.5 ), + rAnchorRect.Top() + static_cast< sal_Int32 >( ::std::min< double >( rPoint.Y(), 16384.0 ) / 16384.0 * rAnchorRect.GetHeight() + 0.5 ) ); +} + +} // namespace + +SdrObjectUniquePtr XclImpPolygonObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj; + if( maCoords.size() >= 2 ) + { + // create the polygon + ::basegfx::B2DPolygon aB2DPolygon; + for( const auto& rCoord : maCoords ) + aB2DPolygon.append( lclGetPolyPoint( rAnchorRect, rCoord ) ); + // close polygon if specified + if( ::get_flag( mnPolyFlags, EXC_OBJ_POLY_CLOSED ) && (maCoords.front() != maCoords.back()) ) + aB2DPolygon.append( lclGetPolyPoint( rAnchorRect, maCoords.front() ) ); + // create the SdrObject + SdrObjKind eObjKind = maFillData.IsFilled() ? SdrObjKind::PathPoly : SdrObjKind::PathPolyLine; + xSdrObj.reset( + new SdrPathObj( + *GetDoc().GetDrawLayer(), + eObjKind, + ::basegfx::B2DPolyPolygon(aB2DPolygon))); + ConvertRectStyle( *xSdrObj ); + } + rDffConv.Progress(); + return xSdrObj; +} + +void XclImpObjTextData::ReadByteString( XclImpStream& rStrm ) +{ + mxString.reset(); + if( maData.mnTextLen > 0 ) + { + mxString = std::make_shared<XclImpString>( rStrm.ReadRawByteString( maData.mnTextLen ) ); + // skip padding byte for word boundaries + if( rStrm.GetRecPos() & 1 ) rStrm.Ignore( 1 ); + } +} + +void XclImpObjTextData::ReadFormats( XclImpStream& rStrm ) +{ + if( mxString ) + mxString->ReadObjFormats( rStrm, maData.mnFormatSize ); + else + rStrm.Ignore( maData.mnFormatSize ); +} + +XclImpTextObj::XclImpTextObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ) +{ +} + +void XclImpTextObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + maTextData.maData.ReadObj3( rStrm ); + ReadMacro3( rStrm, nMacroSize ); + maTextData.ReadByteString( rStrm ); + maTextData.ReadFormats( rStrm ); +} + +void XclImpTextObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + maTextData.maData.ReadObj3( rStrm ); + ReadMacro4( rStrm, nMacroSize ); + maTextData.ReadByteString( rStrm ); + maTextData.ReadFormats( rStrm ); +} + +void XclImpTextObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + maTextData.maData.ReadObj5( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + maTextData.ReadByteString( rStrm ); + rStrm.Ignore( maTextData.maData.mnLinkSize ); // ignore text link formula + maTextData.ReadFormats( rStrm ); +} + +SdrObjectUniquePtr XclImpTextObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + std::unique_ptr<SdrObjCustomShape, SdrObjectFreeOp> xSdrObj( + new SdrObjCustomShape( + *GetDoc().GetDrawLayer())); + xSdrObj->NbcSetSnapRect( rAnchorRect ); + OUString aRectType = "rectangle"; + xSdrObj->MergeDefaultAttributes( &aRectType ); + ConvertRectStyle( *xSdrObj ); + bool bAutoSize = ::get_flag( maTextData.maData.mnFlags, EXC_OBJ_TEXT_AUTOSIZE ); + xSdrObj->SetMergedItem( makeSdrTextAutoGrowWidthItem( bAutoSize ) ); + xSdrObj->SetMergedItem( makeSdrTextAutoGrowHeightItem( bAutoSize ) ); + xSdrObj->SetMergedItem( makeSdrTextWordWrapItem( true ) ); + rDffConv.Progress(); + return xSdrObj; +} + +void XclImpTextObj::DoPreProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + // set text data + if( SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( &rSdrObj ) ) + { + if( maTextData.mxString ) + { + if( maTextData.mxString->IsRich() ) + { + if (maTextData.mxString->GetText().getLength() > 1024 && utl::ConfigManager::IsFuzzing()) + { + SAL_WARN("sc.filter", "truncating slow long rich text for fuzzing performance"); + maTextData.mxString->SetText(maTextData.mxString->GetText().copy(0, 1024)); + } + + // rich text + std::unique_ptr< EditTextObject > xEditObj( + XclImpStringHelper::CreateTextObject( GetRoot(), *maTextData.mxString ) ); + OutlinerParaObject aOutlineObj(std::move(xEditObj)); + aOutlineObj.SetOutlinerMode( OutlinerMode::TextObject ); + pTextObj->NbcSetOutlinerParaObject( std::move(aOutlineObj) ); + } + else + { + // plain text + pTextObj->NbcSetText( maTextData.mxString->GetText() ); + } + + /* #i96858# Do not apply any formatting if there is no text. + SdrObjCustomShape::SetVerticalWriting (initiated from + SetMergedItem) calls SdrTextObj::ForceOutlinerParaObject which + ensures that we can erroneously write a ClientTextbox record + (with no content) while exporting to XLS, which can cause a + corrupted exported document. */ + + SvxAdjust eHorAlign = SvxAdjust::Left; + SdrTextVertAdjust eVerAlign = SDRTEXTVERTADJUST_TOP; + + // orientation (this is only a fake, drawing does not support real text orientation) + namespace csst = ::com::sun::star::text; + csst::WritingMode eWriteMode = csst::WritingMode_LR_TB; + switch( maTextData.maData.mnOrient ) + { + default: + case EXC_OBJ_ORIENT_NONE: + { + eWriteMode = csst::WritingMode_LR_TB; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: eHorAlign = SvxAdjust::Left; break; + case EXC_OBJ_HOR_CENTER: eHorAlign = SvxAdjust::Center; break; + case EXC_OBJ_HOR_RIGHT: eHorAlign = SvxAdjust::Right; break; + case EXC_OBJ_HOR_JUSTIFY: eHorAlign = SvxAdjust::Block; break; + } + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eVerAlign = SDRTEXTVERTADJUST_TOP; break; + case EXC_OBJ_VER_CENTER: eVerAlign = SDRTEXTVERTADJUST_CENTER; break; + case EXC_OBJ_VER_BOTTOM: eVerAlign = SDRTEXTVERTADJUST_BOTTOM; break; + case EXC_OBJ_VER_JUSTIFY: eVerAlign = SDRTEXTVERTADJUST_BLOCK; break; + } + } + break; + + case EXC_OBJ_ORIENT_90CCW: + { + if( SdrObjCustomShape* pObjCustomShape = dynamic_cast< SdrObjCustomShape* >( &rSdrObj ) ) + { + css::beans::PropertyValue aTextRotateAngle; + aTextRotateAngle.Name = "TextRotateAngle"; + aTextRotateAngle.Value <<= 180.0; + SdrCustomShapeGeometryItem aGeometryItem(pObjCustomShape->GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY )); + aGeometryItem.SetPropertyValue( aTextRotateAngle ); + pObjCustomShape->SetMergedItem( aGeometryItem ); + } + eWriteMode = csst::WritingMode_TB_RL; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: eVerAlign = SDRTEXTVERTADJUST_TOP; break; + case EXC_OBJ_HOR_CENTER: eVerAlign = SDRTEXTVERTADJUST_CENTER; break; + case EXC_OBJ_HOR_RIGHT: eVerAlign = SDRTEXTVERTADJUST_BOTTOM; break; + case EXC_OBJ_HOR_JUSTIFY: eVerAlign = SDRTEXTVERTADJUST_BLOCK; break; + } + MSO_Anchor eTextAnchor = static_cast<MSO_Anchor>(rDffConv.GetPropertyValue( DFF_Prop_anchorText, mso_anchorTop )); + switch( eTextAnchor ) + { + case mso_anchorTopCentered : + case mso_anchorMiddleCentered : + case mso_anchorBottomCentered : + { + eHorAlign = SvxAdjust::Center; + } + break; + + default: + { + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eHorAlign = SvxAdjust::Right; break; + case EXC_OBJ_VER_CENTER: eHorAlign = SvxAdjust::Center; break; + case EXC_OBJ_VER_BOTTOM: eHorAlign = SvxAdjust::Left; break; + case EXC_OBJ_VER_JUSTIFY: eHorAlign = SvxAdjust::Block; break; + } + } + } + } + break; + + case EXC_OBJ_ORIENT_STACKED: + { + // sj: STACKED is not supported, maybe it can be optimized here a bit + [[fallthrough]]; + } + case EXC_OBJ_ORIENT_90CW: + { + eWriteMode = csst::WritingMode_TB_RL; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: eVerAlign = SDRTEXTVERTADJUST_BOTTOM; break; + case EXC_OBJ_HOR_CENTER: eVerAlign = SDRTEXTVERTADJUST_CENTER; break; + case EXC_OBJ_HOR_RIGHT: eVerAlign = SDRTEXTVERTADJUST_TOP; break; + case EXC_OBJ_HOR_JUSTIFY: eVerAlign = SDRTEXTVERTADJUST_BLOCK; break; + } + MSO_Anchor eTextAnchor = static_cast<MSO_Anchor>(rDffConv.GetPropertyValue( DFF_Prop_anchorText, mso_anchorTop )); + switch ( eTextAnchor ) + { + case mso_anchorTopCentered : + case mso_anchorMiddleCentered : + case mso_anchorBottomCentered : + { + eHorAlign = SvxAdjust::Center; + } + break; + + default: + { + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eHorAlign = SvxAdjust::Left; break; + case EXC_OBJ_VER_CENTER: eHorAlign = SvxAdjust::Center; break; + case EXC_OBJ_VER_BOTTOM: eHorAlign = SvxAdjust::Right; break; + case EXC_OBJ_VER_JUSTIFY: eHorAlign = SvxAdjust::Block; break; + } + } + } + } + break; + } + rSdrObj.SetMergedItem( SvxAdjustItem( eHorAlign, EE_PARA_JUST ) ); + rSdrObj.SetMergedItem( SdrTextVertAdjustItem( eVerAlign ) ); + rSdrObj.SetMergedItem( SvxWritingModeItem( eWriteMode, SDRATTR_TEXTDIRECTION ) ); + } + } + // base class processing + XclImpRectObj::DoPreProcessSdrObj( rDffConv, rSdrObj ); +} + +XclImpChartObj::XclImpChartObj( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpRectObj( rRoot ), + mbOwnTab( bOwnTab ) +{ + SetSimpleMacro( false ); + SetCustomDffObj( true ); +} + +void XclImpChartObj::ReadChartSubStream( XclImpStream& rStrm ) +{ + /* If chart is read from a chartsheet (mbOwnTab == true), the BOF record + has already been read. If chart is embedded as object, the next record + has to be the BOF record. */ + if( mbOwnTab ) + { + /* #i109800# The input stream may point somewhere inside the chart + substream and not exactly to the leading BOF record. To read this + record correctly in the following, the stream has to rewind it, so + that the next call to StartNextRecord() will find it correctly. */ + if( rStrm.GetRecId() != EXC_ID5_BOF ) + rStrm.RewindRecord(); + } + else + { + if( (rStrm.GetNextRecId() == EXC_ID5_BOF) && rStrm.StartNextRecord() ) + { + sal_uInt16 nBofType; + rStrm.Seek( 2 ); + nBofType = rStrm.ReaduInt16(); + SAL_WARN_IF( nBofType != EXC_BOF_CHART, "sc.filter", "XclImpChartObj::ReadChartSubStream - no chart BOF record" ); + } + else + { + SAL_INFO("sc.filter", "XclImpChartObj::ReadChartSubStream - missing chart substream"); + return; + } + } + + // read chart, even if BOF record contains wrong substream identifier + mxChart = std::make_shared<XclImpChart>( GetRoot(), mbOwnTab ); + mxChart->ReadChartSubStream( rStrm ); + if( mbOwnTab ) + FinalizeTabChart(); +} + +void XclImpChartObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + // read OBJ record and the following chart substream + ReadFrameData( rStrm ); + rStrm.Ignore( 18 ); + ReadMacro3( rStrm, nMacroSize ); + // set frame format from OBJ record, it is used if chart itself is transparent + if( mxChart ) + mxChart->UpdateObjFrame( maLineData, maFillData ); +} + +void XclImpChartObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + // read OBJ record and the following chart substream + ReadFrameData( rStrm ); + rStrm.Ignore( 18 ); + ReadMacro4( rStrm, nMacroSize ); + // set frame format from OBJ record, it is used if chart itself is transparent + if( mxChart ) + mxChart->UpdateObjFrame( maLineData, maFillData ); +} + +void XclImpChartObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + // read OBJ record and the following chart substream + ReadFrameData( rStrm ); + rStrm.Ignore( 18 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + ReadChartSubStream( rStrm ); + // set frame format from OBJ record, it is used if chart itself is transparent + if( mxChart ) + mxChart->UpdateObjFrame( maLineData, maFillData ); +} + +void XclImpChartObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 /*nSubRecSize*/ ) +{ + // read the following chart substream + if( nSubRecId == EXC_ID_OBJEND ) + { + // enable CONTINUE handling for the entire chart substream + rStrm.ResetRecord( true ); + ReadChartSubStream( rStrm ); + /* disable CONTINUE handling again to be able to read + following CONTINUE records as MSODRAWING records. */ + rStrm.ResetRecord( false ); + } +} + +std::size_t XclImpChartObj::DoGetProgressSize() const +{ + return mxChart ? mxChart->GetProgressSize() : 1; +} + +SdrObjectUniquePtr XclImpChartObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj; + SfxObjectShell* pDocShell = GetDocShell(); + if( rDffConv.SupportsOleObjects() && SvtModuleOptions().IsChart() && pDocShell && mxChart && !mxChart->IsPivotChart() ) + { + // create embedded chart object + OUString aEmbObjName; + OUString sBaseURL(GetRoot().GetMedium().GetBaseURL()); + Reference< XEmbeddedObject > xEmbObj = pDocShell->GetEmbeddedObjectContainer(). + CreateEmbeddedObject( SvGlobalName( SO3_SCH_CLASSID ).GetByteSequence(), aEmbObjName, &sBaseURL ); + + if (!xEmbObj) + return xSdrObj; + + /* Set the size to the embedded object, this prevents that font sizes + of text objects are changed in the chart when the object is + inserted into the draw page. */ + sal_Int64 nAspect = css::embed::Aspects::MSOLE_CONTENT; + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xEmbObj->getMapUnit( nAspect ) ); + Size aSize( OutputDevice::LogicToLogic( rAnchorRect.GetSize(), MapMode( MapUnit::Map100thMM ), MapMode( aUnit ) ) ); + css::awt::Size aAwtSize( aSize.Width(), aSize.Height() ); + xEmbObj->setVisualAreaSize( nAspect, aAwtSize ); + + // #i121334# This call will change the chart's default background fill from white to transparent. + // Add here again if this is wanted (see task description for details) + // ChartHelper::AdaptDefaultsForChart( xEmbObj ); + + // create the container OLE object + xSdrObj.reset( + new SdrOle2Obj( + *GetDoc().GetDrawLayer(), + svt::EmbeddedObjectRef(xEmbObj, nAspect), + aEmbObjName, + rAnchorRect)); + } + + return xSdrObj; +} + +void XclImpChartObj::DoPostProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + const SdrOle2Obj* pSdrOleObj = dynamic_cast< const SdrOle2Obj* >( &rSdrObj ); + if( !(mxChart && pSdrOleObj) ) + return; + + const Reference< XEmbeddedObject >& xEmbObj = pSdrOleObj->GetObjRef(); + if( xEmbObj.is() && ::svt::EmbeddedObjectRef::TryRunningState( xEmbObj ) ) try + { + Reference< XEmbedPersist > xPersist( xEmbObj, UNO_QUERY_THROW ); + Reference< XModel > xModel( xEmbObj->getComponent(), UNO_QUERY_THROW ); + mxChart->Convert( xModel, rDffConv, xPersist->getEntryName(), rSdrObj.GetLogicRect() ); + } + catch( const Exception& ) + { + } +} + +void XclImpChartObj::FinalizeTabChart() +{ + /* #i44077# Calculate and store DFF anchor for sheet charts. + Needed to get used area if this chart is inserted as OLE object. */ + OSL_ENSURE( mbOwnTab, "XclImpChartObj::FinalizeTabChart - not allowed for embedded chart objects" ); + + // set uninitialized page to landscape + if( !GetPageSettings().GetPageData().mbValid ) + GetPageSettings().SetPaperSize( EXC_PAPERSIZE_DEFAULT, false ); + + // calculate size of the chart object + const XclPageData& rPageData = GetPageSettings().GetPageData(); + Size aPaperSize = rPageData.GetScPaperSize(); + + tools::Long nWidth = XclTools::GetHmmFromTwips( aPaperSize.Width() ); + tools::Long nHeight = XclTools::GetHmmFromTwips( aPaperSize.Height() ); + + // subtract page margins, give some more extra space + nWidth -= o3tl::saturating_add(XclTools::GetHmmFromInch(rPageData.mfLeftMargin + rPageData.mfRightMargin), static_cast<sal_Int32>(2000)); + nHeight -= o3tl::saturating_add(XclTools::GetHmmFromInch(rPageData.mfTopMargin + rPageData.mfBottomMargin), static_cast<sal_Int32>(1000)); + + // print column/row headers? + if( rPageData.mbPrintHeadings ) + { + nWidth -= 2000; + nHeight -= 1000; + } + + // create the object anchor + XclObjAnchor aAnchor; + aAnchor.SetRect( GetRoot(), GetCurrScTab(), tools::Rectangle( 1000, 500, nWidth, nHeight ), MapUnit::Map100thMM ); + SetAnchor( aAnchor ); +} + +XclImpNoteObj::XclImpNoteObj( const XclImpRoot& rRoot ) : + XclImpTextObj( rRoot ), + maScPos( ScAddress::INITIALIZE_INVALID ), + mnNoteFlags( 0 ) +{ + SetSimpleMacro( false ); + // caption object will be created manually + SetInsertSdrObj( false ); +} + +void XclImpNoteObj::SetNoteData( const ScAddress& rScPos, sal_uInt16 nNoteFlags ) +{ + maScPos = rScPos; + mnNoteFlags = nNoteFlags; +} + +void XclImpNoteObj::DoPreProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + // create formatted text + XclImpTextObj::DoPreProcessSdrObj( rDffConv, rSdrObj ); + OutlinerParaObject* pOutlinerObj = rSdrObj.GetOutlinerParaObject(); + if( maScPos.IsValid() && pOutlinerObj ) + { + // create cell note with all data from drawing object + ScNoteUtil::CreateNoteFromObjectData( + GetDoc(), maScPos, + rSdrObj.GetMergedItemSet().CloneAsValue(), // new object on heap expected + *pOutlinerObj, + rSdrObj.GetLogicRect(), + ::get_flag( mnNoteFlags, EXC_NOTE_VISIBLE ) ); + } +} + +XclImpControlHelper::XclImpControlHelper( const XclImpRoot& rRoot, XclCtrlBindMode eBindMode ) : + mrRoot( rRoot ), + meBindMode( eBindMode ) +{ +} + +XclImpControlHelper::~XclImpControlHelper() +{ +} + +SdrObjectUniquePtr XclImpControlHelper::CreateSdrObjectFromShape( + const Reference< XShape >& rxShape, const tools::Rectangle& rAnchorRect ) const +{ + mxShape = rxShape; + SdrObjectUniquePtr xSdrObj( SdrObject::getSdrObjectFromXShape( rxShape ) ); + if( xSdrObj ) + { + xSdrObj->NbcSetSnapRect( rAnchorRect ); + // #i30543# insert into control layer + xSdrObj->NbcSetLayer( SC_LAYER_CONTROLS ); + } + return xSdrObj; +} + +void XclImpControlHelper::ApplySheetLinkProps() const +{ + + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( mxShape ); + if( !xCtrlModel.is() ) + return; + + // sheet links + SfxObjectShell* pDocShell = mrRoot.GetDocShell(); + if(!pDocShell) + return; + + Reference< XMultiServiceFactory > xFactory( pDocShell->GetModel(), UNO_QUERY ); + if( !xFactory.is() ) + return; + + // cell link + if( mxCellLink ) try + { + Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY_THROW ); + + // create argument sequence for createInstanceWithArguments() + CellAddress aApiAddress; + ScUnoConversion::FillApiAddress( aApiAddress, *mxCellLink ); + + NamedValue aValue; + aValue.Name = SC_UNONAME_BOUNDCELL; + aValue.Value <<= aApiAddress; + + Sequence< Any > aArgs{ Any(aValue) }; + + // create the CellValueBinding instance and set at the control model + OUString aServiceName; + switch( meBindMode ) + { + case EXC_CTRL_BINDCONTENT: aServiceName = SC_SERVICENAME_VALBIND; break; + case EXC_CTRL_BINDPOSITION: aServiceName = SC_SERVICENAME_LISTCELLBIND; break; + } + Reference< XValueBinding > xBinding( + xFactory->createInstanceWithArguments( aServiceName, aArgs ), UNO_QUERY_THROW ); + xBindable->setValueBinding( xBinding ); + } + catch( const Exception& ) + { + } + + // source range + if( !mxSrcRange ) + return; + + try + { + Reference< XListEntrySink > xEntrySink( xCtrlModel, UNO_QUERY_THROW ); + + // create argument sequence for createInstanceWithArguments() + CellRangeAddress aApiRange; + ScUnoConversion::FillApiRange( aApiRange, *mxSrcRange ); + + NamedValue aValue; + aValue.Name = SC_UNONAME_CELLRANGE; + aValue.Value <<= aApiRange; + + Sequence< Any > aArgs{ Any(aValue) }; + + // create the EntrySource instance and set at the control model + Reference< XListEntrySource > xEntrySource( xFactory->createInstanceWithArguments( + SC_SERVICENAME_LISTSOURCE, aArgs ), UNO_QUERY_THROW ); + xEntrySink->setListEntrySource( xEntrySource ); + } + catch( const Exception& ) + { + } +} + +void XclImpControlHelper::ProcessControl( const XclImpDrawObjBase& rDrawObj ) const +{ + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( mxShape ); + if( !xCtrlModel.is() ) + return; + + ApplySheetLinkProps(); + + ScfPropertySet aPropSet( xCtrlModel ); + + // #i51348# set object name at control model + aPropSet.SetStringProperty( "Name", rDrawObj.GetObjName() ); + + // control visible and printable? + aPropSet.SetBoolProperty( "EnableVisible", rDrawObj.IsVisible() ); + aPropSet.SetBoolProperty( "Printable", rDrawObj.IsPrintable() ); + + // virtual call for type specific processing + DoProcessControl( aPropSet ); +} + +void XclImpControlHelper::ReadCellLinkFormula( XclImpStream& rStrm, bool bWithBoundSize ) +{ + ScRangeList aScRanges; + ReadRangeList( aScRanges, rStrm, bWithBoundSize ); + // Use first cell of first range + if ( !aScRanges.empty() ) + { + const ScRange & rScRange = aScRanges.front(); + mxCellLink = std::make_shared<ScAddress>( rScRange.aStart ); + } +} + +void XclImpControlHelper::ReadSourceRangeFormula( XclImpStream& rStrm, bool bWithBoundSize ) +{ + ScRangeList aScRanges; + ReadRangeList( aScRanges, rStrm, bWithBoundSize ); + // Use first range + if ( !aScRanges.empty() ) + { + const ScRange & rScRange = aScRanges.front(); + mxSrcRange = std::make_shared<ScRange>( rScRange ); + } +} + +void XclImpControlHelper::DoProcessControl( ScfPropertySet& ) const +{ +} + +void XclImpControlHelper::ReadRangeList( ScRangeList& rScRanges, XclImpStream& rStrm ) +{ + XclTokenArray aXclTokArr; + sal_uInt16 nSize = XclTokenArray::ReadSize(rStrm); + rStrm.Ignore( 4 ); + aXclTokArr.ReadArray(nSize, rStrm); + mrRoot.GetFormulaCompiler().CreateRangeList( rScRanges, EXC_FMLATYPE_CONTROL, aXclTokArr, rStrm ); +} + +void XclImpControlHelper::ReadRangeList( ScRangeList& rScRanges, XclImpStream& rStrm, bool bWithBoundSize ) +{ + if( bWithBoundSize ) + { + sal_uInt16 nSize; + nSize = rStrm.ReaduInt16(); + if( nSize > 0 ) + { + rStrm.PushPosition(); + ReadRangeList( rScRanges, rStrm ); + rStrm.PopPosition(); + rStrm.Ignore( nSize ); + } + } + else + { + ReadRangeList( rScRanges, rStrm ); + } +} + +XclImpTbxObjBase::XclImpTbxObjBase( const XclImpRoot& rRoot ) : + XclImpTextObj( rRoot ), + XclImpControlHelper( rRoot, EXC_CTRL_BINDPOSITION ) +{ + SetSimpleMacro( false ); + SetCustomDffObj( true ); +} + +namespace { + +void lclExtractColor( sal_uInt8& rnColorIdx, const DffPropSet& rDffPropSet, sal_uInt32 nPropId ) +{ + if( rDffPropSet.IsProperty( nPropId ) ) + { + sal_uInt32 nColor = rDffPropSet.GetPropertyValue( nPropId, 0 ); + if( (nColor & 0xFF000000) == 0x08000000 ) + rnColorIdx = ::extract_value< sal_uInt8 >( nColor, 0, 8 ); + } +} + +} // namespace + +void XclImpTbxObjBase::SetDffProperties( const DffPropSet& rDffPropSet ) +{ + maFillData.mnPattern = rDffPropSet.GetPropertyBool( DFF_Prop_fFilled ) ? EXC_PATT_SOLID : EXC_PATT_NONE; + lclExtractColor( maFillData.mnBackColorIdx, rDffPropSet, DFF_Prop_fillBackColor ); + lclExtractColor( maFillData.mnPattColorIdx, rDffPropSet, DFF_Prop_fillColor ); + ::set_flag( maFillData.mnAuto, EXC_OBJ_LINE_AUTO, false ); + + maLineData.mnStyle = rDffPropSet.GetPropertyBool( DFF_Prop_fLine ) ? EXC_OBJ_LINE_SOLID : EXC_OBJ_LINE_NONE; + lclExtractColor( maLineData.mnColorIdx, rDffPropSet, DFF_Prop_lineColor ); + ::set_flag( maLineData.mnAuto, EXC_OBJ_FILL_AUTO, false ); +} + +bool XclImpTbxObjBase::FillMacroDescriptor( ScriptEventDescriptor& rDescriptor ) const +{ + return XclControlHelper::FillMacroDescriptor( rDescriptor, DoGetEventType(), GetMacroName(), GetDocShell() ); +} + +void XclImpTbxObjBase::ConvertFont( ScfPropertySet& rPropSet ) const +{ + if( maTextData.mxString ) + { + const XclFormatRunVec& rFormatRuns = maTextData.mxString->GetFormats(); + if( rFormatRuns.empty() ) + GetFontBuffer().WriteDefaultCtrlFontProperties( rPropSet ); + else + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CONTROL, rFormatRuns.front().mnFontIdx ); + } +} + +void XclImpTbxObjBase::ConvertLabel( ScfPropertySet& rPropSet ) const +{ + if( maTextData.mxString ) + { + OUString aLabel = maTextData.mxString->GetText(); + if( maTextData.maData.mnShortcut > 0 ) + { + sal_Int32 nPos = aLabel.indexOf( static_cast< sal_Unicode >( maTextData.maData.mnShortcut ) ); + if( nPos != -1 ) + aLabel = aLabel.replaceAt( nPos, 0, u"~" ); + } + rPropSet.SetStringProperty( "Label", aLabel ); + + //Excel Alt text <==> Aoo description + //For TBX control, if user does not operate alt text, alt text will be set label text as default value in Excel. + //In this case, DFF_Prop_wzDescription will not be set in excel file. + //So In the end of SvxMSDffManager::ImportShape, description will not be set. But actually in excel, + //the alt text is the label value. So here set description as label text first which is called before ImportShape. + Reference< css::beans::XPropertySet > xPropset( mxShape, UNO_QUERY ); + try{ + if(xPropset.is()) + xPropset->setPropertyValue( "Description", Any(aLabel) ); + }catch( ... ) + { + SAL_WARN("sc.filter", "Can't set a default text for TBX Control "); + } + } + ConvertFont( rPropSet ); +} + +SdrObjectUniquePtr XclImpTbxObjBase::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj( rDffConv.CreateSdrObject( *this, rAnchorRect ) ); + rDffConv.Progress(); + return xSdrObj; +} + +void XclImpTbxObjBase::DoPreProcessSdrObj( XclImpDffConverter& /*rDffConv*/, SdrObject& /*rSdrObj*/ ) const +{ + // do not call DoPreProcessSdrObj() from base class (to skip text processing) + ProcessControl( *this ); +} + +XclImpButtonObj::XclImpButtonObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ) +{ +} + +void XclImpButtonObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); + + /* Horizontal text alignment. For unknown reason, the property type is a + simple sal_Int16 and not a com.sun.star.style.HorizontalAlignment. */ + sal_Int16 nHorAlign = 1; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: nHorAlign = 0; break; + case EXC_OBJ_HOR_CENTER: nHorAlign = 1; break; + case EXC_OBJ_HOR_RIGHT: nHorAlign = 2; break; + } + rPropSet.SetProperty( "Align", nHorAlign ); + + // vertical text alignment + namespace csss = ::com::sun::star::style; + csss::VerticalAlignment eVerAlign = csss::VerticalAlignment_MIDDLE; + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eVerAlign = csss::VerticalAlignment_TOP; break; + case EXC_OBJ_VER_CENTER: eVerAlign = csss::VerticalAlignment_MIDDLE; break; + case EXC_OBJ_VER_BOTTOM: eVerAlign = csss::VerticalAlignment_BOTTOM; break; + } + rPropSet.SetProperty( "VerticalAlign", eVerAlign ); + + // always wrap text automatically + rPropSet.SetBoolProperty( "MultiLine", true ); + + // default button + bool bDefButton = ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_DEFAULT ); + rPropSet.SetBoolProperty( "DefaultButton", bDefButton ); + + // button type (flags cannot be combined in OOo) + namespace cssa = ::com::sun::star::awt; + cssa::PushButtonType eButtonType = cssa::PushButtonType_STANDARD; + if( ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_CLOSE ) ) + eButtonType = cssa::PushButtonType_OK; + else if( ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_CANCEL ) ) + eButtonType = cssa::PushButtonType_CANCEL; + else if( ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_HELP ) ) + eButtonType = cssa::PushButtonType_HELP; + // property type is short, not enum + rPropSet.SetProperty( "PushButtonType", sal_Int16( eButtonType ) ); +} + +OUString XclImpButtonObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.CommandButton"; +} + +XclTbxEventType XclImpButtonObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_ACTION; +} + +XclImpCheckBoxObj::XclImpCheckBoxObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnState( EXC_OBJ_CHECKBOX_UNCHECKED ), + mnCheckBoxFlags( 0 ) +{ +} + +void XclImpCheckBoxObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 20 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + mnState = rStrm.ReaduInt16(); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnCheckBoxFlags = rStrm.ReaduInt16(); +} + +void XclImpCheckBoxObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJCBLS: + // do not read EXC_ID_OBJCBLSDATA, not written by OOo Excel export + mnState = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnCheckBoxFlags = rStrm.ReaduInt16(); + break; + case EXC_ID_OBJCBLSFMLA: + ReadCellLinkFormula( rStrm, false ); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpCheckBoxObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); + + // state + bool bSupportsTristate = GetObjType() == EXC_OBJTYPE_CHECKBOX; + sal_Int16 nApiState = 0; + switch( mnState ) + { + case EXC_OBJ_CHECKBOX_UNCHECKED: nApiState = 0; break; + case EXC_OBJ_CHECKBOX_CHECKED: nApiState = 1; break; + case EXC_OBJ_CHECKBOX_TRISTATE: nApiState = bSupportsTristate ? 2 : 1; break; + } + if( bSupportsTristate ) + rPropSet.SetBoolProperty( "TriState", nApiState == 2 ); + rPropSet.SetProperty( "DefaultState", nApiState ); + + // box style + namespace AwtVisualEffect = ::com::sun::star::awt::VisualEffect; + sal_Int16 nEffect = ::get_flagvalue( mnCheckBoxFlags, EXC_OBJ_CHECKBOX_FLAT, AwtVisualEffect::FLAT, AwtVisualEffect::LOOK3D ); + rPropSet.SetProperty( "VisualEffect", nEffect ); + + // do not wrap text automatically + rPropSet.SetBoolProperty( "MultiLine", false ); + + // #i40279# always centered vertically + namespace csss = ::com::sun::star::style; + rPropSet.SetProperty( "VerticalAlign", csss::VerticalAlignment_MIDDLE ); + + // background color + if( maFillData.IsFilled() ) + { + sal_Int32 nColor = static_cast< sal_Int32 >( GetSolidFillColor( maFillData ) ); + rPropSet.SetProperty( "BackgroundColor", nColor ); + } +} + +OUString XclImpCheckBoxObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.CheckBox"; +} + +XclTbxEventType XclImpCheckBoxObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_ACTION; +} + +XclImpOptionButtonObj::XclImpOptionButtonObj( const XclImpRoot& rRoot ) : + XclImpCheckBoxObj( rRoot ), + mnNextInGroup( 0 ), + mnFirstInGroup( 1 ) +{ +} + +void XclImpOptionButtonObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 32 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + mnState = rStrm.ReaduInt16(); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnCheckBoxFlags = rStrm.ReaduInt16(); + mnNextInGroup = rStrm.ReaduInt16(); + mnFirstInGroup = rStrm.ReaduInt16(); +} + +void XclImpOptionButtonObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJRBODATA: + mnNextInGroup = rStrm.ReaduInt16(); + mnFirstInGroup = rStrm.ReaduInt16(); + break; + default: + XclImpCheckBoxObj::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpOptionButtonObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + XclImpCheckBoxObj::DoProcessControl( rPropSet ); + // TODO: grouping + XclImpOptionButtonObj* pTbxObj = dynamic_cast< XclImpOptionButtonObj* >( GetObjectManager().GetSheetDrawing( GetTab() ).FindDrawObj( mnNextInGroup ).get() ); + if ( pTbxObj && pTbxObj->mnFirstInGroup ) + { + // Group has terminated + // traverse each RadioButton in group and + // a) apply the groupname + // b) propagate the linked cell from the lead radiobutton + // c) apply the correct Ref value + XclImpOptionButtonObj* pLeader = pTbxObj; + + sal_Int32 nRefVal = 1; + do + { + + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( pTbxObj->mxShape ); + if ( xCtrlModel.is() ) + { + ScfPropertySet aProps( xCtrlModel ); + OUString sGroupName = OUString::number( pLeader->GetDffShapeId() ); + + aProps.SetStringProperty( "GroupName", sGroupName ); + aProps.SetStringProperty( "RefValue", OUString::number( nRefVal++ ) ); + if ( pLeader->HasCellLink() && !pTbxObj->HasCellLink() ) + { + // propagate cell link info + pTbxObj->mxCellLink = std::make_shared<ScAddress>( *pLeader->mxCellLink ); + pTbxObj->ApplySheetLinkProps(); + } + pTbxObj = dynamic_cast< XclImpOptionButtonObj* >( GetObjectManager().GetSheetDrawing( GetTab() ).FindDrawObj( pTbxObj->mnNextInGroup ).get() ); + } + else + pTbxObj = nullptr; + } while ( pTbxObj && ( pTbxObj->mnFirstInGroup != 1 ) ); + } + else + { + // not the leader? try and find it + } +} + +OUString XclImpOptionButtonObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.RadioButton"; +} + +XclTbxEventType XclImpOptionButtonObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_ACTION; +} + +XclImpLabelObj::XclImpLabelObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ) +{ +} + +void XclImpLabelObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); + + // text alignment (always top/left aligned) + rPropSet.SetProperty( "Align", sal_Int16( 0 ) ); + namespace csss = ::com::sun::star::style; + rPropSet.SetProperty( "VerticalAlign", csss::VerticalAlignment_TOP ); + + // always wrap text automatically + rPropSet.SetBoolProperty( "MultiLine", true ); +} + +OUString XclImpLabelObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.FixedText"; +} + +XclTbxEventType XclImpLabelObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_MOUSE; +} + +XclImpGroupBoxObj::XclImpGroupBoxObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnGroupBoxFlags( 0 ) +{ +} + +void XclImpGroupBoxObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 26 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16( ); + mnGroupBoxFlags = rStrm.ReaduInt16(); +} + +void XclImpGroupBoxObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJGBODATA: + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnGroupBoxFlags = rStrm.ReaduInt16(); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpGroupBoxObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); +} + +OUString XclImpGroupBoxObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.GroupBox"; +} + +XclTbxEventType XclImpGroupBoxObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_MOUSE; +} + +XclImpDialogObj::XclImpDialogObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ) +{ +} + +void XclImpDialogObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); +} + +OUString XclImpDialogObj::DoGetServiceName() const +{ + // dialog frame faked by a groupbox + return "com.sun.star.form.component.GroupBox"; +} + +XclTbxEventType XclImpDialogObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_MOUSE; +} + +XclImpEditObj::XclImpEditObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnContentType( EXC_OBJ_EDIT_TEXT ), + mnMultiLine( 0 ), + mnScrollBar( 0 ), + mnListBoxObjId( 0 ) +{ +} + +bool XclImpEditObj::IsNumeric() const +{ + return (mnContentType == EXC_OBJ_EDIT_INTEGER) || (mnContentType == EXC_OBJ_EDIT_DOUBLE); +} + +void XclImpEditObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 14 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + mnContentType = rStrm.ReaduInt16(); + mnMultiLine = rStrm.ReaduInt16(); + mnScrollBar = rStrm.ReaduInt16(); + mnListBoxObjId = rStrm.ReaduInt16(); +} + +void XclImpEditObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJEDODATA: + mnContentType = rStrm.ReaduInt16(); + mnMultiLine = rStrm.ReaduInt16(); + mnScrollBar = rStrm.ReaduInt16(); + mnListBoxObjId = rStrm.ReaduInt16(); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpEditObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + if( maTextData.mxString ) + { + OUString aText = maTextData.mxString->GetText(); + if( IsNumeric() ) + { + // TODO: OUString::toDouble() does not handle local decimal separator + rPropSet.SetProperty( "DefaultValue", aText.toDouble() ); + rPropSet.SetBoolProperty( "Spin", mnScrollBar != 0 ); + } + else + { + rPropSet.SetProperty( "DefaultText", aText ); + rPropSet.SetBoolProperty( "MultiLine", mnMultiLine != 0 ); + rPropSet.SetBoolProperty( "VScroll", mnScrollBar != 0 ); + } + } + ConvertFont( rPropSet ); +} + +OUString XclImpEditObj::DoGetServiceName() const +{ + return IsNumeric() ? + OUString( "com.sun.star.form.component.NumericField" ) : + OUString( "com.sun.star.form.component.TextField" ); +} + +XclTbxEventType XclImpEditObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_TEXT; +} + +XclImpTbxObjScrollableBase::XclImpTbxObjScrollableBase( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnValue( 0 ), + mnMin( 0 ), + mnMax( 100 ), + mnStep( 1 ), + mnPageStep( 10 ), + mnOrient( 0 ), + mnThumbWidth( 1 ), + mnScrollFlags( 0 ) +{ +} + +void XclImpTbxObjScrollableBase::ReadSbs( XclImpStream& rStrm ) +{ + rStrm.Ignore( 4 ); + mnValue = rStrm.ReaduInt16(); + mnMin = rStrm.ReaduInt16(); + mnMax = rStrm.ReaduInt16(); + mnStep = rStrm.ReaduInt16(); + mnPageStep = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + mnThumbWidth = rStrm.ReaduInt16(); + mnScrollFlags = rStrm.ReaduInt16(); +} + +void XclImpTbxObjScrollableBase::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJSBS: + ReadSbs( rStrm ); + break; + case EXC_ID_OBJSBSFMLA: + ReadCellLinkFormula( rStrm, false ); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +XclImpSpinButtonObj::XclImpSpinButtonObj( const XclImpRoot& rRoot ) : + XclImpTbxObjScrollableBase( rRoot ) +{ +} + +void XclImpSpinButtonObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); +} + +void XclImpSpinButtonObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // Calc's "Border" property is not the 3D/flat style effect in Excel (#i34712#) + rPropSet.SetProperty( "Border", css::awt::VisualEffect::NONE ); + rPropSet.SetProperty< sal_Int32 >( "DefaultSpinValue", mnValue ); + rPropSet.SetProperty< sal_Int32 >( "SpinValueMin", mnMin ); + rPropSet.SetProperty< sal_Int32 >( "SpinValueMax", mnMax ); + rPropSet.SetProperty< sal_Int32 >( "SpinIncrement", mnStep ); + + // Excel spin buttons always vertical + rPropSet.SetProperty( "Orientation", css::awt::ScrollBarOrientation::VERTICAL ); +} + +OUString XclImpSpinButtonObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.SpinButton"; +} + +XclTbxEventType XclImpSpinButtonObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_VALUE; +} + +XclImpScrollBarObj::XclImpScrollBarObj( const XclImpRoot& rRoot ) : + XclImpTbxObjScrollableBase( rRoot ) +{ +} + +void XclImpScrollBarObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); +} + +void XclImpScrollBarObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // Calc's "Border" property is not the 3D/flat style effect in Excel (#i34712#) + rPropSet.SetProperty( "Border", css::awt::VisualEffect::NONE ); + rPropSet.SetProperty< sal_Int32 >( "DefaultScrollValue", mnValue ); + rPropSet.SetProperty< sal_Int32 >( "ScrollValueMin", mnMin ); + rPropSet.SetProperty< sal_Int32 >( "ScrollValueMax", mnMax ); + rPropSet.SetProperty< sal_Int32 >( "LineIncrement", mnStep ); + rPropSet.SetProperty< sal_Int32 >( "BlockIncrement", mnPageStep ); + rPropSet.SetProperty( "VisibleSize", ::std::min< sal_Int32 >( mnPageStep, 1 ) ); + + namespace AwtScrollOrient = ::com::sun::star::awt::ScrollBarOrientation; + sal_Int32 nApiOrient = ::get_flagvalue( mnOrient, EXC_OBJ_SCROLLBAR_HOR, AwtScrollOrient::HORIZONTAL, AwtScrollOrient::VERTICAL ); + rPropSet.SetProperty( "Orientation", nApiOrient ); +} + +OUString XclImpScrollBarObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.ScrollBar"; +} + +XclTbxEventType XclImpScrollBarObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_VALUE; +} + +XclImpTbxObjListBase::XclImpTbxObjListBase( const XclImpRoot& rRoot ) : + XclImpTbxObjScrollableBase( rRoot ), + mnEntryCount( 0 ), + mnSelEntry( 0 ), + mnListFlags( 0 ), + mnEditObjId( 0 ), + mbHasDefFontIdx( false ) +{ +} + +void XclImpTbxObjListBase::ReadLbsData( XclImpStream& rStrm ) +{ + ReadSourceRangeFormula( rStrm, true ); + mnEntryCount = rStrm.ReaduInt16(); + mnSelEntry = rStrm.ReaduInt16(); + mnListFlags = rStrm.ReaduInt16(); + mnEditObjId = rStrm.ReaduInt16(); +} + +void XclImpTbxObjListBase::SetBoxFormatting( ScfPropertySet& rPropSet ) const +{ + // border style + namespace AwtVisualEffect = ::com::sun::star::awt::VisualEffect; + sal_Int16 nApiBorder = ::get_flagvalue( mnListFlags, EXC_OBJ_LISTBOX_FLAT, AwtVisualEffect::FLAT, AwtVisualEffect::LOOK3D ); + rPropSet.SetProperty( "Border", nApiBorder ); + + // font formatting + if( mbHasDefFontIdx ) + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CONTROL, maTextData.maData.mnDefFontIdx ); + else + GetFontBuffer().WriteDefaultCtrlFontProperties( rPropSet ); +} + +XclImpListBoxObj::XclImpListBoxObj( const XclImpRoot& rRoot ) : + XclImpTbxObjListBase( rRoot ) +{ +} + +void XclImpListBoxObj::ReadFullLbsData( XclImpStream& rStrm, std::size_t nRecLeft ) +{ + std::size_t nRecEnd = rStrm.GetRecPos() + nRecLeft; + ReadLbsData( rStrm ); + OSL_ENSURE( (rStrm.GetRecPos() == nRecEnd) || (rStrm.GetRecPos() + mnEntryCount == nRecEnd), + "XclImpListBoxObj::ReadFullLbsData - invalid size of OBJLBSDATA record" ); + while (rStrm.IsValid()) + { + if (rStrm.GetRecPos() >= nRecEnd) + break; + maSelection.push_back( rStrm.ReaduInt8() ); + } +} + +void XclImpListBoxObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + rStrm.Ignore( 18 ); + maTextData.maData.mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + ReadFullLbsData( rStrm, rStrm.GetRecLeft() ); + mbHasDefFontIdx = true; +} + +void XclImpListBoxObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJLBSDATA: + ReadFullLbsData( rStrm, nSubRecSize ); + break; + default: + XclImpTbxObjListBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpListBoxObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // listbox formatting + SetBoxFormatting( rPropSet ); + + // selection type + sal_uInt8 nSelType = ::extract_value< sal_uInt8 >( mnListFlags, 4, 2 ); + bool bMultiSel = nSelType != EXC_OBJ_LISTBOX_SINGLE; + rPropSet.SetBoolProperty( "MultiSelection", bMultiSel ); + + // selection (do not set, if listbox is linked to a cell) + if( HasCellLink() ) + return; + + ScfInt16Vec aSelVec; + + // multi selection: API expects sequence of list entry indexes + if( bMultiSel ) + { + sal_Int16 nIndex = 0; + for( const auto& rItem : maSelection ) + { + if( rItem != 0 ) + aSelVec.push_back( nIndex ); + ++nIndex; + } + } + // single selection: mnSelEntry is one-based, API expects zero-based + else if( mnSelEntry > 0 ) + aSelVec.push_back( static_cast< sal_Int16 >( mnSelEntry - 1 ) ); + + if( !aSelVec.empty() ) + { + Sequence<sal_Int16> aSelSeq(aSelVec.data(), static_cast<sal_Int32>(aSelVec.size())); + rPropSet.SetProperty( "DefaultSelection", aSelSeq ); + } +} + +OUString XclImpListBoxObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.ListBox"; +} + +XclTbxEventType XclImpListBoxObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_CHANGE; +} + +XclImpDropDownObj::XclImpDropDownObj( const XclImpRoot& rRoot ) : + XclImpTbxObjListBase( rRoot ), + mnLeft( 0 ), + mnTop( 0 ), + mnRight( 0 ), + mnBottom( 0 ), + mnDropDownFlags( 0 ), + mnLineCount( 0 ), + mnMinWidth( 0 ) +{ +} + +sal_uInt16 XclImpDropDownObj::GetDropDownType() const +{ + return ::extract_value< sal_uInt8 >( mnDropDownFlags, 0, 2 ); +} + +void XclImpDropDownObj::ReadFullLbsData( XclImpStream& rStrm ) +{ + ReadLbsData( rStrm ); + mnDropDownFlags = rStrm.ReaduInt16(); + mnLineCount = rStrm.ReaduInt16(); + mnMinWidth = rStrm.ReaduInt16(); + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + // dropdowns of auto-filters have 'simple' style, they don't have a text area + if( GetDropDownType() == EXC_OBJ_DROPDOWN_SIMPLE ) + SetProcessSdrObj( false ); +} + +void XclImpDropDownObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + rStrm.Ignore( 18 ); + maTextData.maData.mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 14 ); + mnLeft = rStrm.ReaduInt16(); + mnTop = rStrm.ReaduInt16(); + mnRight = rStrm.ReaduInt16(); + mnBottom = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + ReadFullLbsData( rStrm ); + mbHasDefFontIdx = true; +} + +void XclImpDropDownObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJLBSDATA: + ReadFullLbsData( rStrm ); + break; + default: + XclImpTbxObjListBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpDropDownObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // dropdown listbox formatting + SetBoxFormatting( rPropSet ); + // enable dropdown button + rPropSet.SetBoolProperty( "Dropdown", true ); + // dropdown line count + rPropSet.SetProperty( "LineCount", mnLineCount ); + + if( GetDropDownType() == EXC_OBJ_DROPDOWN_COMBOBOX ) + { + // text of editable combobox + if( maTextData.mxString ) + rPropSet.SetStringProperty( "DefaultText", maTextData.mxString->GetText() ); + } + else + { + // selection (do not set, if dropdown is linked to a cell) + if( !HasCellLink() && (mnSelEntry > 0) ) + { + Sequence< sal_Int16 > aSelSeq{ o3tl::narrowing<sal_Int16>(mnSelEntry - 1) }; + rPropSet.SetProperty( "DefaultSelection", aSelSeq ); + } + } +} + +OUString XclImpDropDownObj::DoGetServiceName() const +{ + return (GetDropDownType() == EXC_OBJ_DROPDOWN_COMBOBOX) ? + OUString( "com.sun.star.form.component.ComboBox" ) : + OUString( "com.sun.star.form.component.ListBox" ); +} + +XclTbxEventType XclImpDropDownObj::DoGetEventType() const +{ + return (GetDropDownType() == EXC_OBJ_DROPDOWN_COMBOBOX) ? EXC_TBX_EVENT_TEXT : EXC_TBX_EVENT_CHANGE; +} + +XclImpPictureObj::XclImpPictureObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ), + XclImpControlHelper( rRoot, EXC_CTRL_BINDCONTENT ), + mnStorageId( 0 ), + mnCtlsStrmPos( 0 ), + mnCtlsStrmSize( 0 ), + mbEmbedded( false ), + mbLinked( false ), + mbSymbol( false ), + mbControl( false ), + mbUseCtlsStrm( false ) +{ + SetAreaObj( true ); + SetSimpleMacro( true ); + SetCustomDffObj( true ); +} + +OUString XclImpPictureObj::GetOleStorageName() const +{ + OUStringBuffer aStrgName; + if( (mbEmbedded || mbLinked) && !mbControl && (mnStorageId > 0) ) + { + aStrgName = mbEmbedded ? std::u16string_view(u"" EXC_STORAGE_OLE_EMBEDDED) : std::u16string_view(u"" EXC_STORAGE_OLE_LINKED); + static const char spcHexChars[] = "0123456789ABCDEF"; + for( sal_uInt8 nIndex = 32; nIndex > 0; nIndex -= 4 ) + aStrgName.append(OUStringChar( spcHexChars[ ::extract_value< sal_uInt8 >( mnStorageId, nIndex - 4, 4 ) ] )); + } + return aStrgName.makeStringAndClear(); +} + +void XclImpPictureObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + sal_uInt16 nLinkSize; + ReadFrameData( rStrm ); + rStrm.Ignore( 6 ); + nLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + ReadFlags3( rStrm ); + ReadMacro3( rStrm, nMacroSize ); + ReadPictFmla( rStrm, nLinkSize ); + + if( (rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord() ) + maGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); +} + +void XclImpPictureObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + sal_uInt16 nLinkSize; + ReadFrameData( rStrm ); + rStrm.Ignore( 6 ); + nLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + ReadFlags3( rStrm ); + ReadMacro4( rStrm, nMacroSize ); + ReadPictFmla( rStrm, nLinkSize ); + + if( (rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord() ) + maGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); +} + +void XclImpPictureObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + sal_uInt16 nLinkSize; + ReadFrameData( rStrm ); + rStrm.Ignore( 6 ); + nLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + ReadFlags3( rStrm ); + rStrm.Ignore( 4 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + ReadPictFmla( rStrm, nLinkSize ); + + if( (rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord() ) + { + // page background is stored as hidden picture with name "__BkgndObj" + if ( IsHidden() && (GetObjName() == "__BkgndObj") ) + GetPageSettings().ReadImgData( rStrm ); + else + maGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); + } +} + +void XclImpPictureObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJFLAGS: + ReadFlags8( rStrm ); + break; + case EXC_ID_OBJPICTFMLA: + ReadPictFmla( rStrm, rStrm.ReaduInt16() ); + break; + default: + XclImpDrawObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +SdrObjectUniquePtr XclImpPictureObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + // try to create an OLE object or form control + SdrObjectUniquePtr xSdrObj( rDffConv.CreateSdrObject( *this, rAnchorRect ) ); + + // insert a graphic replacement for unsupported ole object ( if none already + // exists ) Hmm ok, it's possibly that there has been some imported + // graphic at a base level but unlikely, normally controls have a valid + // preview in the IMGDATA record ( see below ) + // It might be possible to push such an imported graphic up to this + // XclImpPictureObj instance but there are so many layers of indirection I + // don't see an easy way. This way at least ensures that we can + // avoid a 'blank' shape that can result from a failed control import + if ( !xSdrObj && IsOcxControl() && maGraphic.GetType() == GraphicType::NONE ) + { + const_cast< XclImpPictureObj* >( this )->maGraphic = + SdrOle2Obj::GetEmptyOLEReplacementGraphic(); + } + // no OLE - create a plain picture from IMGDATA record data + if( !xSdrObj && (maGraphic.GetType() != GraphicType::NONE) ) + { + xSdrObj.reset( + new SdrGrafObj( + *GetDoc().GetDrawLayer(), + maGraphic, + rAnchorRect)); + ConvertRectStyle( *xSdrObj ); + } + + rDffConv.Progress(); + return xSdrObj; +} + +OUString XclImpPictureObj::GetObjName() const +{ + if( IsOcxControl() ) + { + OUString sName( GetObjectManager().GetOleNameOverride( GetTab(), GetObjId() ) ); + if (!sName.isEmpty()) + return sName; + } + return XclImpDrawObjBase::GetObjName(); +} + +void XclImpPictureObj::DoPreProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + if( IsOcxControl() ) + { + // do not call XclImpRectObj::DoPreProcessSdrObj(), it would trace missing "printable" feature + ProcessControl( *this ); + } + else if( mbEmbedded || mbLinked ) + { + // trace missing "printable" feature + XclImpRectObj::DoPreProcessSdrObj( rDffConv, rSdrObj ); + + SfxObjectShell* pDocShell = GetDocShell(); + SdrOle2Obj* pOleSdrObj = dynamic_cast< SdrOle2Obj* >( &rSdrObj ); + if( pOleSdrObj && pDocShell ) + { + comphelper::EmbeddedObjectContainer& rEmbObjCont = pDocShell->GetEmbeddedObjectContainer(); + Reference< XEmbeddedObject > xEmbObj = pOleSdrObj->GetObjRef(); + OUString aOldName( pOleSdrObj->GetPersistName() ); + + /* The object persistence should be already in the storage, but + the object still might not be inserted into the container. */ + if( rEmbObjCont.HasEmbeddedObject( aOldName ) ) + { + if( !rEmbObjCont.HasEmbeddedObject( xEmbObj ) ) + // filter code is allowed to call the following method + rEmbObjCont.AddEmbeddedObject( xEmbObj, aOldName ); + } + else + { + /* If the object is still not in container it must be inserted + there, the name must be generated in this case. */ + OUString aNewName; + rEmbObjCont.InsertEmbeddedObject( xEmbObj, aNewName ); + if( aOldName != aNewName ) + // SetPersistName, not SetName + pOleSdrObj->SetPersistName( aNewName ); + } + } + } +} + +void XclImpPictureObj::ReadFlags3( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + nFlags = rStrm.ReaduInt16(); + mbSymbol = ::get_flag( nFlags, EXC_OBJ_PIC_SYMBOL ); +} + +void XclImpPictureObj::ReadFlags8( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + nFlags = rStrm.ReaduInt16(); + mbSymbol = ::get_flag( nFlags, EXC_OBJ_PIC_SYMBOL ); + mbControl = ::get_flag( nFlags, EXC_OBJ_PIC_CONTROL ); + mbUseCtlsStrm = ::get_flag( nFlags, EXC_OBJ_PIC_CTLSSTREAM ); + OSL_ENSURE( mbControl || !mbUseCtlsStrm, "XclImpPictureObj::ReadFlags8 - CTLS stream for controls only" ); + SetProcessSdrObj( mbControl || !mbUseCtlsStrm ); +} + +void XclImpPictureObj::ReadPictFmla( XclImpStream& rStrm, sal_uInt16 nLinkSize ) +{ + std::size_t nLinkEnd = rStrm.GetRecPos() + nLinkSize; + if( nLinkSize >= 6 ) + { + sal_uInt16 nFmlaSize; + nFmlaSize = rStrm.ReaduInt16(); + OSL_ENSURE( nFmlaSize > 0, "XclImpPictureObj::ReadPictFmla - missing link formula" ); + // BIFF3/BIFF4 do not support storages, nothing to do here + if( (nFmlaSize > 0) && (GetBiff() >= EXC_BIFF5) ) + { + rStrm.Ignore( 4 ); + sal_uInt8 nToken; + nToken = rStrm.ReaduInt8(); + + // different processing for linked vs. embedded OLE objects + if( nToken == XclTokenArrayHelper::GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ) ) + { + mbLinked = true; + switch( GetBiff() ) + { + case EXC_BIFF5: + { + sal_Int16 nRefIdx; + sal_uInt16 nNameIdx; + nRefIdx = rStrm.ReadInt16(); + rStrm.Ignore( 8 ); + nNameIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 12 ); + const ExtName* pExtName = GetOldRoot().pExtNameBuff->GetNameByIndex( nRefIdx, nNameIdx ); + if( pExtName && pExtName->IsOLE() ) + mnStorageId = pExtName->nStorageId; + } + break; + case EXC_BIFF8: + { + sal_uInt16 nXti, nExtName; + nXti = rStrm.ReaduInt16(); + nExtName = rStrm.ReaduInt16(); + const XclImpExtName* pExtName = GetLinkManager().GetExternName( nXti, nExtName ); + if( pExtName && (pExtName->GetType() == xlExtOLE) ) + mnStorageId = pExtName->GetStorageId(); + } + break; + default: + DBG_ERROR_BIFF(); + } + } + else if( nToken == XclTokenArrayHelper::GetTokenId( EXC_TOKID_TBL, EXC_TOKCLASS_NONE ) ) + { + mbEmbedded = true; + OSL_ENSURE( nFmlaSize == 5, "XclImpPictureObj::ReadPictFmla - unexpected formula size" ); + rStrm.Ignore( nFmlaSize - 1 ); // token ID already read + if( nFmlaSize & 1 ) + rStrm.Ignore( 1 ); // padding byte + + // a class name may follow inside the picture link + if( rStrm.GetRecPos() + 2 <= nLinkEnd ) + { + sal_uInt16 nLen = rStrm.ReaduInt16(); + if( nLen > 0 ) + maClassName = (GetBiff() == EXC_BIFF8) ? rStrm.ReadUniString( nLen ) : rStrm.ReadRawByteString( nLen ); + } + } + // else: ignore other formulas, e.g. pictures linked to cell ranges + } + } + + // seek behind picture link data + rStrm.Seek( nLinkEnd ); + + // read additional data for embedded OLE objects following the picture link + if( IsOcxControl() ) + { + // #i26521# form controls to be ignored + if( maClassName == "Forms.HTML:Hidden.1" ) + { + SetProcessSdrObj( false ); + return; + } + + if( rStrm.GetRecLeft() <= 8 ) return; + + // position and size of control data in 'Ctls' stream + mnCtlsStrmPos = static_cast< std::size_t >( rStrm.ReaduInt32() ); + mnCtlsStrmSize = static_cast< std::size_t >( rStrm.ReaduInt32() ); + + if( rStrm.GetRecLeft() <= 8 ) return; + + // additional string (16-bit characters), e.g. for progress bar control + sal_uInt32 nAddStrSize; + nAddStrSize = rStrm.ReaduInt32(); + OSL_ENSURE( rStrm.GetRecLeft() >= nAddStrSize + 4, "XclImpPictureObj::ReadPictFmla - missing data" ); + if( rStrm.GetRecLeft() >= nAddStrSize + 4 ) + { + rStrm.Ignore( nAddStrSize ); + // cell link and source range + ReadCellLinkFormula( rStrm, true ); + ReadSourceRangeFormula( rStrm, true ); + } + } + else if( mbEmbedded && (rStrm.GetRecLeft() >= 4) ) + { + mnStorageId = rStrm.ReaduInt32(); + } +} + +// DFF stream conversion ====================================================== + +void XclImpSolverContainer::InsertSdrObjectInfo( SdrObject& rSdrObj, sal_uInt32 nDffShapeId, ShapeFlag nDffFlags ) +{ + if( nDffShapeId > 0 ) + { + maSdrInfoMap[ nDffShapeId ].Set( &rSdrObj, nDffFlags ); + maSdrObjMap[ &rSdrObj ] = nDffShapeId; + } +} + +void XclImpSolverContainer::RemoveSdrObjectInfo( SdrObject& rSdrObj ) +{ + // remove info of passed object from the maps + XclImpSdrObjMap::iterator aIt = maSdrObjMap.find( &rSdrObj ); + if( aIt != maSdrObjMap.end() ) + { + maSdrInfoMap.erase( aIt->second ); + maSdrObjMap.erase( aIt ); + } + + // remove info of all child objects of a group object + if( SdrObjGroup* pGroupObj = dynamic_cast< SdrObjGroup* >( &rSdrObj ) ) + { + if( SdrObjList* pSubList = pGroupObj->GetSubList() ) + { + // iterate flat over the list because this function already works recursively + SdrObjListIter aObjIt( pSubList, SdrIterMode::Flat ); + for( SdrObject* pChildObj = aObjIt.Next(); pChildObj; pChildObj = aObjIt.Next() ) + RemoveSdrObjectInfo( *pChildObj ); + } + } +} + +void XclImpSolverContainer::UpdateConnectorRules() +{ + for (auto const & pRule : aCList) + { + UpdateConnection( pRule->nShapeA, pRule->pAObj, &pRule->nSpFlagsA ); + UpdateConnection( pRule->nShapeB, pRule->pBObj, &pRule->nSpFlagsB ); + UpdateConnection( pRule->nShapeC, pRule->pCObj ); + } +} + +void XclImpSolverContainer::RemoveConnectorRules() +{ + aCList.clear(); + maSdrInfoMap.clear(); + maSdrObjMap.clear(); +} + +void XclImpSolverContainer::UpdateConnection( sal_uInt32 nDffShapeId, SdrObject*& rpSdrObj, ShapeFlag* pnDffFlags ) +{ + XclImpSdrInfoMap::const_iterator aIt = maSdrInfoMap.find( nDffShapeId ); + if( aIt != maSdrInfoMap.end() ) + { + rpSdrObj = aIt->second.mpSdrObj; + if( pnDffFlags ) + *pnDffFlags = aIt->second.mnDffFlags; + } +} + +XclImpSimpleDffConverter::XclImpSimpleDffConverter( const XclImpRoot& rRoot, SvStream& rDffStrm ) : + SvxMSDffManager( rDffStrm, rRoot.GetBasePath(), 0, nullptr, rRoot.GetDoc().GetDrawLayer(), 1440, COL_DEFAULT, nullptr ), + XclImpRoot( rRoot ) +{ + SetSvxMSDffSettings( SVXMSDFF_SETTINGS_CROP_BITMAPS | SVXMSDFF_SETTINGS_IMPORT_EXCEL ); +} + +XclImpSimpleDffConverter::~XclImpSimpleDffConverter() +{ +} + +bool XclImpSimpleDffConverter::GetColorFromPalette( sal_uInt16 nIndex, Color& rColor ) const +{ + Color nColor = GetPalette().GetColor( nIndex ); + + if( nColor == COL_AUTO ) + return false; + + rColor = nColor; + return true; +} + +XclImpDffConverter::XclImpDffConvData::XclImpDffConvData( + XclImpDrawing& rDrawing, SdrModel& rSdrModel, SdrPage& rSdrPage ) : + mrDrawing( rDrawing ), + mrSdrModel( rSdrModel ), + mrSdrPage( rSdrPage ), + mnLastCtrlIndex( -1 ), + mbHasCtrlForm( false ) +{ +} + +constexpr OUStringLiteral gaStdFormName( u"Standard" ); /// Standard name of control forms. + +XclImpDffConverter::XclImpDffConverter( const XclImpRoot& rRoot, SvStream& rDffStrm ) : + XclImpSimpleDffConverter( rRoot, rDffStrm ), + oox::ole::MSConvertOCXControls( rRoot.GetDocShell()->GetModel() ), + mnOleImpFlags( 0 ), + mbNotifyMacroEventRead(false) +{ + const SvtFilterOptions& rFilterOpt = SvtFilterOptions::Get(); + if( rFilterOpt.IsMathType2Math() ) + mnOleImpFlags |= OLE_MATHTYPE_2_STARMATH; + if( rFilterOpt.IsWinWord2Writer() ) + mnOleImpFlags |= OLE_WINWORD_2_STARWRITER; + if( rFilterOpt.IsPowerPoint2Impress() ) + mnOleImpFlags |= OLE_POWERPOINT_2_STARIMPRESS; + + // try to open the 'Ctls' storage stream containing OCX control properties + mxCtlsStrm = OpenStream( EXC_STREAM_CTLS ); + + // default text margin (convert EMU to drawing layer units) + mnDefTextMargin = EXC_OBJ_TEXT_MARGIN; + ScaleEmu( mnDefTextMargin ); +} + +XclImpDffConverter::~XclImpDffConverter() +{ +} + +OUString XclImpObjectManager::GetOleNameOverride( SCTAB nTab, sal_uInt16 nObjId ) +{ + OUString sOleName; + OUString sCodeName = GetExtDocOptions().GetCodeName( nTab ); + + if (mxOleCtrlNameOverride.is() && mxOleCtrlNameOverride->hasByName(sCodeName)) + { + Reference< XIndexContainer > xIdToOleName; + mxOleCtrlNameOverride->getByName( sCodeName ) >>= xIdToOleName; + xIdToOleName->getByIndex( nObjId ) >>= sOleName; + } + + return sOleName; +} + +void XclImpDffConverter::StartProgressBar( std::size_t nProgressSize ) +{ + mxProgress = std::make_shared<ScfProgressBar>( GetDocShell(), STR_PROGRESS_CALCULATING ); + mxProgress->AddSegment( nProgressSize ); + mxProgress->Activate(); +} + +void XclImpDffConverter::Progress( std::size_t nDelta ) +{ + OSL_ENSURE( mxProgress, "XclImpDffConverter::Progress - invalid call, no progress bar" ); + mxProgress->Progress( nDelta ); +} + +void XclImpDffConverter::InitializeDrawing( XclImpDrawing& rDrawing, SdrModel& rSdrModel, SdrPage& rSdrPage ) +{ + XclImpDffConvDataRef xConvData = std::make_shared<XclImpDffConvData>( rDrawing, rSdrModel, rSdrPage ); + maDataStack.push_back( xConvData ); + SetModel( &xConvData->mrSdrModel, 1440 ); +} + +void XclImpDffConverter::ProcessObject( SdrObjList& rObjList, XclImpDrawObjBase& rDrawObj ) +{ + if( !rDrawObj.IsProcessSdrObj() ) + return; + + const XclObjAnchor* pAnchor = rDrawObj.GetAnchor(); + if(!pAnchor) + return; + + tools::Rectangle aAnchorRect = GetConvData().mrDrawing.CalcAnchorRect( *pAnchor, false ); + if( rDrawObj.IsValidSize( aAnchorRect ) ) + { + // CreateSdrObject() recursively creates embedded child objects + SdrObjectUniquePtr xSdrObj( rDrawObj.CreateSdrObject( *this, aAnchorRect, false ) ); + if( xSdrObj ) + rDrawObj.PreProcessSdrObject( *this, *xSdrObj ); + // call InsertSdrObject() also, if SdrObject is missing + InsertSdrObject( rObjList, rDrawObj, xSdrObj.release() ); + } +} + +void XclImpDffConverter::ProcessDrawing( const XclImpDrawObjVector& rDrawObjs ) +{ + SdrPage& rSdrPage = GetConvData().mrSdrPage; + for( const auto& rxDrawObj : rDrawObjs ) + ProcessObject( rSdrPage, *rxDrawObj ); +} + +void XclImpDffConverter::ProcessDrawing( SvStream& rDffStrm ) +{ + if( rDffStrm.TellEnd() > 0 ) + { + rDffStrm.Seek( STREAM_SEEK_TO_BEGIN ); + DffRecordHeader aHeader; + ReadDffRecordHeader( rDffStrm, aHeader ); + OSL_ENSURE( aHeader.nRecType == DFF_msofbtDgContainer, "XclImpDffConverter::ProcessDrawing - unexpected record" ); + if( aHeader.nRecType == DFF_msofbtDgContainer ) + ProcessDgContainer( rDffStrm, aHeader ); + } +} + +void XclImpDffConverter::FinalizeDrawing() +{ + OSL_ENSURE( !maDataStack.empty(), "XclImpDffConverter::FinalizeDrawing - no drawing manager on stack" ); + maDataStack.pop_back(); + // restore previous model at core DFF converter + if( !maDataStack.empty() ) + SetModel( &maDataStack.back()->mrSdrModel, 1440 ); +} + +void XclImpDffConverter::NotifyMacroEventRead() +{ + if (mbNotifyMacroEventRead) + return; + comphelper::DocumentInfo::notifyMacroEventRead(mxModel); + mbNotifyMacroEventRead = true; +} + +SdrObjectUniquePtr XclImpDffConverter::CreateSdrObject( const XclImpTbxObjBase& rTbxObj, const tools::Rectangle& rAnchorRect ) +{ + SdrObjectUniquePtr xSdrObj; + + OUString aServiceName = rTbxObj.GetServiceName(); + if( SupportsOleObjects() && !aServiceName.isEmpty() ) try + { + // create the form control from scratch + Reference< XFormComponent > xFormComp( ScfApiHelper::CreateInstance( GetDocShell(), aServiceName ), UNO_QUERY_THROW ); + // set controls form, needed in virtual function InsertControl() + InitControlForm(); + // try to insert the control into the form + css::awt::Size aDummySize; + Reference< XShape > xShape; + XclImpDffConvData& rConvData = GetConvData(); + if( rConvData.mxCtrlForm.is() && InsertControl( xFormComp, aDummySize, &xShape, true ) ) + { + xSdrObj = rTbxObj.CreateSdrObjectFromShape( xShape, rAnchorRect ); + // try to attach a macro to the control + ScriptEventDescriptor aDescriptor; + if( (rConvData.mnLastCtrlIndex >= 0) && rTbxObj.FillMacroDescriptor( aDescriptor ) ) + { + NotifyMacroEventRead(); + Reference< XEventAttacherManager > xEventMgr( rConvData.mxCtrlForm, UNO_QUERY_THROW ); + xEventMgr->registerScriptEvent( rConvData.mnLastCtrlIndex, aDescriptor ); + } + } + } + catch( const Exception& ) + { + } + + return xSdrObj; +} + +SdrObjectUniquePtr XclImpDffConverter::CreateSdrObject( const XclImpPictureObj& rPicObj, const tools::Rectangle& rAnchorRect ) +{ + SdrObjectUniquePtr xSdrObj; + + if( SupportsOleObjects() ) + { + if( rPicObj.IsOcxControl() ) + { + if( mxCtlsStrm.is() ) try + { + /* set controls form, needed in virtual function InsertControl() + called from ReadOCXExcelKludgeStream() */ + InitControlForm(); + + // read from mxCtlsStrm into xShape, insert the control model into the form + Reference< XShape > xShape; + if( GetConvData().mxCtrlForm.is() ) + { + Reference< XFormComponent > xFComp; + ReadOCXCtlsStream( mxCtlsStrm, xFComp, rPicObj.GetCtlsStreamPos(), rPicObj.GetCtlsStreamSize() ); + // recreate the method formerly known as ReadOCXExcelKludgeStream() + if ( xFComp.is() ) + { + css::awt::Size aSz; // not used in import + ScfPropertySet aPropSet( xFComp ); + aPropSet.SetStringProperty( "Name", rPicObj.GetObjName() ); + InsertControl( xFComp, aSz,&xShape,true); + xSdrObj = rPicObj.CreateSdrObjectFromShape( xShape, rAnchorRect ); + } + } + } + catch( const Exception& ) + { + } + } + else + { + SfxObjectShell* pDocShell = GetDocShell(); + tools::SvRef<SotStorage> xSrcStrg = GetRootStorage(); + OUString aStrgName = rPicObj.GetOleStorageName(); + if( pDocShell && xSrcStrg.is() && (!aStrgName.isEmpty()) ) + { + // first try to resolve graphic from DFF storage + Graphic aGraphic; + tools::Rectangle aVisArea; + if( !GetBLIP( GetPropertyValue( DFF_Prop_pib, 0 ), aGraphic, &aVisArea ) ) + { + // if not found, use graphic from object (imported from IMGDATA record) + aGraphic = rPicObj.GetGraphic(); + } + if( aGraphic.GetType() != GraphicType::NONE ) + { + ErrCode nError = ERRCODE_NONE; + namespace cssea = ::com::sun::star::embed::Aspects; + sal_Int64 nAspects = rPicObj.IsSymbol() ? cssea::MSOLE_ICON : cssea::MSOLE_CONTENT; + xSdrObj.reset( + CreateSdrOLEFromStorage( + GetConvData().mrSdrModel, + aStrgName, + xSrcStrg, + pDocShell->GetStorage(), + aGraphic, + rAnchorRect, + aVisArea, + nullptr, + nError, + mnOleImpFlags, + nAspects, + GetRoot().GetMedium().GetBaseURL())); + } + } + } + } + + return xSdrObj; +} + +bool XclImpDffConverter::SupportsOleObjects() const +{ + return GetConvData().mrDrawing.SupportsOleObjects(); +} + +// virtual functions ---------------------------------------------------------- + +void XclImpDffConverter::ProcessClientAnchor2( SvStream& rDffStrm, + DffRecordHeader& rHeader, DffObjData& rObjData ) +{ + // find the OBJ record data related to the processed shape + XclImpDffConvData& rConvData = GetConvData(); + XclImpDrawObjBase* pDrawObj = rConvData.mrDrawing.FindDrawObj( rObjData.rSpHd ).get(); + if(!pDrawObj) + return; + + OSL_ENSURE( rHeader.nRecType == DFF_msofbtClientAnchor, "XclImpDffConverter::ProcessClientAnchor2 - no client anchor record" ); + XclObjAnchor aAnchor; + rHeader.SeekToContent( rDffStrm ); + sal_uInt8 nFlags(0); + rDffStrm.ReadUChar( nFlags ); + rDffStrm.SeekRel( 1 ); // flags + rDffStrm >> aAnchor; // anchor format equal to BIFF5 OBJ records + + if (!rDffStrm.good()) + { + SAL_WARN("sc.filter", "ProcessClientAnchor2 short read"); + return; + } + + pDrawObj->SetAnchor( aAnchor ); + rObjData.aChildAnchor = rConvData.mrDrawing.CalcAnchorRect( aAnchor, true ); + rObjData.bChildAnchor = true; + // page anchoring is the best approximation we have if mbMove + // is set + rObjData.bPageAnchor = ( nFlags & 0x1 ); +} + +namespace { + +struct XclImpDrawObjClientData : public SvxMSDffClientData +{ + const XclImpDrawObjBase* m_pTopLevelObj; + + XclImpDrawObjClientData() + : m_pTopLevelObj(nullptr) + { + } + virtual void NotifyFreeObj(SdrObject*) override {} +}; + +} + +SdrObject* XclImpDffConverter::ProcessObj( SvStream& rDffStrm, DffObjData& rDffObjData, + SvxMSDffClientData& rClientData, tools::Rectangle& /*rTextRect*/, SdrObject* pOldSdrObj ) +{ + XclImpDffConvData& rConvData = GetConvData(); + + /* pOldSdrObj passes a generated SdrObject. This function owns this object + and can modify it. The function has either to return it back to caller + or to delete it by itself. */ + SdrObjectUniquePtr xSdrObj( pOldSdrObj ); + + // find the OBJ record data related to the processed shape + XclImpDrawObjRef xDrawObj = rConvData.mrDrawing.FindDrawObj( rDffObjData.rSpHd ); + const tools::Rectangle& rAnchorRect = rDffObjData.aChildAnchor; + + // Do not process the global page group shape + bool bGlobalPageGroup( rDffObjData.nSpFlags & ShapeFlag::Patriarch ); + if( !xDrawObj || !xDrawObj->IsProcessSdrObj() || bGlobalPageGroup ) + return nullptr; // simply return, xSdrObj will be destroyed + + /* Pass pointer to top-level object back to caller. If the processed + object is embedded in a group, the pointer is already set to the + top-level parent object. */ + XclImpDrawObjClientData& rDrawObjClientData = static_cast<XclImpDrawObjClientData&>(rClientData); + const bool bIsTopLevel = !rDrawObjClientData.m_pTopLevelObj; + if (bIsTopLevel ) + rDrawObjClientData.m_pTopLevelObj = xDrawObj.get(); + + // connectors don't have to be area objects + if( dynamic_cast< SdrEdgeObj* >( xSdrObj.get() ) ) + xDrawObj->SetAreaObj( false ); + + /* Check for valid size for all objects. Needed to ignore lots of invisible + phantom objects from deleted rows or columns (for performance reasons). + #i30816# Include objects embedded in groups. + #i58780# Ignore group shapes, size is not initialized. */ + bool bEmbeddedGroup = !bIsTopLevel && dynamic_cast< SdrObjGroup* >( xSdrObj.get() ); + if( !bEmbeddedGroup && !xDrawObj->IsValidSize( rAnchorRect ) ) + return nullptr; // simply return, xSdrObj will be destroyed + + // set shape information from DFF stream + OUString aObjName = GetPropertyString( DFF_Prop_wzName, rDffStrm ); + OUString aHyperlink = ReadHlinkProperty( rDffStrm ); + bool bVisible = !GetPropertyBool( DFF_Prop_fHidden ); + bool bAutoMargin = GetPropertyBool( DFF_Prop_AutoTextMargin ); + xDrawObj->SetDffData( rDffObjData, aObjName, aHyperlink, bVisible, bAutoMargin ); + + /* Connect textbox data (string, alignment, text orientation) to object. + don't ask for a text-ID, DFF export doesn't set one. */ + if( XclImpTextObj* pTextObj = dynamic_cast< XclImpTextObj* >( xDrawObj.get() ) ) + if( const XclImpObjTextData* pTextData = rConvData.mrDrawing.FindTextData( rDffObjData.rSpHd ) ) + pTextObj->SetTextData( *pTextData ); + + // copy line and fill formatting of TBX form controls from DFF properties + if( XclImpTbxObjBase* pTbxObj = dynamic_cast< XclImpTbxObjBase* >( xDrawObj.get() ) ) + pTbxObj->SetDffProperties( *this ); + + // try to create a custom SdrObject that overwrites the passed object + SdrObjectUniquePtr xNewSdrObj( xDrawObj->CreateSdrObject( *this, rAnchorRect, true ) ); + if( xNewSdrObj ) + xSdrObj = std::move( xNewSdrObj ); + + // process the SdrObject + if( xSdrObj ) + { + // filled without color -> set system window color + if( GetPropertyBool( DFF_Prop_fFilled ) && !IsProperty( DFF_Prop_fillColor ) ) + xSdrObj->SetMergedItem( XFillColorItem( OUString(), GetPalette().GetColor( EXC_COLOR_WINDOWBACK ) ) ); + + // additional processing on the SdrObject + xDrawObj->PreProcessSdrObject( *this, *xSdrObj ); + + /* If the SdrObject will not be inserted into the draw page, delete it + here. Happens e.g. for notes: The PreProcessSdrObject() call above + has inserted the note into the document, and the SdrObject is not + needed anymore. */ + if( !xDrawObj->IsInsertSdrObj() ) + xSdrObj.reset(); + } + + if( xSdrObj ) + { + /* Store the relation between shape ID and SdrObject for connectors. + Must be done here (and not in InsertSdrObject() function), + otherwise all SdrObjects embedded in groups would be lost. */ + rConvData.maSolverCont.InsertSdrObjectInfo( *xSdrObj, xDrawObj->GetDffShapeId(), xDrawObj->GetDffFlags() ); + + /* If the drawing object is embedded in a group object, call + PostProcessSdrObject() here. For top-level objects this will be + done automatically in InsertSdrObject() but grouped shapes are + inserted into their groups somewhere in the SvxMSDffManager base + class without chance of notification. Unfortunately, now this is + called before the object is really inserted into its group object, + but that should not have any effect for grouped objects. */ + if( !bIsTopLevel ) + xDrawObj->PostProcessSdrObject( *this, *xSdrObj ); + } + + return xSdrObj.release(); +} + +SdrObject* XclImpDffConverter::FinalizeObj(DffObjData& rDffObjData, SdrObject* pOldSdrObj ) +{ + XclImpDffConvData& rConvData = GetConvData(); + + /* pOldSdrObj passes a generated SdrObject. This function owns this object + and can modify it. The function has either to return it back to caller + or to delete it by itself. */ + SdrObjectUniquePtr xSdrObj( pOldSdrObj ); + + // find the OBJ record data related to the processed shape + XclImpDrawObjRef xDrawObj = rConvData.mrDrawing.FindDrawObj( rDffObjData.rSpHd ); + + if( xSdrObj && xDrawObj ) + { + // cell anchoring + if ( !rDffObjData.bPageAnchor ) + ScDrawLayer::SetCellAnchoredFromPosition( *xSdrObj, GetDoc(), xDrawObj->GetTab(), false ); + } + + return xSdrObj.release(); +} + +bool XclImpDffConverter::InsertControl( const Reference< XFormComponent >& rxFormComp, + const css::awt::Size& /*rSize*/, Reference< XShape >* pxShape, + bool /*bFloatingCtrl*/ ) +{ + if( GetDocShell() ) try + { + XclImpDffConvData& rConvData = GetConvData(); + Reference< XIndexContainer > xFormIC( rConvData.mxCtrlForm, UNO_QUERY_THROW ); + Reference< XControlModel > xCtrlModel( rxFormComp, UNO_QUERY_THROW ); + + // create the control shape + Reference< XShape > xShape( ScfApiHelper::CreateInstance( GetDocShell(), "com.sun.star.drawing.ControlShape" ), UNO_QUERY_THROW ); + Reference< XControlShape > xCtrlShape( xShape, UNO_QUERY_THROW ); + + // insert the new control into the form + sal_Int32 nNewIndex = xFormIC->getCount(); + xFormIC->insertByIndex( nNewIndex, Any( rxFormComp ) ); + // on success: store new index of the control for later use (macro events) + rConvData.mnLastCtrlIndex = nNewIndex; + + // set control model at control shape and pass back shape to caller + xCtrlShape->setControl( xCtrlModel ); + if( pxShape ) *pxShape = xShape; + return true; + } + catch( const Exception& ) + { + OSL_FAIL( "XclImpDffConverter::InsertControl - cannot create form control" ); + } + + return false; +} + +// private -------------------------------------------------------------------- + +XclImpDffConverter::XclImpDffConvData& XclImpDffConverter::GetConvData() +{ + OSL_ENSURE( !maDataStack.empty(), "XclImpDffConverter::GetConvData - no drawing manager on stack" ); + return *maDataStack.back(); +} + +const XclImpDffConverter::XclImpDffConvData& XclImpDffConverter::GetConvData() const +{ + OSL_ENSURE( !maDataStack.empty(), "XclImpDffConverter::GetConvData - no drawing manager on stack" ); + return *maDataStack.back(); +} + +OUString XclImpDffConverter::ReadHlinkProperty( SvStream& rDffStrm ) const +{ + /* Reads hyperlink data from a complex DFF property. Contents of this + property are equal to the HLINK record, import of this record is + implemented in class XclImpHyperlink. This function has to create an + instance of the XclImpStream class to be able to reuse the + functionality of XclImpHyperlink. */ + OUString aString; + sal_uInt32 nBufferSize = GetPropertyValue( DFF_Prop_pihlShape, 0 ); + if( (0 < nBufferSize) && (nBufferSize <= 0xFFFF) && SeekToContent( DFF_Prop_pihlShape, rDffStrm ) ) + { + // create a faked BIFF record that can be read by XclImpStream class + SvMemoryStream aMemStream; + aMemStream.WriteUInt16( 0 ).WriteUInt16( nBufferSize ); + + // copy from DFF stream to memory stream + ::std::vector< sal_uInt8 > aBuffer( nBufferSize ); + sal_uInt8* pnData = aBuffer.data(); + if (rDffStrm.ReadBytes(pnData, nBufferSize) == nBufferSize) + { + aMemStream.WriteBytes(pnData, nBufferSize); + + // create BIFF import stream to be able to use XclImpHyperlink class + XclImpStream aXclStrm( aMemStream, GetRoot() ); + if( aXclStrm.StartNextRecord() ) + aString = XclImpHyperlink::ReadEmbeddedData( aXclStrm ); + } + } + return aString; +} + +bool XclImpDffConverter::ProcessDgContainer( SvStream& rDffStrm, const DffRecordHeader& rDgHeader ) +{ + std::size_t nEndPos = rDgHeader.GetRecEndFilePos(); + bool isBreak(false); + while (!isBreak && rDffStrm.good() && rDffStrm.Tell() < nEndPos) + { + DffRecordHeader aHeader; + ReadDffRecordHeader( rDffStrm, aHeader ); + switch( aHeader.nRecType ) + { + case DFF_msofbtSolverContainer: + isBreak = !ProcessSolverContainer( rDffStrm, aHeader ); + break; + case DFF_msofbtSpgrContainer: + isBreak = !ProcessShGrContainer( rDffStrm, aHeader ); + break; + default: + isBreak = !aHeader.SeekToEndOfRecord( rDffStrm ); + } + } + // seek to end of drawing page container + isBreak = !rDgHeader.SeekToEndOfRecord( rDffStrm ); + + // #i12638# #i37900# connector rules + XclImpSolverContainer& rSolverCont = GetConvData().maSolverCont; + rSolverCont.UpdateConnectorRules(); + SolveSolver( rSolverCont ); + rSolverCont.RemoveConnectorRules(); + return !isBreak; +} + +bool XclImpDffConverter::ProcessShGrContainer( SvStream& rDffStrm, const DffRecordHeader& rShGrHeader ) +{ + std::size_t nEndPos = rShGrHeader.GetRecEndFilePos(); + bool isBreak(false); + while (!isBreak && rDffStrm.good() && rDffStrm.Tell() < nEndPos) + { + DffRecordHeader aHeader; + ReadDffRecordHeader( rDffStrm, aHeader ); + switch( aHeader.nRecType ) + { + case DFF_msofbtSpgrContainer: + case DFF_msofbtSpContainer: + isBreak = !ProcessShContainer( rDffStrm, aHeader ); + break; + default: + isBreak = !aHeader.SeekToEndOfRecord( rDffStrm ); + } + } + // seek to end of shape group container + return rShGrHeader.SeekToEndOfRecord( rDffStrm ) && !isBreak; +} + +bool XclImpDffConverter::ProcessSolverContainer( SvStream& rDffStrm, const DffRecordHeader& rSolverHeader ) +{ + // solver container wants to read the solver container header again + rSolverHeader.SeekToBegOfRecord( rDffStrm ); + // read the entire solver container + ReadSvxMSDffSolverContainer( rDffStrm, GetConvData().maSolverCont ); + // seek to end of solver container + return rSolverHeader.SeekToEndOfRecord( rDffStrm ); +} + +bool XclImpDffConverter::ProcessShContainer( SvStream& rDffStrm, const DffRecordHeader& rShHeader ) +{ + rShHeader.SeekToBegOfRecord( rDffStrm ); + tools::Rectangle aDummy; + XclImpDrawObjClientData aDrawObjClientData; + /* The call to ImportObj() creates and returns a new SdrObject for the + processed shape. We take ownership of the returned object here. If the + shape is a group object, all embedded objects are created recursively, + and the returned group object contains them all. ImportObj() calls the + virtual functions ProcessClientAnchor2() and ProcessObj() and writes + the pointer to the related draw object data (OBJ record) into aDrawObjClientData. */ + SdrObjectUniquePtr xSdrObj( ImportObj( rDffStrm, aDrawObjClientData, aDummy, aDummy, /*nCalledByGroup*/0, /*pShapeId*/nullptr ) ); + if (aDrawObjClientData.m_pTopLevelObj && xSdrObj ) + InsertSdrObject( GetConvData().mrSdrPage, *aDrawObjClientData.m_pTopLevelObj, xSdrObj.release() ); + return rShHeader.SeekToEndOfRecord( rDffStrm ); +} + +void XclImpDffConverter::InsertSdrObject( SdrObjList& rObjList, const XclImpDrawObjBase& rDrawObj, SdrObject* pSdrObj ) +{ + XclImpDffConvData& rConvData = GetConvData(); + /* Take ownership of the passed object. If insertion fails (e.g. rDrawObj + states to skip insertion), the object is automatically deleted. */ + SdrObjectUniquePtr xSdrObj( pSdrObj ); + if( xSdrObj && rDrawObj.IsInsertSdrObj() ) + { + rObjList.NbcInsertObject( xSdrObj.release() ); + // callback to drawing manager for e.g. tracking of used sheet area + rConvData.mrDrawing.OnObjectInserted( rDrawObj ); + // callback to drawing object for post processing (use pSdrObj, xSdrObj already released) + rDrawObj.PostProcessSdrObject( *this, *pSdrObj ); + } + /* SdrObject still here? Insertion failed, remove data from shape ID map. + The SdrObject will be destructed then. */ + if( xSdrObj ) + rConvData.maSolverCont.RemoveSdrObjectInfo( *xSdrObj ); +} + +void XclImpDffConverter::InitControlForm() +{ + XclImpDffConvData& rConvData = GetConvData(); + if( rConvData.mbHasCtrlForm ) + return; + + rConvData.mbHasCtrlForm = true; + if( !SupportsOleObjects() ) + return; + + try + { + Reference< XFormsSupplier > xFormsSupplier( rConvData.mrSdrPage.getUnoPage(), UNO_QUERY_THROW ); + Reference< XNameContainer > xFormsNC( xFormsSupplier->getForms(), UNO_SET_THROW ); + // find or create the Standard form used to insert the imported controls + if( xFormsNC->hasByName( gaStdFormName ) ) + { + xFormsNC->getByName( gaStdFormName ) >>= rConvData.mxCtrlForm; + } + else if( SfxObjectShell* pDocShell = GetDocShell() ) + { + rConvData.mxCtrlForm.set( ScfApiHelper::CreateInstance( pDocShell, "com.sun.star.form.component.Form" ), UNO_QUERY_THROW ); + xFormsNC->insertByName( gaStdFormName, Any( rConvData.mxCtrlForm ) ); + } + } + catch( const Exception& ) + { + } +} + +// Drawing manager ============================================================ + +XclImpDrawing::XclImpDrawing( const XclImpRoot& rRoot, bool bOleObjects ) : + XclImpRoot( rRoot ), + mbOleObjs( bOleObjects ) +{ +} + +XclImpDrawing::~XclImpDrawing() +{ +} + +Graphic XclImpDrawing::ReadImgData( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + Graphic aGraphic; + sal_uInt16 nFormat = rStrm.ReaduInt16(); + rStrm.Ignore( 2 );//nEnv + sal_uInt32 nDataSize = rStrm.ReaduInt32(); + if( nDataSize <= rStrm.GetRecLeft() ) + { + switch( nFormat ) + { + case EXC_IMGDATA_WMF: ReadWmf( aGraphic, rStrm ); break; + case EXC_IMGDATA_BMP: ReadBmp( aGraphic, rRoot, rStrm ); break; + default: OSL_FAIL( "XclImpDrawing::ReadImgData - unknown image format" ); + } + } + return aGraphic; +} + +void XclImpDrawing::ReadObj( XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + /* #i61786# In BIFF8 streams, OBJ records may occur without MSODRAWING + records. In this case, the OBJ records are in BIFF5 format. Do a sanity + check here that there is no DFF data loaded before. */ + OSL_ENSURE( maDffStrm.Tell() == 0, "XclImpDrawing::ReadObj - unexpected DFF stream data, OBJ will be ignored" ); + if( maDffStrm.Tell() == 0 ) switch( GetBiff() ) + { + case EXC_BIFF3: + xDrawObj = XclImpDrawObjBase::ReadObj3( GetRoot(), rStrm ); + break; + case EXC_BIFF4: + xDrawObj = XclImpDrawObjBase::ReadObj4( GetRoot(), rStrm ); + break; + case EXC_BIFF5: + case EXC_BIFF8: + xDrawObj = XclImpDrawObjBase::ReadObj5( GetRoot(), rStrm ); + break; + default: + DBG_ERROR_BIFF(); + } + + if( xDrawObj ) + { + // insert into maRawObjs or into the last open group object + maRawObjs.InsertGrouped( xDrawObj ); + // to be able to find objects by ID + maObjMapId[ xDrawObj->GetObjId() ] = xDrawObj; + } +} + +void XclImpDrawing::ReadMsoDrawing( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); + // disable internal CONTINUE handling + rStrm.ResetRecord( false ); + // read leading MSODRAWING record + ReadDffRecord( rStrm ); + + // read following drawing records, but do not start following unrelated record + bool bLoop = true; + while( bLoop ) switch( rStrm.GetNextRecId() ) + { + case EXC_ID_MSODRAWING: + case EXC_ID_MSODRAWINGSEL: + case EXC_ID_CONT: + rStrm.StartNextRecord(); + ReadDffRecord( rStrm ); + break; + case EXC_ID_OBJ: + rStrm.StartNextRecord(); + ReadObj8( rStrm ); + break; + case EXC_ID_TXO: + rStrm.StartNextRecord(); + ReadTxo( rStrm ); + break; + default: + bLoop = false; + } + + // re-enable internal CONTINUE handling + rStrm.ResetRecord( true ); +} + +XclImpDrawObjRef XclImpDrawing::FindDrawObj( const DffRecordHeader& rHeader ) const +{ + /* maObjMap stores objects by position of the client data (OBJ record) in + the DFF stream, which is always behind shape start position of the + passed header. The function upper_bound() finds the first element in + the map whose key is greater than the start position of the header. Its + end position is used to test whether the found object is really related + to the shape. */ + XclImpDrawObjRef xDrawObj; + XclImpObjMap::const_iterator aIt = maObjMap.upper_bound( rHeader.GetRecBegFilePos() ); + if( (aIt != maObjMap.end()) && (aIt->first <= rHeader.GetRecEndFilePos()) ) + xDrawObj = aIt->second; + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawing::FindDrawObj( sal_uInt16 nObjId ) const +{ + XclImpDrawObjRef xDrawObj; + XclImpObjMapById::const_iterator aIt = maObjMapId.find( nObjId ); + if( aIt != maObjMapId.end() ) + xDrawObj = aIt->second; + return xDrawObj; +} + +const XclImpObjTextData* XclImpDrawing::FindTextData( const DffRecordHeader& rHeader ) const +{ + /* maTextMap stores textbox data by position of the client data (TXO + record) in the DFF stream, which is always behind shape start position + of the passed header. The function upper_bound() finds the first + element in the map whose key is greater than the start position of the + header. Its end position is used to test whether the found object is + really related to the shape. */ + XclImpObjTextMap::const_iterator aIt = maTextMap.upper_bound( rHeader.GetRecBegFilePos() ); + if( (aIt != maTextMap.end()) && (aIt->first <= rHeader.GetRecEndFilePos()) ) + return aIt->second.get(); + return nullptr; +} + +void XclImpDrawing::SetSkipObj( sal_uInt16 nObjId ) +{ + maSkipObjs.push_back( nObjId ); +} + +std::size_t XclImpDrawing::GetProgressSize() const +{ + return std::accumulate(maObjMap.begin(), maObjMap.end(), maRawObjs.GetProgressSize(), + [](const std::size_t& rSum, const XclImpObjMap::value_type& rEntry) { return rSum + rEntry.second->GetProgressSize(); }); +} + +void XclImpDrawing::ImplConvertObjects( XclImpDffConverter& rDffConv, SdrModel& rSdrModel, SdrPage& rSdrPage ) +{ + //rhbz#636521, disable undo during conversion. faster, smaller and stops + //temp objects being inserted into the undo list + bool bOrigUndoStatus = rSdrModel.IsUndoEnabled(); + rSdrModel.EnableUndo(false); + // register this drawing manager at the passed (global) DFF manager + rDffConv.InitializeDrawing( *this, rSdrModel, rSdrPage ); + // process list of objects to be skipped + for( const auto& rSkipObj : maSkipObjs ) + if( XclImpDrawObjBase* pDrawObj = FindDrawObj( rSkipObj ).get() ) + pDrawObj->SetProcessSdrObj( false ); + // process drawing objects without DFF data + rDffConv.ProcessDrawing( maRawObjs ); + // process all objects in the DFF stream + rDffConv.ProcessDrawing( maDffStrm ); + // unregister this drawing manager at the passed (global) DFF manager + rDffConv.FinalizeDrawing(); + rSdrModel.EnableUndo(bOrigUndoStatus); +} + +// protected ------------------------------------------------------------------ + +void XclImpDrawing::AppendRawObject( const XclImpDrawObjRef& rxDrawObj ) +{ + OSL_ENSURE( rxDrawObj, "XclImpDrawing::AppendRawObject - unexpected empty reference" ); + maRawObjs.push_back( rxDrawObj ); +} + +// private -------------------------------------------------------------------- + +void XclImpDrawing::ReadWmf( Graphic& rGraphic, XclImpStream& rStrm ) // static helper +{ + // extract graphic data from IMGDATA and following CONTINUE records + rStrm.Ignore( 8 ); + SvMemoryStream aMemStrm; + rStrm.CopyToStream( aMemStrm, rStrm.GetRecLeft() ); + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + // import the graphic from memory stream + GDIMetaFile aGDIMetaFile; + if( ::ReadWindowMetafile( aMemStrm, aGDIMetaFile ) ) + rGraphic = aGDIMetaFile; +} + +void XclImpDrawing::ReadBmp( Graphic& rGraphic, const XclImpRoot& rRoot, XclImpStream& rStrm ) // static helper +{ + // extract graphic data from IMGDATA and following CONTINUE records + SvMemoryStream aMemStrm; + + /* Excel 3 and 4 seem to write broken BMP data. Usually they write a + DIBCOREHEADER (12 bytes) containing width, height, planes = 1, and + pixel depth = 32 bit. After that, 3 unused bytes are added before the + actual pixel data. This does even confuse Excel 5 and later, which + cannot read the image data correctly. */ + if( rRoot.GetBiff() <= EXC_BIFF4 ) + { + rStrm.PushPosition(); + sal_uInt32 nHdrSize; + sal_uInt16 nWidth, nHeight, nPlanes, nDepth; + nHdrSize = rStrm.ReaduInt32(); + nWidth = rStrm.ReaduInt16(); + nHeight = rStrm.ReaduInt16(); + nPlanes = rStrm.ReaduInt16(); + nDepth = rStrm.ReaduInt16(); + if( (nHdrSize == 12) && (nPlanes == 1) && (nDepth == 32) ) + { + rStrm.Ignore( 3 ); + aMemStrm.SetEndian( SvStreamEndian::LITTLE ); + aMemStrm.WriteUInt32( nHdrSize ).WriteUInt16( nWidth ).WriteUInt16( nHeight ).WriteUInt16( nPlanes ).WriteUInt16( nDepth ); + rStrm.CopyToStream( aMemStrm, rStrm.GetRecLeft() ); + } + rStrm.PopPosition(); + } + + // no special handling above -> just copy the remaining record data + if( aMemStrm.Tell() == 0 ) + rStrm.CopyToStream( aMemStrm, rStrm.GetRecLeft() ); + + // import the graphic from memory stream + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + Bitmap aBitmap; + if( ReadDIB(aBitmap, aMemStrm, false) ) // read DIB without file header + rGraphic = BitmapEx(aBitmap); +} + +void XclImpDrawing::ReadDffRecord( XclImpStream& rStrm ) +{ + maDffStrm.Seek( STREAM_SEEK_TO_END ); + rStrm.CopyRecordToStream( maDffStrm ); +} + +void XclImpDrawing::ReadObj8( XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj = XclImpDrawObjBase::ReadObj8( GetRoot(), rStrm ); + // store the new object in the internal containers + maObjMap[ maDffStrm.Tell() ] = xDrawObj; + maObjMapId[ xDrawObj->GetObjId() ] = xDrawObj; +} + +void XclImpDrawing::ReadTxo( XclImpStream& rStrm ) +{ + XclImpObjTextRef xTextData = std::make_shared<XclImpObjTextData>(); + maTextMap[ maDffStrm.Tell() ] = xTextData; + + // 1) read the TXO record + xTextData->maData.ReadTxo8( rStrm ); + + // 2) first CONTINUE with string + xTextData->mxString.reset(); + bool bValid = true; + if( xTextData->maData.mnTextLen > 0 ) + { + bValid = (rStrm.GetNextRecId() == EXC_ID_CONT) && rStrm.StartNextRecord(); + OSL_ENSURE( bValid, "XclImpDrawing::ReadTxo - missing CONTINUE record" ); + if( bValid ) + xTextData->mxString = std::make_shared<XclImpString>( rStrm.ReadUniString( xTextData->maData.mnTextLen ) ); + } + + // 3) second CONTINUE with formatting runs + if( xTextData->maData.mnFormatSize > 0 ) + { + bValid = (rStrm.GetNextRecId() == EXC_ID_CONT) && rStrm.StartNextRecord(); + OSL_ENSURE( bValid, "XclImpDrawing::ReadTxo - missing CONTINUE record" ); + if( bValid ) + xTextData->ReadFormats( rStrm ); + } +} + +XclImpSheetDrawing::XclImpSheetDrawing( const XclImpRoot& rRoot, SCTAB nScTab ) : + XclImpDrawing( rRoot, true ), + maScUsedArea( ScAddress::INITIALIZE_INVALID ) +{ + maScUsedArea.aStart.SetTab( nScTab ); + maScUsedArea.aEnd.SetTab( nScTab ); +} + +void XclImpSheetDrawing::ReadNote( XclImpStream& rStrm ) +{ + switch( GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + ReadNote3( rStrm ); + break; + case EXC_BIFF8: + ReadNote8( rStrm ); + break; + default: + DBG_ERROR_BIFF(); + } +} + +void XclImpSheetDrawing::ReadTabChart( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF5 ); + auto xChartObj = std::make_shared<XclImpChartObj>( GetRoot(), true ); + xChartObj->ReadChartSubStream( rStrm ); + // insert the chart as raw object without connected DFF data + AppendRawObject( xChartObj ); +} + +void XclImpSheetDrawing::ConvertObjects( XclImpDffConverter& rDffConv ) +{ + if( SdrModel* pSdrModel = GetDoc().GetDrawLayer() ) + if( SdrPage* pSdrPage = GetSdrPage( maScUsedArea.aStart.Tab() ) ) + ImplConvertObjects( rDffConv, *pSdrModel, *pSdrPage ); +} + +tools::Rectangle XclImpSheetDrawing::CalcAnchorRect( const XclObjAnchor& rAnchor, bool /*bDffAnchor*/ ) const +{ + return rAnchor.GetRect( GetRoot(), maScUsedArea.aStart.Tab(), MapUnit::Map100thMM ); +} + +void XclImpSheetDrawing::OnObjectInserted( const XclImpDrawObjBase& rDrawObj ) +{ + ScRange aScObjArea = rDrawObj.GetUsedArea( maScUsedArea.aStart.Tab() ); + if( aScObjArea.IsValid() ) + maScUsedArea.ExtendTo( aScObjArea ); +} + +// private -------------------------------------------------------------------- + +void XclImpSheetDrawing::ReadNote3( XclImpStream& rStrm ) +{ + XclAddress aXclPos; + rStrm >> aXclPos; + sal_uInt16 nTotalLen = rStrm.ReaduInt16(); + + ScAddress aScNotePos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScNotePos, aXclPos, maScUsedArea.aStart.Tab(), true ) ) + return; + + sal_uInt16 nPartLen = ::std::min( nTotalLen, static_cast< sal_uInt16 >( rStrm.GetRecLeft() ) ); + OUStringBuffer aNoteText(rStrm.ReadRawByteString( nPartLen )); + nTotalLen = nTotalLen - nPartLen; + while (true) + { + if (!nTotalLen) + break; + if (rStrm.GetNextRecId() != EXC_ID_NOTE) + break; + if (!rStrm.StartNextRecord()) + break; + rStrm >> aXclPos; + nPartLen = rStrm.ReaduInt16(); + OSL_ENSURE( aXclPos.mnRow == 0xFFFF, "XclImpObjectManager::ReadNote3 - missing continuation NOTE record" ); + if( aXclPos.mnRow == 0xFFFF ) + { + OSL_ENSURE( nPartLen <= nTotalLen, "XclImpObjectManager::ReadNote3 - string too long" ); + aNoteText.append(rStrm.ReadRawByteString( nPartLen )); + nTotalLen = nTotalLen - ::std::min( nTotalLen, nPartLen ); + } + else + { + // seems to be a new note, record already started -> load the note + rStrm.Seek( EXC_REC_SEEK_TO_BEGIN ); + ReadNote( rStrm ); + nTotalLen = 0; + } + } + ScNoteUtil::CreateNoteFromString( GetDoc(), aScNotePos, aNoteText.makeStringAndClear(), false, false ); +} + +void XclImpSheetDrawing::ReadNote8( XclImpStream& rStrm ) +{ + XclAddress aXclPos; + sal_uInt16 nFlags, nObjId; + rStrm >> aXclPos; + nFlags = rStrm.ReaduInt16(); + nObjId = rStrm.ReaduInt16(); + + ScAddress aScNotePos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScNotePos, aXclPos, maScUsedArea.aStart.Tab(), true ) ) + if( nObjId != EXC_OBJ_INVALID_ID ) + if( XclImpNoteObj* pNoteObj = dynamic_cast< XclImpNoteObj* >( FindDrawObj( nObjId ).get() ) ) + pNoteObj->SetNoteData( aScNotePos, nFlags ); +} + +// The object manager ========================================================= + +XclImpObjectManager::XclImpObjectManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ + maDefObjNames[ EXC_OBJTYPE_GROUP ] = "Group"; + maDefObjNames[ EXC_OBJTYPE_LINE ] = ScResId( STR_SHAPE_LINE ); + maDefObjNames[ EXC_OBJTYPE_RECTANGLE ] = ScResId( STR_SHAPE_RECTANGLE ); + maDefObjNames[ EXC_OBJTYPE_OVAL ] = ScResId( STR_SHAPE_OVAL ); + maDefObjNames[ EXC_OBJTYPE_ARC ] = "Arc"; + maDefObjNames[ EXC_OBJTYPE_CHART ] = "Chart"; + maDefObjNames[ EXC_OBJTYPE_TEXT ] = "Text"; + maDefObjNames[ EXC_OBJTYPE_BUTTON ] = ScResId( STR_FORM_BUTTON ); + maDefObjNames[ EXC_OBJTYPE_PICTURE ] = "Picture"; + maDefObjNames[ EXC_OBJTYPE_POLYGON ] = "Freeform"; + maDefObjNames[ EXC_OBJTYPE_CHECKBOX ] = ScResId( STR_FORM_CHECKBOX ); + maDefObjNames[ EXC_OBJTYPE_OPTIONBUTTON ] = ScResId( STR_FORM_OPTIONBUTTON ); + maDefObjNames[ EXC_OBJTYPE_EDIT ] = "Edit Box"; + maDefObjNames[ EXC_OBJTYPE_LABEL ] = ScResId( STR_FORM_LABEL ); + maDefObjNames[ EXC_OBJTYPE_DIALOG ] = "Dialog Frame"; + maDefObjNames[ EXC_OBJTYPE_SPIN ] = ScResId( STR_FORM_SPINNER ); + maDefObjNames[ EXC_OBJTYPE_SCROLLBAR ] = ScResId( STR_FORM_SCROLLBAR ); + maDefObjNames[ EXC_OBJTYPE_LISTBOX ] = ScResId( STR_FORM_LISTBOX ); + maDefObjNames[ EXC_OBJTYPE_GROUPBOX ] = ScResId( STR_FORM_GROUPBOX ); + maDefObjNames[ EXC_OBJTYPE_DROPDOWN ] = ScResId( STR_FORM_DROPDOWN ); + maDefObjNames[ EXC_OBJTYPE_NOTE ] = "Comment"; + maDefObjNames[ EXC_OBJTYPE_DRAWING ] = ScResId( STR_SHAPE_AUTOSHAPE ); +} + +XclImpObjectManager::~XclImpObjectManager() +{ +} + +void XclImpObjectManager::ReadMsoDrawingGroup( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); + // Excel continues this record with MSODRAWINGGROUP and CONTINUE records, hmm. + rStrm.ResetRecord( true, EXC_ID_MSODRAWINGGROUP ); + maDggStrm.Seek( STREAM_SEEK_TO_END ); + rStrm.CopyRecordToStream( maDggStrm ); +} + +XclImpSheetDrawing& XclImpObjectManager::GetSheetDrawing( SCTAB nScTab ) +{ + XclImpSheetDrawingRef& rxDrawing = maSheetDrawings[ nScTab ]; + if( !rxDrawing ) + rxDrawing = std::make_shared<XclImpSheetDrawing>( GetRoot(), nScTab ); + return *rxDrawing; +} + +void XclImpObjectManager::ConvertObjects() +{ + // do nothing if the document does not contain a drawing layer + if( !GetDoc().GetDrawLayer() ) + return; + + // get total progress bar size for all sheet drawing managers + std::size_t nProgressSize = std::accumulate(maSheetDrawings.begin(), maSheetDrawings.end(), std::size_t(0), + [](const std::size_t& rSum, const XclImpSheetDrawingMap::value_type& rEntry) { return rSum + rEntry.second->GetProgressSize(); }); + // nothing to do if progress bar is zero (no objects present) + if( nProgressSize == 0 ) + return; + + XclImpDffConverter aDffConv( GetRoot(), maDggStrm ); + aDffConv.StartProgressBar( nProgressSize ); + for( auto& rEntry : maSheetDrawings ) + rEntry.second->ConvertObjects( aDffConv ); + + // #i112436# don't call ScChartListenerCollection::SetDirty here, + // instead use InterpretDirtyCells in ScDocument::CalcAfterLoad. +} + +OUString XclImpObjectManager::GetDefaultObjName( const XclImpDrawObjBase& rDrawObj ) const +{ + OUStringBuffer aDefName; + DefObjNameMap::const_iterator aIt = maDefObjNames.find( rDrawObj.GetObjType() ); + if( aIt != maDefObjNames.end() ) + aDefName.append(aIt->second); + return aDefName.append(' ').append(static_cast<sal_Int32>(rDrawObj.GetObjId())).makeStringAndClear(); +} + +ScRange XclImpObjectManager::GetUsedArea( SCTAB nScTab ) const +{ + XclImpSheetDrawingMap::const_iterator aIt = maSheetDrawings.find( nScTab ); + if( aIt != maSheetDrawings.end() ) + return aIt->second->GetUsedArea(); + return ScRange( ScAddress::INITIALIZE_INVALID ); +} + +// DFF property set helper ==================================================== + +XclImpDffPropSet::XclImpDffPropSet( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maDffConv( rRoot, maDummyStrm ) +{ +} + +void XclImpDffPropSet::Read( XclImpStream& rStrm ) +{ + sal_uInt32 nPropSetSize; + + rStrm.PushPosition(); + rStrm.Ignore( 4 ); + nPropSetSize = rStrm.ReaduInt32(); + rStrm.PopPosition(); + + mxMemStrm.reset( new SvMemoryStream ); + rStrm.CopyToStream( *mxMemStrm, 8 + nPropSetSize ); + mxMemStrm->Seek( STREAM_SEEK_TO_BEGIN ); + maDffConv.ReadPropSet( *mxMemStrm, nullptr ); +} + +sal_uInt32 XclImpDffPropSet::GetPropertyValue( sal_uInt16 nPropId ) const +{ + return maDffConv.GetPropertyValue( nPropId, 0 ); +} + +void XclImpDffPropSet::FillToItemSet( SfxItemSet& rItemSet ) const +{ + if( mxMemStrm ) + maDffConv.ApplyAttributes( *mxMemStrm, rItemSet ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclImpDffPropSet& rPropSet ) +{ + rPropSet.Read( rStrm ); + return rStrm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiformula.cxx b/sc/source/filter/excel/xiformula.cxx new file mode 100644 index 000000000..a5f4d7864 --- /dev/null +++ b/sc/source/filter/excel/xiformula.cxx @@ -0,0 +1,107 @@ +/* -*- 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 <xiformula.hxx> +#include <rangelst.hxx> +#include <xistream.hxx> + +#include <excform.hxx> + +// Formula compiler =========================================================== + +/** Implementation class of the export formula compiler. */ +class XclImpFmlaCompImpl : protected XclImpRoot, protected XclTokenArrayHelper +{ +public: + explicit XclImpFmlaCompImpl( const XclImpRoot& rRoot ); + + /** Creates a range list from the passed Excel token array. */ + void CreateRangeList( + ScRangeList& rScRanges, XclFormulaType eType, + const XclTokenArray& rXclTokArr, XclImpStream& rStrm ); + + std::unique_ptr<ScTokenArray> CreateFormula( XclFormulaType eType, const XclTokenArray& rXclTokArr ); + +}; + +XclImpFmlaCompImpl::XclImpFmlaCompImpl( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpFmlaCompImpl::CreateRangeList( + ScRangeList& rScRanges, XclFormulaType /*eType*/, + const XclTokenArray& rXclTokArr, XclImpStream& /*rStrm*/ ) +{ + rScRanges.RemoveAll(); + + //FIXME: evil hack, using old formula import :-) + if( !rXclTokArr.Empty() ) + { + SvMemoryStream aMemStrm; + aMemStrm.WriteUInt16( EXC_ID_EOF ).WriteUInt16( rXclTokArr.GetSize() ); + aMemStrm.WriteBytes(rXclTokArr.GetData(), rXclTokArr.GetSize()); + XclImpStream aFmlaStrm( aMemStrm, GetRoot() ); + aFmlaStrm.StartNextRecord(); + GetOldFmlaConverter().GetAbsRefs( rScRanges, aFmlaStrm, aFmlaStrm.GetRecSize() ); + } +} + +std::unique_ptr<ScTokenArray> XclImpFmlaCompImpl::CreateFormula( + XclFormulaType /*eType*/, const XclTokenArray& rXclTokArr ) +{ + if (rXclTokArr.Empty()) + return nullptr; + + // evil hack! are we trying to phase out the old style formula converter ? + SvMemoryStream aMemStrm; + aMemStrm.WriteUInt16( EXC_ID_EOF ).WriteUInt16( rXclTokArr.GetSize() ); + aMemStrm.WriteBytes(rXclTokArr.GetData(), rXclTokArr.GetSize()); + XclImpStream aFmlaStrm( aMemStrm, GetRoot() ); + aFmlaStrm.StartNextRecord(); + std::unique_ptr<ScTokenArray> pArray; + GetOldFmlaConverter().Reset(); + GetOldFmlaConverter().Convert(pArray, aFmlaStrm, aFmlaStrm.GetRecSize(), true); + return pArray; +} + +XclImpFormulaCompiler::XclImpFormulaCompiler( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mxImpl( std::make_shared<XclImpFmlaCompImpl>( rRoot ) ) +{ +} + +XclImpFormulaCompiler::~XclImpFormulaCompiler() +{ +} + +void XclImpFormulaCompiler::CreateRangeList( + ScRangeList& rScRanges, XclFormulaType eType, + const XclTokenArray& rXclTokArr, XclImpStream& rStrm ) +{ + mxImpl->CreateRangeList( rScRanges, eType, rXclTokArr, rStrm ); +} + +std::unique_ptr<ScTokenArray> XclImpFormulaCompiler::CreateFormula( + XclFormulaType eType, const XclTokenArray& rXclTokArr ) +{ + return mxImpl->CreateFormula(eType, rXclTokArr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xihelper.cxx b/sc/source/filter/excel/xihelper.cxx new file mode 100644 index 000000000..ef38c5b65 --- /dev/null +++ b/sc/source/filter/excel/xihelper.cxx @@ -0,0 +1,896 @@ +/* -*- 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 <memory> +#include <xihelper.hxx> +#include <svl/itemset.hxx> +#include <svl/sharedstringpool.hxx> +#include <editeng/editobj.hxx> +#include <tools/urlobj.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/flditem.hxx> +#include <document.hxx> +#include <rangelst.hxx> +#include <editutil.hxx> +#include <attrib.hxx> +#include <xltracer.hxx> +#include <xistream.hxx> +#include <xistring.hxx> +#include <xistyle.hxx> +#include <excform.hxx> +#include <scmatrix.hxx> +#include <documentimport.hxx> +#include <sal/log.hxx> + +// Excel->Calc cell address/range conversion ================================== + +namespace { + +/** Fills the passed Calc address with the passed Excel cell coordinates without checking any limits. */ +void lclFillAddress( ScAddress& rScPos, sal_uInt16 nXclCol, sal_uInt32 nXclRow, SCTAB nScTab ) +{ + rScPos.SetCol( static_cast< SCCOL >( nXclCol ) ); + rScPos.SetRow( static_cast< SCROW >( nXclRow ) ); + rScPos.SetTab( nScTab ); +} + +} // namespace + +XclImpAddressConverter::XclImpAddressConverter( const XclImpRoot& rRoot ) : + XclAddressConverterBase( rRoot.GetTracer(), rRoot.GetScMaxPos() ) +{ +} + +// cell address --------------------------------------------------------------- + +bool XclImpAddressConverter::CheckAddress( const XclAddress& rXclPos, bool bWarn ) +{ + bool bValidCol = rXclPos.mnCol <= mnMaxCol; + bool bValidRow = rXclPos.mnRow <= mnMaxRow; + bool bValid = bValidCol && bValidRow; + if( !bValid && bWarn ) + { + mbColTrunc |= !bValidCol; + mbRowTrunc |= !bValidRow; + mrTracer.TraceInvalidAddress( ScAddress( + static_cast< SCCOL >( rXclPos.mnCol ), static_cast< SCROW >( rXclPos.mnRow ), 0 ), maMaxPos ); + } + return bValid; +} + +bool XclImpAddressConverter::ConvertAddress( ScAddress& rScPos, + const XclAddress& rXclPos, SCTAB nScTab, bool bWarn ) +{ + bool bValid = CheckAddress( rXclPos, bWarn ); + if( bValid ) + lclFillAddress( rScPos, rXclPos.mnCol, rXclPos.mnRow, nScTab ); + return bValid; +} + +ScAddress XclImpAddressConverter::CreateValidAddress( + const XclAddress& rXclPos, SCTAB nScTab, bool bWarn ) +{ + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !ConvertAddress( aScPos, rXclPos, nScTab, bWarn ) ) + { + aScPos.SetCol( static_cast< SCCOL >( ::std::min( rXclPos.mnCol, mnMaxCol ) ) ); + aScPos.SetRow( static_cast< SCROW >( ::std::min( rXclPos.mnRow, mnMaxRow ) ) ); + aScPos.SetTab( limit_cast< SCTAB >( nScTab, 0, maMaxPos.Tab() ) ); + } + return aScPos; +} + +// cell range ----------------------------------------------------------------- + +bool XclImpAddressConverter::ConvertRange( ScRange& rScRange, + const XclRange& rXclRange, SCTAB nScTab1, SCTAB nScTab2, bool bWarn ) +{ + // check start position + bool bValidStart = CheckAddress( rXclRange.maFirst, bWarn ); + if( bValidStart ) + { + lclFillAddress( rScRange.aStart, rXclRange.maFirst.mnCol, rXclRange.maFirst.mnRow, nScTab1 ); + + // check & correct end position + sal_uInt16 nXclCol2 = rXclRange.maLast.mnCol; + sal_uInt32 nXclRow2 = rXclRange.maLast.mnRow; + if( !CheckAddress( rXclRange.maLast, bWarn ) ) + { + nXclCol2 = ::std::min( nXclCol2, mnMaxCol ); + nXclRow2 = ::std::min( nXclRow2, mnMaxRow ); + } + lclFillAddress( rScRange.aEnd, nXclCol2, nXclRow2, nScTab2 ); + } + return bValidStart; +} + +// cell range list ------------------------------------------------------------ + +void XclImpAddressConverter::ConvertRangeList( ScRangeList& rScRanges, + const XclRangeList& rXclRanges, SCTAB nScTab, bool bWarn ) +{ + rScRanges.RemoveAll(); + for( const auto& rXclRange : rXclRanges ) + { + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( ConvertRange( aScRange, rXclRange, nScTab, nScTab, bWarn ) ) + rScRanges.push_back( aScRange ); + } +} + +// String->EditEngine conversion ============================================== + +namespace { + +std::unique_ptr<EditTextObject> lclCreateTextObject( const XclImpRoot& rRoot, + const XclImpString& rString, XclFontItemType eType, sal_uInt16 nXFIndex ) +{ + std::unique_ptr<EditTextObject> pTextObj; + + const XclImpXFBuffer& rXFBuffer = rRoot.GetXFBuffer(); + const XclImpFont* pFirstFont = rXFBuffer.GetFont( nXFIndex ); + bool bFirstEscaped = pFirstFont && pFirstFont->HasEscapement(); + + if( rString.IsRich() || bFirstEscaped ) + { + const XclImpFontBuffer& rFontBuffer = rRoot.GetFontBuffer(); + const XclFormatRunVec& rFormats = rString.GetFormats(); + + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + rEE.SetTextCurrentDefaults( rString.GetText() ); + + SfxItemSet aItemSet( rEE.GetEmptyItemSet() ); + if( bFirstEscaped ) + rFontBuffer.FillToItemSet( aItemSet, eType, rXFBuffer.GetFontIndex( nXFIndex ) ); + ESelection aSelection; + + XclFormatRun aNextRun; + XclFormatRunVec::const_iterator aIt = rFormats.begin(); + XclFormatRunVec::const_iterator aEnd = rFormats.end(); + + if( aIt != aEnd ) + aNextRun = *aIt++; + else + aNextRun.mnChar = 0xFFFF; + + sal_Int32 nLen = rString.GetText().getLength(); + for( sal_Int32 nChar = 0; nChar < nLen; ++nChar ) + { + // reached new different formatted text portion + if( nChar >= aNextRun.mnChar ) + { + // send items to edit engine + rEE.QuickSetAttribs( aItemSet, aSelection ); + + // start new item set + aItemSet.ClearItem(); + rFontBuffer.FillToItemSet( aItemSet, eType, aNextRun.mnFontIdx ); + + // read new formatting information + if( aIt != aEnd ) + aNextRun = *aIt++; + else + aNextRun.mnChar = 0xFFFF; + + // reset selection start to current position + aSelection.nStartPara = aSelection.nEndPara; + aSelection.nStartPos = aSelection.nEndPos; + } + + // set end of selection to current position + if( rString.GetText()[ nChar ] == '\n' ) + { + ++aSelection.nEndPara; + aSelection.nEndPos = 0; + } + else + ++aSelection.nEndPos; + } + + // send items of last text portion to edit engine + rEE.QuickSetAttribs( aItemSet, aSelection ); + + pTextObj = rEE.CreateTextObject(); + } + + return pTextObj; +} + +} // namespace + +std::unique_ptr<EditTextObject> XclImpStringHelper::CreateTextObject( + const XclImpRoot& rRoot, const XclImpString& rString ) +{ + return lclCreateTextObject( rRoot, rString, XclFontItemType::Editeng, 0 ); +} + +void XclImpStringHelper::SetToDocument( + ScDocumentImport& rDoc, const ScAddress& rPos, const XclImpRoot& rRoot, + const XclImpString& rString, sal_uInt16 nXFIndex ) +{ + if (rString.GetText().isEmpty()) + return; + + ::std::unique_ptr< EditTextObject > pTextObj( lclCreateTextObject( rRoot, rString, XclFontItemType::Editeng, nXFIndex ) ); + + if (pTextObj) + { + rDoc.setEditCell(rPos, std::move(pTextObj)); + } + else + { + const OUString& aStr = rString.GetText(); + if (aStr.indexOf('\n') != -1 || aStr.indexOf('\r') != -1) + { + // Multiline content. + ScFieldEditEngine& rEngine = rDoc.getDoc().GetEditEngine(); + rEngine.SetTextCurrentDefaults(aStr); + rDoc.setEditCell(rPos, rEngine.CreateTextObject()); + } + else + { + // Normal text cell. + rDoc.setStringCell(rPos, aStr); + } + } +} + +// Header/footer conversion =================================================== + +XclImpHFConverter::XclImpHFPortionInfo::XclImpHFPortionInfo() : + mnHeight( 0 ), + mnMaxLineHt( 0 ) +{ + maSel.nStartPara = maSel.nEndPara = 0; + maSel.nStartPos = maSel.nEndPos = 0; +} + +XclImpHFConverter::XclImpHFConverter( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mrEE( rRoot.GetHFEditEngine() ), + mxFontData( new XclFontData ), + meCurrObj( EXC_HF_CENTER ) +{ +} + +XclImpHFConverter::~XclImpHFConverter() +{ +} + +void XclImpHFConverter::ParseString( const OUString& rHFString ) +{ + // edit engine objects + mrEE.SetText( OUString() ); + maInfos.clear(); + maInfos.resize( EXC_HF_PORTION_COUNT ); + meCurrObj = EXC_HF_CENTER; + + // parser temporaries + maCurrText.truncate(); + OUStringBuffer aReadFont; // current font name + OUStringBuffer aReadStyle; // current font style + sal_uInt16 nReadHeight = 0; // current font height + ResetFontData(); + + /** State of the parser. */ + enum XclHFParserState + { + xlPSText, /// Read text, search for functions. + xlPSFunc, /// Read function (token following a '&'). + xlPSFont, /// Read font name ('&' is followed by '"', reads until next '"' or ','). + xlPSFontStyle, /// Read font style name (font part after ',', reads until next '"'). + xlPSHeight /// Read font height ('&' is followed by num. digits, reads until non-digit). + } eState = xlPSText; + + const sal_Unicode* pChar = rHFString.getStr(); + const sal_Unicode* pNull = pChar + rHFString.getLength(); // pointer to terminating null char + while( *pChar ) + { + switch( eState ) + { + +// --- read text character --- + + case xlPSText: + { + switch( *pChar ) + { + case '&': // new command + InsertText(); + eState = xlPSFunc; + break; + case '\n': // line break + InsertText(); + InsertLineBreak(); + break; + default: + maCurrText.append(OUStringChar(*pChar)); + } + } + break; + +// --- read control sequence --- + + case xlPSFunc: + { + eState = xlPSText; + switch( *pChar ) + { + case '&': maCurrText.append("&"); break; // the '&' character + + case 'L': SetNewPortion( EXC_HF_LEFT ); break; // Left portion + case 'C': SetNewPortion( EXC_HF_CENTER ); break; // Center portion + case 'R': SetNewPortion( EXC_HF_RIGHT ); break; // Right portion + + case 'P': InsertField( SvxFieldItem( SvxPageField(), EE_FEATURE_FIELD ) ); break; // page + case 'N': InsertField( SvxFieldItem( SvxPagesField(), EE_FEATURE_FIELD ) ); break; // page count + case 'D': InsertField( SvxFieldItem( SvxDateField(), EE_FEATURE_FIELD ) ); break; // date + case 'T': InsertField( SvxFieldItem( SvxTimeField(), EE_FEATURE_FIELD ) ); break; // time + case 'A': InsertField( SvxFieldItem( SvxTableField(), EE_FEATURE_FIELD ) ); break; // table name + + case 'Z': // file path + InsertField( SvxFieldItem( SvxExtFileField(), EE_FEATURE_FIELD ) ); // convert to full name + if( (pNull - pChar >= 2) && (*(pChar + 1) == '&') && (*(pChar + 2) == 'F') ) + { + // &Z&F found - ignore the &F part + pChar += 2; + } + break; + case 'F': // file name + InsertField( SvxFieldItem( SvxExtFileField( OUString(), SvxFileType::Var, SvxFileFormat::NameAndExt ), EE_FEATURE_FIELD ) ); + break; + + case 'U': // underline + SetAttribs(); + mxFontData->mnUnderline = (mxFontData->mnUnderline == EXC_FONTUNDERL_SINGLE) ? + EXC_FONTUNDERL_NONE : EXC_FONTUNDERL_SINGLE; + break; + case 'E': // double underline + SetAttribs(); + mxFontData->mnUnderline = (mxFontData->mnUnderline == EXC_FONTUNDERL_DOUBLE) ? + EXC_FONTUNDERL_NONE : EXC_FONTUNDERL_DOUBLE; + break; + case 'S': // strikeout + SetAttribs(); + mxFontData->mbStrikeout = !mxFontData->mbStrikeout; + break; + case 'X': // superscript + SetAttribs(); + mxFontData->mnEscapem = (mxFontData->mnEscapem == EXC_FONTESC_SUPER) ? + EXC_FONTESC_NONE : EXC_FONTESC_SUPER; + break; + case 'Y': // subscript + SetAttribs(); + mxFontData->mnEscapem = (mxFontData->mnEscapem == EXC_FONTESC_SUB) ? + EXC_FONTESC_NONE : EXC_FONTESC_SUB; + break; + + case '\"': // font name + aReadFont.setLength(0); + aReadStyle.setLength(0); + eState = xlPSFont; + break; + default: + if( ('0' <= *pChar) && (*pChar <= '9') ) // font size + { + nReadHeight = *pChar - '0'; + eState = xlPSHeight; + } + } + } + break; + +// --- read font name --- + + case xlPSFont: + { + switch( *pChar ) + { + case '\"': + --pChar; + [[fallthrough]]; + case ',': + eState = xlPSFontStyle; + break; + default: + aReadFont.append(*pChar); + } + } + break; + +// --- read font style --- + + case xlPSFontStyle: + { + switch( *pChar ) + { + case '\"': + SetAttribs(); + if( !aReadFont.isEmpty() ) + mxFontData->maName = aReadFont.toString(); + mxFontData->maStyle = aReadStyle.toString(); + eState = xlPSText; + break; + default: + aReadStyle.append(*pChar); + } + } + break; + +// --- read font height --- + + case xlPSHeight: + { + if( ('0' <= *pChar) && (*pChar <= '9') ) + { + if( nReadHeight != 0xFFFF ) + { + nReadHeight *= 10; + nReadHeight += (*pChar - '0'); + if( nReadHeight > 1600 ) // max 1600pt = 32000twips + nReadHeight = 0xFFFF; + } + } + else + { + if( (nReadHeight != 0) && (nReadHeight != 0xFFFF) ) + { + SetAttribs(); + mxFontData->mnHeight = nReadHeight * 20; + } + --pChar; + eState = xlPSText; + } + } + break; + } + ++pChar; + } + + // finalize + CreateCurrObject(); + maInfos[ EXC_HF_LEFT ].mnHeight += GetMaxLineHeight( EXC_HF_LEFT ); + maInfos[ EXC_HF_CENTER ].mnHeight += GetMaxLineHeight( EXC_HF_CENTER ); + maInfos[ EXC_HF_RIGHT ].mnHeight += GetMaxLineHeight( EXC_HF_RIGHT ); +} + +void XclImpHFConverter::FillToItemSet( SfxItemSet& rItemSet, sal_uInt16 nWhichId ) const +{ + ScPageHFItem aHFItem( nWhichId ); + if( maInfos[ EXC_HF_LEFT ].mxObj ) + aHFItem.SetLeftArea( *maInfos[ EXC_HF_LEFT ].mxObj ); + if( maInfos[ EXC_HF_CENTER ].mxObj ) + aHFItem.SetCenterArea( *maInfos[ EXC_HF_CENTER ].mxObj ); + if( maInfos[ EXC_HF_RIGHT ].mxObj ) + aHFItem.SetRightArea( *maInfos[ EXC_HF_RIGHT ].mxObj ); + rItemSet.Put( aHFItem ); +} + +sal_Int32 XclImpHFConverter::GetTotalHeight() const +{ + return ::std::max( maInfos[ EXC_HF_LEFT ].mnHeight, + ::std::max( maInfos[ EXC_HF_CENTER ].mnHeight, maInfos[ EXC_HF_RIGHT ].mnHeight ) ); +} + +// private -------------------------------------------------------------------- + +sal_uInt16 XclImpHFConverter::GetMaxLineHeight( XclImpHFPortion ePortion ) const +{ + sal_uInt16 nMaxHt = maInfos[ ePortion ].mnMaxLineHt; + return (nMaxHt == 0) ? mxFontData->mnHeight : nMaxHt; +} + +void XclImpHFConverter::UpdateMaxLineHeight( XclImpHFPortion ePortion ) +{ + sal_uInt16& rnMaxHt = maInfos[ ePortion ].mnMaxLineHt; + rnMaxHt = ::std::max( rnMaxHt, mxFontData->mnHeight ); +} + +void XclImpHFConverter::UpdateCurrMaxLineHeight() +{ + UpdateMaxLineHeight( meCurrObj ); +} + +void XclImpHFConverter::SetAttribs() +{ + ESelection& rSel = GetCurrSel(); + if( (rSel.nStartPara != rSel.nEndPara) || (rSel.nStartPos != rSel.nEndPos) ) + { + SfxItemSet aItemSet( mrEE.GetEmptyItemSet() ); + XclImpFont aFont( GetRoot(), *mxFontData ); + aFont.FillToItemSet( aItemSet, XclFontItemType::HeaderFooter ); + mrEE.QuickSetAttribs( aItemSet, rSel ); + rSel.nStartPara = rSel.nEndPara; + rSel.nStartPos = rSel.nEndPos; + } +} + +void XclImpHFConverter::ResetFontData() +{ + if( const XclImpFont* pFirstFont = GetFontBuffer().GetFont( EXC_FONT_APP ) ) + *mxFontData = pFirstFont->GetFontData(); + else + { + mxFontData->Clear(); + mxFontData->mnHeight = 200; + } +} + +void XclImpHFConverter::InsertText() +{ + if( !maCurrText.isEmpty() ) + { + ESelection& rSel = GetCurrSel(); + OUString sString(maCurrText.makeStringAndClear()); + mrEE.QuickInsertText( sString, ESelection( rSel.nEndPara, rSel.nEndPos, rSel.nEndPara, rSel.nEndPos ) ); + rSel.nEndPos = rSel.nEndPos + sString.getLength(); + UpdateCurrMaxLineHeight(); + } +} + +void XclImpHFConverter::InsertField( const SvxFieldItem& rFieldItem ) +{ + ESelection& rSel = GetCurrSel(); + mrEE.QuickInsertField( rFieldItem, ESelection( rSel.nEndPara, rSel.nEndPos, rSel.nEndPara, rSel.nEndPos ) ); + ++rSel.nEndPos; + UpdateCurrMaxLineHeight(); +} + +void XclImpHFConverter::InsertLineBreak() +{ + ESelection& rSel = GetCurrSel(); + mrEE.QuickInsertText( OUString('\n'), ESelection( rSel.nEndPara, rSel.nEndPos, rSel.nEndPara, rSel.nEndPos ) ); + ++rSel.nEndPara; + rSel.nEndPos = 0; + GetCurrInfo().mnHeight += GetMaxLineHeight( meCurrObj ); + GetCurrInfo().mnMaxLineHt = 0; +} + +void XclImpHFConverter::CreateCurrObject() +{ + InsertText(); + SetAttribs(); + GetCurrObj() = mrEE.CreateTextObject(); +} + +void XclImpHFConverter::SetNewPortion( XclImpHFPortion eNew ) +{ + if( eNew != meCurrObj ) + { + CreateCurrObject(); + meCurrObj = eNew; + if( GetCurrObj() ) + mrEE.SetText( *GetCurrObj() ); + else + mrEE.SetText( OUString() ); + ResetFontData(); + } +} + +// URL conversion ============================================================= + +namespace { + +void lclAppendUrlChar( OUString& rUrl, sal_Unicode cChar ) +{ + // encode special characters + switch( cChar ) + { + case '#': rUrl += "%23"; break; + case '%': rUrl += "%25"; break; + default: rUrl += OUStringChar( cChar ); + } +} + +} // namespace + +void XclImpUrlHelper::DecodeUrl( + OUString& rUrl, OUString& rTabName, bool& rbSameWb, + const XclImpRoot& rRoot, const OUString& rEncodedUrl ) +{ + enum + { + xlUrlInit, /// Initial state, read string mode character. + xlUrlPath, /// Read URL path. + xlUrlFileName, /// Read file name. + xlUrlSheetName, /// Read sheet name. + xlUrlRaw /// Raw mode. No control characters will occur. + } eState = xlUrlInit; + + bool bEncoded = true; + rbSameWb = false; + + sal_Unicode cCurrDrive = 0; + OUString aDosBase( INetURLObject( rRoot.GetBasePath() ).getFSysPath( FSysStyle::Dos ) ); + if (!aDosBase.isEmpty() && aDosBase.match(":\\", 1)) + cCurrDrive = aDosBase[0]; + + const sal_Unicode* pChar = rEncodedUrl.getStr(); + while( *pChar ) + { + switch( eState ) + { + +// --- first character --- + + case xlUrlInit: + { + switch( *pChar ) + { + case EXC_URLSTART_ENCODED: + eState = xlUrlPath; + break; + case EXC_URLSTART_SELF: + case EXC_URLSTART_SELFENCODED: + rbSameWb = true; + eState = xlUrlSheetName; + break; + case '[': + bEncoded = false; + eState = xlUrlFileName; + break; + default: + bEncoded = false; + lclAppendUrlChar( rUrl, *pChar ); + eState = xlUrlPath; + } + } + break; + +// --- URL path --- + + case xlUrlPath: + { + switch( *pChar ) + { + case EXC_URL_DOSDRIVE: + { + if( *(pChar + 1) ) + { + ++pChar; + if( *pChar == '@' ) + rUrl += "\\\\"; + else + { + lclAppendUrlChar( rUrl, *pChar ); + rUrl += ":\\"; + } + } + else + rUrl += "<NULL-DRIVE!>"; + } + break; + case EXC_URL_DRIVEROOT: + if( cCurrDrive ) + { + lclAppendUrlChar( rUrl, cCurrDrive ); + rUrl += ":"; + } + [[fallthrough]]; + case EXC_URL_SUBDIR: + if( bEncoded ) + rUrl += "\\"; + else // control character in raw name -> DDE link + { + rUrl += OUStringChar(EXC_DDE_DELIM); + eState = xlUrlRaw; + } + break; + case EXC_URL_PARENTDIR: + rUrl += "..\\"; + break; + case EXC_URL_RAW: + { + if( *(pChar + 1) ) + { + sal_Int32 nLen = *++pChar; + for( sal_Int32 nChar = 0; (nChar < nLen) && *(pChar + 1); ++nChar ) + lclAppendUrlChar( rUrl, *++pChar ); +// rUrl.Append( ':' ); + } + } + break; + case '[': + eState = xlUrlFileName; + break; + default: + lclAppendUrlChar( rUrl, *pChar ); + } + } + break; + +// --- file name --- + + case xlUrlFileName: + { + switch( *pChar ) + { + case ']': eState = xlUrlSheetName; break; + default: lclAppendUrlChar( rUrl, *pChar ); + } + } + break; + +// --- sheet name --- + + case xlUrlSheetName: + rTabName += OUStringChar( *pChar ); + break; + +// --- raw read mode --- + + case xlUrlRaw: + lclAppendUrlChar( rUrl, *pChar ); + break; + } + + ++pChar; + } +} + +void XclImpUrlHelper::DecodeUrl( + OUString& rUrl, bool& rbSameWb, const XclImpRoot& rRoot, const OUString& rEncodedUrl ) +{ + OUString aTabName; + OUString aUrl; + DecodeUrl( aUrl, aTabName, rbSameWb, rRoot, rEncodedUrl ); + rUrl = aUrl; + OSL_ENSURE( aTabName.isEmpty(), "XclImpUrlHelper::DecodeUrl - sheet name ignored" ); +} + +bool XclImpUrlHelper::DecodeLink( OUString& rApplic, OUString& rTopic, const OUString& rEncUrl ) +{ + sal_Int32 nPos = rEncUrl.indexOf( EXC_DDE_DELIM ); + if( (nPos > 0) && (nPos + 1 < rEncUrl.getLength()) ) + { + rApplic = rEncUrl.copy( 0, nPos ); + rTopic = rEncUrl.copy( nPos + 1 ); + return true; + } + return false; +} + +// Cached Values ============================================================== + +XclImpCachedValue::XclImpCachedValue( XclImpStream& rStrm ) : + mfValue( 0.0 ), + mnBoolErr( 0 ) +{ + mnType = rStrm.ReaduInt8(); + switch( mnType ) + { + case EXC_CACHEDVAL_EMPTY: + rStrm.Ignore( 8 ); + break; + case EXC_CACHEDVAL_DOUBLE: + mfValue = rStrm.ReadDouble(); + break; + case EXC_CACHEDVAL_STRING: + maStr = rStrm.ReadUniString(); + break; + case EXC_CACHEDVAL_BOOL: + case EXC_CACHEDVAL_ERROR: + { + double fVal; + mnBoolErr = rStrm.ReaduInt8(); + rStrm.Ignore( 7 ); + + std::unique_ptr<ScTokenArray> pScTokArr = rStrm.GetRoot().GetOldFmlaConverter().GetBoolErr( + XclTools::ErrorToEnum( fVal, mnType == EXC_CACHEDVAL_ERROR, mnBoolErr ) ); + if( pScTokArr ) + mxTokArr = std::move( pScTokArr ); + } + break; + default: + OSL_FAIL( "XclImpCachedValue::XclImpCachedValue - unknown data type" ); + } +} + +XclImpCachedValue::~XclImpCachedValue() +{ +} + +FormulaError XclImpCachedValue::GetScError() const +{ + return (mnType == EXC_CACHEDVAL_ERROR) ? XclTools::GetScErrorCode( mnBoolErr ) : FormulaError::NONE; +} + +// Matrix Cached Values ============================================================== + +XclImpCachedMatrix::XclImpCachedMatrix( XclImpStream& rStrm ) : + mnScCols( 0 ), + mnScRows( 0 ) +{ + mnScCols = rStrm.ReaduInt8(); + mnScRows = rStrm.ReaduInt16(); + + if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 ) + { + // in BIFF2-BIFF7: 256 columns represented by 0 columns + if( mnScCols == 0 ) + mnScCols = 256; + } + else + { + // in BIFF8: columns and rows decreased by 1 + ++mnScCols; + ++mnScRows; + } + + //assuming worst case scenario of unknown types + const size_t nMinRecordSize = 1; + const size_t nMaxRows = rStrm.GetRecLeft() / (nMinRecordSize * mnScCols); + if (mnScRows > nMaxRows) + { + SAL_WARN("sc", "Parsing error: " << nMaxRows << + " max possible rows, but " << mnScRows << " claimed, truncating"); + mnScRows = nMaxRows; + } + + for( SCSIZE nScRow = 0; nScRow < mnScRows; ++nScRow ) + for( SCSIZE nScCol = 0; nScCol < mnScCols; ++nScCol ) + maValueList.push_back( std::make_unique<XclImpCachedValue>( rStrm ) ); +} + +XclImpCachedMatrix::~XclImpCachedMatrix() +{ +} + +ScMatrixRef XclImpCachedMatrix::CreateScMatrix( svl::SharedStringPool& rPool ) const +{ + ScMatrixRef xScMatrix; + OSL_ENSURE( mnScCols * mnScRows == maValueList.size(), "XclImpCachedMatrix::CreateScMatrix - element count mismatch" ); + if( mnScCols && mnScRows && static_cast< sal_uLong >( mnScCols * mnScRows ) <= maValueList.size() ) + { + xScMatrix = new ScMatrix(mnScCols, mnScRows, 0.0); + XclImpValueList::const_iterator itValue = maValueList.begin(); + for( SCSIZE nScRow = 0; nScRow < mnScRows; ++nScRow ) + { + for( SCSIZE nScCol = 0; nScCol < mnScCols; ++nScCol ) + { + switch( (*itValue)->GetType() ) + { + case EXC_CACHEDVAL_EMPTY: + // Excel shows 0.0 here, not an empty cell + xScMatrix->PutEmpty( nScCol, nScRow ); + break; + case EXC_CACHEDVAL_DOUBLE: + xScMatrix->PutDouble( (*itValue)->GetValue(), nScCol, nScRow ); + break; + case EXC_CACHEDVAL_STRING: + xScMatrix->PutString(rPool.intern((*itValue)->GetString()), nScCol, nScRow); + break; + case EXC_CACHEDVAL_BOOL: + xScMatrix->PutBoolean( (*itValue)->GetBool(), nScCol, nScRow ); + break; + case EXC_CACHEDVAL_ERROR: + xScMatrix->PutError( (*itValue)->GetScError(), nScCol, nScRow ); + break; + default: + OSL_FAIL( "XclImpCachedMatrix::CreateScMatrix - unknown value type" ); + xScMatrix->PutEmpty( nScCol, nScRow ); + } + ++itValue; + } + } + } + return xScMatrix; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xilink.cxx b/sc/source/filter/excel/xilink.cxx new file mode 100644 index 000000000..e9fea951e --- /dev/null +++ b/sc/source/filter/excel/xilink.cxx @@ -0,0 +1,962 @@ +/* -*- 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 <xilink.hxx> +#include <document.hxx> +#include <scextopt.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xiname.hxx> +#include <xltools.hxx> +#include <excform.hxx> +#include <tokenarray.hxx> +#include <externalrefmgr.hxx> +#include <scmatrix.hxx> +#include <svl/sharedstringpool.hxx> +#include <sal/log.hxx> + +#include <vector> +#include <memory> + +// *** Helper classes *** + +// Cached external cells ====================================================== + +namespace { + +/** + * Contains the address and value of an external referenced cell. + * Note that this is non-copyable, so cannot be used in most stl/boost containers. + */ +class XclImpCrn : public XclImpCachedValue +{ +public: + /** Reads a cached value and stores it with its cell address. */ + explicit XclImpCrn( XclImpStream& rStrm, const XclAddress& rXclPos ); + + const XclAddress& GetAddress() const { return maXclPos;} + +private: + XclAddress maXclPos; /// Excel position of the cached cell. +}; + +// Sheet in an external document ============================================== + +/** Contains the name and sheet index of one sheet in an external document. */ +class XclImpSupbookTab +{ +public: + /** Stores the sheet name and marks the sheet index as invalid. + The sheet index is set while creating the Calc sheet with CreateTable(). */ + explicit XclImpSupbookTab( const OUString& rTabName ); + + const OUString& GetTabName() const { return maTabName; } + + /** Reads a CRN record (external referenced cell) at the specified address. */ + void ReadCrn( XclImpStream& rStrm, const XclAddress& rXclPos ); + + void LoadCachedValues( const ScExternalRefCache::TableTypeRef& pCacheTable, + svl::SharedStringPool& rPool ); + +private: + typedef std::shared_ptr< XclImpCrn > XclImpCrnRef; + + std::vector< XclImpCrnRef > maCrnList; /// List of CRN records (cached cell values). + OUString maTabName; /// Name of the external sheet. +}; + +} + +// External document (SUPBOOK) ================================================ + +/** This class represents an external linked document (record SUPBOOK). + @descr Contains a list of all referenced sheets in the document. */ +class XclImpSupbook : protected XclImpRoot +{ +public: + /** Reads the SUPBOOK record from stream. */ + explicit XclImpSupbook( XclImpStream& rStrm ); + + /** Reads an XCT record (count of following CRNs and current sheet). */ + void ReadXct( XclImpStream& rStrm ); + /** Reads a CRN record (external referenced cell). */ + void ReadCrn( XclImpStream& rStrm ); + /** Reads an EXTERNNAME record. */ + void ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ); + + /** Returns the SUPBOOK record type. */ + XclSupbookType GetType() const { return meType; } + + /** Returns the URL of the external document. */ + const OUString& GetXclUrl() const { return maXclUrl; } + + /** Returns the external name specified by an index from the Excel document (one-based). */ + const XclImpExtName* GetExternName( sal_uInt16 nXclIndex ) const; + /** Tries to decode the URL to OLE or DDE link components. + @descr For DDE links: Decodes to application name and topic. + For OLE object links: Decodes to class name and document URL. + @return true = decoding was successful, returned strings are valid (not empty). */ + bool GetLinkData( OUString& rApplic, OUString& rDoc ) const; + /** Returns the specified macro name (1-based) or an empty string on error. */ + OUString GetMacroName( sal_uInt16 nXclNameIdx ) const; + + OUString GetTabName( sal_uInt16 nXtiTab ) const; + + sal_uInt16 GetTabCount() const; + + void LoadCachedValues(); + + svl::SharedStringPool& GetSharedStringPool(); + +private: + + std::vector< std::unique_ptr<XclImpSupbookTab> > + maSupbTabList; /// All sheet names of the document. + std::vector< std::unique_ptr<XclImpExtName> > + maExtNameList; /// All external names of the document. + OUString maXclUrl; /// URL of the external document (Excel mode). + XclSupbookType meType; /// Type of the supbook record. + sal_uInt16 mnSBTab; /// Current Excel sheet index from SUPBOOK for XCT/CRN records. +}; + +// Import link manager ======================================================== + +namespace { + +/** Contains the SUPBOOK index and sheet indexes of an external link. + @descr It is possible to enter a formula like =SUM(Sheet1:Sheet3!A1), + therefore here occurs a sheet range. */ +struct XclImpXti +{ + sal_uInt16 mnSupbook; /// Index to SUPBOOK record. + sal_uInt16 mnSBTabFirst; /// Index to the first sheet of the range in the SUPBOOK. + sal_uInt16 mnSBTabLast; /// Index to the last sheet of the range in the SUPBOOK. + explicit XclImpXti() : mnSupbook( SAL_MAX_UINT16 ), mnSBTabFirst( SAL_MAX_UINT16 ), mnSBTabLast( SAL_MAX_UINT16 ) {} +}; + +XclImpStream& operator>>( XclImpStream& rStrm, XclImpXti& rXti ) +{ + rXti.mnSupbook = rStrm.ReaduInt16(); + rXti.mnSBTabFirst = rStrm.ReaduInt16(); + rXti.mnSBTabLast = rStrm.ReaduInt16(); + return rStrm; +} + +} + +/** Implementation of the link manager. */ +class XclImpLinkManagerImpl : protected XclImpRoot +{ +public: + explicit XclImpLinkManagerImpl( const XclImpRoot& rRoot ); + + /** Reads the EXTERNSHEET record. */ + void ReadExternsheet( XclImpStream& rStrm ); + /** Reads a SUPBOOK record. */ + void ReadSupbook( XclImpStream& rStrm ); + /** Reads an XCT record and appends it to the current SUPBOOK. */ + void ReadXct( XclImpStream& rStrm ); + /** Reads a CRN record and appends it to the current SUPBOOK. */ + void ReadCrn( XclImpStream& rStrm ); + /** Reads an EXTERNNAME record and appends it to the current SUPBOOK. */ + void ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ); + + /** Returns true, if the specified XTI entry contains an internal reference. */ + bool IsSelfRef( sal_uInt16 nXtiIndex ) const; + /** Returns the Calc sheet index range of the specified XTI entry. + @return true = XTI data found, returned sheet index range is valid. */ + bool GetScTabRange( + SCTAB& rnFirstScTab, SCTAB& rnLastScTab, + sal_uInt16 nXtiIndex ) const; + /** Returns the specified external name or 0 on error. */ + const XclImpExtName* GetExternName( sal_uInt16 nXtiIndex, sal_uInt16 nExtName ) const; + + /** Returns the absolute file URL of a supporting workbook specified by + the index. */ + const OUString* GetSupbookUrl( sal_uInt16 nXtiIndex ) const; + + OUString GetSupbookTabName( sal_uInt16 nXti, sal_uInt16 nXtiTab ) const; + + /** Tries to decode the URL of the specified XTI entry to OLE or DDE link components. + @descr For DDE links: Decodes to application name and topic. + For OLE object links: Decodes to class name and document URL. + @return true = decoding was successful, returned strings are valid (not empty). */ + bool GetLinkData( OUString& rApplic, OUString& rTopic, sal_uInt16 nXtiIndex ) const; + /** Returns the specified macro name or an empty string on error. */ + OUString GetMacroName( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) const; + +private: + /** Returns the specified XTI (link entry from BIFF8 EXTERNSHEET record). */ + const XclImpXti* GetXti( sal_uInt16 nXtiIndex ) const; + /** Returns the specified SUPBOOK (external document). */ + const XclImpSupbook* GetSupbook( sal_uInt16 nXtiIndex ) const; + + void LoadCachedValues(); + +private: + typedef std::vector< XclImpXti > XclImpXtiVector; + + XclImpXtiVector maXtiList; /// List of all XTI structures. + std::vector< std::unique_ptr<XclImpSupbook> > + maSupbookList; /// List of external documents. +}; + +// *** Implementation *** + +// Excel sheet indexes ======================================================== + +// original Excel sheet names ------------------------------------------------- + +void XclImpTabInfo::AppendXclTabName( const OUString& rXclTabName, SCTAB nScTab ) +{ + maTabNames[ rXclTabName ] = nScTab; +} + +void XclImpTabInfo::InsertScTab( SCTAB nScTab ) +{ + for( auto& rEntry : maTabNames ) + if( rEntry.second >= nScTab ) + ++rEntry.second; +} + +SCTAB XclImpTabInfo::GetScTabFromXclName( const OUString& rXclTabName ) const +{ + XclTabNameMap::const_iterator aIt = maTabNames.find( rXclTabName ); + return (aIt != maTabNames.end()) ? aIt->second : SCTAB_INVALID; +} + +// record creation order - TABID record --------------------------------------- + +void XclImpTabInfo::ReadTabid( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ); + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + rStrm.EnableDecryption(); + std::size_t nReadCount = rStrm.GetRecLeft() / 2; + OSL_ENSURE( nReadCount <= 0xFFFF, "XclImpTabInfo::ReadTabid - record too long" ); + maTabIdVec.clear(); + maTabIdVec.reserve( nReadCount ); + for( std::size_t nIndex = 0; rStrm.IsValid() && (nIndex < nReadCount); ++nIndex ) + // zero index is not allowed in BIFF8, but it seems that it occurs in real life + maTabIdVec.push_back( rStrm.ReaduInt16() ); + } +} + +sal_uInt16 XclImpTabInfo::GetCurrentIndex( sal_uInt16 nCreatedId, sal_uInt16 nMaxTabId ) const +{ + sal_uInt16 nReturn = 0; + for( sal_uInt16 nValue : maTabIdVec ) + { + if( nValue == nCreatedId ) + return nReturn; + if( nValue <= nMaxTabId ) + ++nReturn; + } + return 0; +} + +// External names ============================================================= + +XclImpExtName::MOper::MOper(svl::SharedStringPool& rPool, XclImpStream& rStrm) : + mxCached(new ScMatrix(0,0)) +{ + SCSIZE nLastCol = rStrm.ReaduInt8(); + SCSIZE nLastRow = rStrm.ReaduInt16(); + + //assuming worst case scenario of nOp + one byte unistring len + const size_t nMinRecordSize = 2; + const size_t nMaxRows = rStrm.GetRecLeft() / (nMinRecordSize * (nLastCol+1)); + if (nLastRow >= nMaxRows) + { + SAL_WARN("sc", "Parsing error: " << nMaxRows << + " max possible rows, but " << nLastRow << " index claimed, truncating"); + if (nMaxRows > 0) + nLastRow = nMaxRows-1; + else + return; + } + + mxCached->Resize(nLastCol+1, nLastRow+1); + for (SCSIZE nRow = 0; nRow <= nLastRow; ++nRow) + { + for (SCSIZE nCol = 0; nCol <= nLastCol; ++nCol) + { + sal_uInt8 nOp; + nOp = rStrm.ReaduInt8(); + switch (nOp) + { + case 0x01: + { + double fVal = rStrm.ReadDouble(); + mxCached->PutDouble(fVal, nCol, nRow); + } + break; + case 0x02: + { + OUString aStr = rStrm.ReadUniString(); + mxCached->PutString(rPool.intern(aStr), nCol, nRow); + } + break; + case 0x04: + { + bool bVal = rStrm.ReaduInt8(); + mxCached->PutBoolean(bVal, nCol, nRow); + rStrm.Ignore(7); + } + break; + case 0x10: + { + sal_uInt8 nErr = rStrm.ReaduInt8(); + // Map the error code from xls to calc. + mxCached->PutError(XclTools::GetScErrorCode(nErr), nCol, nRow); + rStrm.Ignore(7); + } + break; + default: + rStrm.Ignore(8); + } + } + } +} + +const ScMatrix& XclImpExtName::MOper::GetCache() const +{ + return *mxCached; +} + +XclImpExtName::XclImpExtName( XclImpSupbook& rSupbook, XclImpStream& rStrm, XclSupbookType eSubType, ExcelToSc* pFormulaConv ) + : mnStorageId(0) +{ + sal_uInt16 nFlags(0); + sal_uInt8 nLen(0); + + nFlags = rStrm.ReaduInt16(); + mnStorageId = rStrm.ReaduInt32(); + nLen = rStrm.ReaduInt8(); + maName = rStrm.ReadUniString( nLen ); + if( ::get_flag( nFlags, EXC_EXTN_BUILTIN ) || !::get_flag( nFlags, EXC_EXTN_OLE_OR_DDE ) ) + { + if( eSubType == XclSupbookType::Addin ) + { + meType = xlExtAddIn; + maName = XclImpRoot::GetScAddInName( maName ); + } + else if ( (eSubType == XclSupbookType::Eurotool) && + maName.equalsIgnoreAsciiCase( "EUROCONVERT" ) ) + meType = xlExtEuroConvert; + else + { + meType = xlExtName; + maName = ScfTools::ConvertToScDefinedName( maName ); + } + } + else + { + meType = ::get_flagvalue( nFlags, EXC_EXTN_OLE, xlExtOLE, xlExtDDE ); + } + + switch (meType) + { + case xlExtDDE: + if (rStrm.GetRecLeft() > 1) + mxDdeMatrix.reset(new XclImpCachedMatrix(rStrm)); + break; + case xlExtName: + // TODO: For now, only global external names are supported. In future + // we should extend this to supporting per-sheet external names. + if (mnStorageId == 0 && pFormulaConv) + { + std::unique_ptr<ScTokenArray> pArray; + sal_uInt16 nFmlaLen; + nFmlaLen = rStrm.ReaduInt16(); + std::vector<OUString> aTabNames; + sal_uInt16 nCount = rSupbook.GetTabCount(); + aTabNames.reserve(nCount); + for (sal_uInt16 i = 0; i < nCount; ++i) + aTabNames.push_back(rSupbook.GetTabName(i)); + + pFormulaConv->ConvertExternName(pArray, rStrm, nFmlaLen, rSupbook.GetXclUrl(), aTabNames); + if (pArray) + mxArray = std::move( pArray ); + } + break; + case xlExtOLE: + mpMOper.reset( new MOper(rSupbook.GetSharedStringPool(), rStrm) ); + break; + default: + ; + } +} + +XclImpExtName::~XclImpExtName() +{ +} + +void XclImpExtName::CreateDdeData( ScDocument& rDoc, const OUString& rApplic, const OUString& rTopic ) const +{ + ScMatrixRef xResults; + if( mxDdeMatrix ) + xResults = mxDdeMatrix->CreateScMatrix(rDoc.GetSharedStringPool()); + rDoc.CreateDdeLink( rApplic, rTopic, maName, SC_DDE_DEFAULT, xResults ); +} + +void XclImpExtName::CreateExtNameData( const ScDocument& rDoc, sal_uInt16 nFileId ) const +{ + if (!mxArray) + return; + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + pRefMgr->storeRangeNameTokens(nFileId, maName, *mxArray); +} + +namespace { + +/** + * Decompose the name into sheet name and range name. An OLE link name is + * always formatted like this [ !Sheet1!R1C1:R5C2 ] and it always uses R1C1 + * notation. + */ +bool extractSheetAndRange(const OUString& rName, OUString& rSheet, OUString& rRange) +{ + sal_Int32 n = rName.getLength(); + const sal_Unicode* p = rName.getStr(); + OUStringBuffer aBuf; + bool bInSheet = true; + for (sal_Int32 i = 0; i < n; ++i, ++p) + { + if (i == 0) + { + // first character must be '!'. + if (*p != '!') + return false; + continue; + } + + if (*p == '!') + { + // sheet name to range separator. + if (!bInSheet) + return false; + rSheet = aBuf.makeStringAndClear(); + bInSheet = false; + continue; + } + + aBuf.append(*p); + } + + rRange = aBuf.makeStringAndClear(); + return true; +} + +} + +bool XclImpExtName::CreateOleData(const ScDocument& rDoc, const OUString& rUrl, + sal_uInt16& rFileId, OUString& rTabName, ScRange& rRange) const +{ + if (!mpMOper) + return false; + + OUString aSheet, aRangeStr; + if (!extractSheetAndRange(maName, aSheet, aRangeStr)) + return false; + + ScRange aRange; + ScRefFlags nRes = aRange.ParseAny(aRangeStr, rDoc, formula::FormulaGrammar::CONV_XL_R1C1); + if ((nRes & ScRefFlags::VALID) == ScRefFlags::ZERO) + return false; + + if (aRange.aStart.Tab() != aRange.aEnd.Tab()) + // We don't support multi-sheet range for this. + return false; + + const ScMatrix& rCache = mpMOper->GetCache(); + SCSIZE nC, nR; + rCache.GetDimensions(nC, nR); + if (!nC || !nR) + // cache matrix is empty. + return false; + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(rUrl); + ScExternalRefCache::TableTypeRef xTab = pRefMgr->getCacheTable(nFileId, aSheet, true); + if (!xTab) + // cache table creation failed. + return false; + + xTab->setWholeTableCached(); + for (SCSIZE i = 0; i < nR; ++i) + { + for (SCSIZE j = 0; j < nC; ++j) + { + SCCOL nCol = aRange.aStart.Col() + j; + SCROW nRow = aRange.aStart.Row() + i; + + ScMatrixValue aVal = rCache.Get(j, i); + switch (aVal.nType) + { + case ScMatValType::Boolean: + { + bool b = aVal.GetBoolean(); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(b ? 1.0 : 0.0)); + xTab->setCell(nCol, nRow, pToken, 0, false); + } + break; + case ScMatValType::Value: + { + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(aVal.fVal)); + xTab->setCell(nCol, nRow, pToken, 0, false); + } + break; + case ScMatValType::String: + { + ScExternalRefCache::TokenRef pToken(new formula::FormulaStringToken(aVal.GetString())); + xTab->setCell(nCol, nRow, pToken, 0, false); + } + break; + default: + ; + } + } + } + + rFileId = nFileId; + rTabName = aSheet; + rRange = aRange; + return true; +} + +bool XclImpExtName::HasFormulaTokens() const +{ + return bool(mxArray); +} + +// Cached external cells ====================================================== + +XclImpCrn::XclImpCrn( XclImpStream& rStrm, const XclAddress& rXclPos ) : + XclImpCachedValue( rStrm ), + maXclPos( rXclPos ) +{ +} + +// Sheet in an external document ============================================== + +XclImpSupbookTab::XclImpSupbookTab( const OUString& rTabName ) : + maTabName( rTabName ) +{ +} + +void XclImpSupbookTab::ReadCrn( XclImpStream& rStrm, const XclAddress& rXclPos ) +{ + XclImpCrnRef crnRef = std::make_shared<XclImpCrn>(rStrm, rXclPos); + maCrnList.push_back( crnRef ); +} + +void XclImpSupbookTab::LoadCachedValues( const ScExternalRefCache::TableTypeRef& pCacheTable, + svl::SharedStringPool& rPool ) +{ + if (maCrnList.empty()) + return; + + for (const auto& rxCrn : maCrnList) + { + const XclImpCrn* const pCrn = rxCrn.get(); + const XclAddress& rAddr = pCrn->GetAddress(); + switch (pCrn->GetType()) + { + case EXC_CACHEDVAL_BOOL: + { + bool b = pCrn->GetBool(); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(b ? 1.0 : 0.0)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + case EXC_CACHEDVAL_DOUBLE: + { + double f = pCrn->GetValue(); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(f)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + case EXC_CACHEDVAL_ERROR: + { + double fError = XclTools::ErrorToDouble( pCrn->GetXclError() ); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(fError)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + case EXC_CACHEDVAL_STRING: + { + svl::SharedString aSS( rPool.intern( pCrn->GetString())); + ScExternalRefCache::TokenRef pToken(new formula::FormulaStringToken( aSS)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + default: + ; + } + } +} + +// External document (SUPBOOK) ================================================ + +XclImpSupbook::XclImpSupbook( XclImpStream& rStrm ) : + XclImpRoot( rStrm.GetRoot() ), + meType( XclSupbookType::Unknown ), + mnSBTab( EXC_TAB_DELETED ) +{ + sal_uInt16 nSBTabCnt; + nSBTabCnt = rStrm.ReaduInt16(); + + if( rStrm.GetRecLeft() == 2 ) + { + switch( rStrm.ReaduInt16() ) + { + case EXC_SUPB_SELF: meType = XclSupbookType::Self; break; + case EXC_SUPB_ADDIN: meType = XclSupbookType::Addin; break; + default: OSL_FAIL( "XclImpSupbook::XclImpSupbook - unknown special SUPBOOK type" ); + } + return; + } + + OUString aEncUrl( rStrm.ReadUniString() ); + bool bSelf = false; + XclImpUrlHelper::DecodeUrl( maXclUrl, bSelf, GetRoot(), aEncUrl ); + + if( maXclUrl.equalsIgnoreAsciiCase( "\010EUROTOOL.XLA" ) ) + { + meType = XclSupbookType::Eurotool; + maSupbTabList.push_back( std::make_unique<XclImpSupbookTab>( maXclUrl ) ); + } + else if( nSBTabCnt ) + { + meType = XclSupbookType::Extern; + + //assuming all empty strings with just len header of 0 + const size_t nMinRecordSize = sizeof(sal_Int16); + const size_t nMaxRecords = rStrm.GetRecLeft() / nMinRecordSize; + if (nSBTabCnt > nMaxRecords) + { + SAL_WARN("sc", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nSBTabCnt << " claimed, truncating"); + nSBTabCnt = nMaxRecords; + } + + for( sal_uInt16 nSBTab = 0; nSBTab < nSBTabCnt; ++nSBTab ) + { + OUString aTabName( rStrm.ReadUniString() ); + maSupbTabList.push_back( std::make_unique<XclImpSupbookTab>( aTabName ) ); + } + } + else + { + meType = XclSupbookType::Special; + // create dummy list entry + maSupbTabList.push_back( std::make_unique<XclImpSupbookTab>( maXclUrl ) ); + } +} + +void XclImpSupbook::ReadXct( XclImpStream& rStrm ) +{ + rStrm.Ignore( 2 ); + mnSBTab = rStrm.ReaduInt16(); +} + +void XclImpSupbook::ReadCrn( XclImpStream& rStrm ) +{ + if (mnSBTab >= maSupbTabList.size()) + return; + XclImpSupbookTab& rSbTab = *maSupbTabList[mnSBTab]; + sal_uInt8 nXclColLast, nXclColFirst; + sal_uInt16 nXclRow; + nXclColLast = rStrm.ReaduInt8(); + nXclColFirst = rStrm.ReaduInt8(); + nXclRow = rStrm.ReaduInt16(); + + for( sal_uInt8 nXclCol = nXclColFirst; (nXclCol <= nXclColLast) && (rStrm.GetRecLeft() > 1); ++nXclCol ) + rSbTab.ReadCrn( rStrm, XclAddress( nXclCol, nXclRow ) ); +} + +void XclImpSupbook::ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ) +{ + maExtNameList.push_back( std::make_unique<XclImpExtName>( *this, rStrm, meType, pFormulaConv ) ); +} + +const XclImpExtName* XclImpSupbook::GetExternName( sal_uInt16 nXclIndex ) const +{ + if (nXclIndex == 0) + { + SAL_WARN("sc", "XclImpSupbook::GetExternName - index must be >0"); + return nullptr; + } + if (meType == XclSupbookType::Self || nXclIndex > maExtNameList.size()) + return nullptr; + return maExtNameList[nXclIndex-1].get(); +} + +bool XclImpSupbook::GetLinkData( OUString& rApplic, OUString& rTopic ) const +{ + return (meType == XclSupbookType::Special) && XclImpUrlHelper::DecodeLink( rApplic, rTopic, maXclUrl ); +} + +OUString XclImpSupbook::GetMacroName( sal_uInt16 nXclNameIdx ) const +{ + OSL_ENSURE( nXclNameIdx > 0, "XclImpSupbook::GetMacroName - index must be >0" ); + const XclImpName* pName = (meType == XclSupbookType::Self) ? GetNameManager().GetName( nXclNameIdx ) : nullptr; + return (pName && pName->IsVBName()) ? pName->GetScName() : OUString(); +} + +OUString XclImpSupbook::GetTabName( sal_uInt16 nXtiTab ) const +{ + if (nXtiTab >= maSupbTabList.size()) + return OUString(); + return maSupbTabList[nXtiTab]->GetTabName(); +} + +sal_uInt16 XclImpSupbook::GetTabCount() const +{ + return ulimit_cast<sal_uInt16>(maSupbTabList.size()); +} + +void XclImpSupbook::LoadCachedValues() +{ + if (meType != XclSupbookType::Extern || GetExtDocOptions().GetDocSettings().mnLinkCnt > 0 || !GetDocShell()) + return; + + OUString aAbsUrl( ScGlobal::GetAbsDocName(maXclUrl, GetDocShell()) ); + + ScExternalRefManager* pRefMgr = GetRoot().GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aAbsUrl); + + for (auto& rxTab : maSupbTabList) + { + const OUString& rTabName = rxTab->GetTabName(); + ScExternalRefCache::TableTypeRef pCacheTable = pRefMgr->getCacheTable(nFileId, rTabName, true); + rxTab->LoadCachedValues( pCacheTable, GetSharedStringPool()); + pCacheTable->setWholeTableCached(); + } +} + +svl::SharedStringPool& XclImpSupbook::GetSharedStringPool() +{ + return GetDoc().GetSharedStringPool(); +} + +// Import link manager ======================================================== + +XclImpLinkManagerImpl::XclImpLinkManagerImpl( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpLinkManagerImpl::ReadExternsheet( XclImpStream& rStrm ) +{ + sal_uInt16 nXtiCount; + nXtiCount = rStrm.ReaduInt16(); + OSL_ENSURE( static_cast< std::size_t >( nXtiCount * 6 ) == rStrm.GetRecLeft(), "XclImpLinkManagerImpl::ReadExternsheet - invalid count" ); + nXtiCount = static_cast< sal_uInt16 >( ::std::min< std::size_t >( nXtiCount, rStrm.GetRecLeft() / 6 ) ); + + /* #i104057# A weird external XLS generator writes multiple EXTERNSHEET + records instead of only one as expected. Surprisingly, Excel seems to + insert the entries of the second record before the entries of the first + record. */ + XclImpXtiVector aNewEntries( nXtiCount ); + for( auto& rNewEntry : aNewEntries ) + { + if (!rStrm.IsValid()) + break; + rStrm >> rNewEntry; + } + maXtiList.insert( maXtiList.begin(), aNewEntries.begin(), aNewEntries.end() ); + + LoadCachedValues(); +} + +void XclImpLinkManagerImpl::ReadSupbook( XclImpStream& rStrm ) +{ + maSupbookList.push_back( std::make_unique<XclImpSupbook>( rStrm ) ); +} + +void XclImpLinkManagerImpl::ReadXct( XclImpStream& rStrm ) +{ + if( !maSupbookList.empty() ) + maSupbookList.back()->ReadXct( rStrm ); +} + +void XclImpLinkManagerImpl::ReadCrn( XclImpStream& rStrm ) +{ + if( !maSupbookList.empty() ) + maSupbookList.back()->ReadCrn( rStrm ); +} + +void XclImpLinkManagerImpl::ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ) +{ + if( !maSupbookList.empty() ) + maSupbookList.back()->ReadExternname( rStrm, pFormulaConv ); +} + +bool XclImpLinkManagerImpl::IsSelfRef( sal_uInt16 nXtiIndex ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nXtiIndex ); + return pSupbook && (pSupbook->GetType() == XclSupbookType::Self); +} + +bool XclImpLinkManagerImpl::GetScTabRange( + SCTAB& rnFirstScTab, SCTAB& rnLastScTab, sal_uInt16 nXtiIndex ) const +{ + if( const XclImpXti* pXti = GetXti( nXtiIndex ) ) + { + if (!maSupbookList.empty() && (pXti->mnSupbook < maSupbookList.size()) ) + { + rnFirstScTab = pXti->mnSBTabFirst; + rnLastScTab = pXti->mnSBTabLast; + return true; + } + } + return false; +} + +const XclImpExtName* XclImpLinkManagerImpl::GetExternName( sal_uInt16 nXtiIndex, sal_uInt16 nExtName ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nXtiIndex ); + return pSupbook ? pSupbook->GetExternName( nExtName ) : nullptr; +} + +const OUString* XclImpLinkManagerImpl::GetSupbookUrl( sal_uInt16 nXtiIndex ) const +{ + const XclImpSupbook* p = GetSupbook( nXtiIndex ); + if (!p) + return nullptr; + return &p->GetXclUrl(); +} + +OUString XclImpLinkManagerImpl::GetSupbookTabName( sal_uInt16 nXti, sal_uInt16 nXtiTab ) const +{ + const XclImpSupbook* p = GetSupbook(nXti); + return p ? p->GetTabName(nXtiTab) : OUString(); +} + +bool XclImpLinkManagerImpl::GetLinkData( OUString& rApplic, OUString& rTopic, sal_uInt16 nXtiIndex ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nXtiIndex ); + return pSupbook && pSupbook->GetLinkData( rApplic, rTopic ); +} + +OUString XclImpLinkManagerImpl::GetMacroName( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nExtSheet ); + return pSupbook ? pSupbook->GetMacroName( nExtName ) : OUString(); +} + +const XclImpXti* XclImpLinkManagerImpl::GetXti( sal_uInt16 nXtiIndex ) const +{ + return (nXtiIndex < maXtiList.size()) ? &maXtiList[ nXtiIndex ] : nullptr; +} + +const XclImpSupbook* XclImpLinkManagerImpl::GetSupbook( sal_uInt16 nXtiIndex ) const +{ + if ( maSupbookList.empty() ) + return nullptr; + const XclImpXti* pXti = GetXti( nXtiIndex ); + if (!pXti || pXti->mnSupbook >= maSupbookList.size()) + return nullptr; + return maSupbookList.at( pXti->mnSupbook ).get(); +} + +void XclImpLinkManagerImpl::LoadCachedValues() +{ + // Read all CRN records which can be accessed via XclImpSupbook, and store + // the cached values to the external reference manager. + for (auto& rxSupbook : maSupbookList) + rxSupbook->LoadCachedValues(); +} + +XclImpLinkManager::XclImpLinkManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mxImpl( new XclImpLinkManagerImpl( rRoot ) ) +{ +} + +XclImpLinkManager::~XclImpLinkManager() +{ +} + +void XclImpLinkManager::ReadExternsheet( XclImpStream& rStrm ) +{ + mxImpl->ReadExternsheet( rStrm ); +} + +void XclImpLinkManager::ReadSupbook( XclImpStream& rStrm ) +{ + mxImpl->ReadSupbook( rStrm ); +} + +void XclImpLinkManager::ReadXct( XclImpStream& rStrm ) +{ + mxImpl->ReadXct( rStrm ); +} + +void XclImpLinkManager::ReadCrn( XclImpStream& rStrm ) +{ + mxImpl->ReadCrn( rStrm ); +} + +void XclImpLinkManager::ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ) +{ + mxImpl->ReadExternname( rStrm, pFormulaConv ); +} + +bool XclImpLinkManager::IsSelfRef( sal_uInt16 nXtiIndex ) const +{ + return mxImpl->IsSelfRef( nXtiIndex ); +} + +bool XclImpLinkManager::GetScTabRange( + SCTAB& rnFirstScTab, SCTAB& rnLastScTab, sal_uInt16 nXtiIndex ) const +{ + return mxImpl->GetScTabRange( rnFirstScTab, rnLastScTab, nXtiIndex ); +} + +const XclImpExtName* XclImpLinkManager::GetExternName( sal_uInt16 nXtiIndex, sal_uInt16 nExtName ) const +{ + return mxImpl->GetExternName( nXtiIndex, nExtName ); +} + +const OUString* XclImpLinkManager::GetSupbookUrl( sal_uInt16 nXtiIndex ) const +{ + return mxImpl->GetSupbookUrl(nXtiIndex); +} + +OUString XclImpLinkManager::GetSupbookTabName( sal_uInt16 nXti, sal_uInt16 nXtiTab ) const +{ + return mxImpl->GetSupbookTabName(nXti, nXtiTab); +} + +bool XclImpLinkManager::GetLinkData( OUString& rApplic, OUString& rTopic, sal_uInt16 nXtiIndex ) const +{ + return mxImpl->GetLinkData( rApplic, rTopic, nXtiIndex ); +} + +OUString XclImpLinkManager::GetMacroName( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) const +{ + return mxImpl->GetMacroName( nExtSheet, nExtName ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiname.cxx b/sc/source/filter/excel/xiname.cxx new file mode 100644 index 000000000..d498dfba4 --- /dev/null +++ b/sc/source/filter/excel/xiname.cxx @@ -0,0 +1,322 @@ +/* -*- 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 <xiname.hxx> +#include <xlname.hxx> +#include <rangenam.hxx> +#include <xistream.hxx> +#include <excform.hxx> +#include <excimp8.hxx> +#include <scextopt.hxx> +#include <document.hxx> + +// *** Implementation *** + +XclImpName::TokenStrmData::TokenStrmData( XclImpStream& rStrm ) : + mrStrm(rStrm), mnStrmPos(0), mnStrmSize(0) {} + +XclImpName::XclImpName( XclImpStream& rStrm, sal_uInt16 nXclNameIdx ) : + XclImpRoot( rStrm.GetRoot() ), + mpScData( nullptr ), + mnScTab( SCTAB_MAX ), + meNameType( ScRangeData::Type::Name ), + mnXclTab( EXC_NAME_GLOBAL ), + mnNameIndex( nXclNameIdx ), + mbVBName( false ), + mbMacro( false ) +{ + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + + // 1) *** read data from stream *** --------------------------------------- + + sal_uInt16 nFlags = 0, nFmlaSize = 0, nExtSheet = EXC_NAME_GLOBAL; + sal_uInt8 nNameLen = 0; + sal_Unicode cBuiltIn(EXC_BUILTIN_UNKNOWN); /// Excel built-in name index. + + switch( GetBiff() ) + { + case EXC_BIFF2: + { + sal_uInt8 nFlagsBiff2; + nFlagsBiff2 = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + rStrm.Ignore( 1 ); //nShortCut + nNameLen = rStrm.ReaduInt8(); + nFmlaSize = rStrm.ReaduInt8(); + ::set_flag( nFlags, EXC_NAME_FUNC, ::get_flag( nFlagsBiff2, EXC_NAME2_FUNC ) ); + } + break; + + case EXC_BIFF3: + case EXC_BIFF4: + { + nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 1 ); //nShortCut + nNameLen = rStrm.ReaduInt8(); + nFmlaSize = rStrm.ReaduInt16(); + } + break; + + case EXC_BIFF5: + case EXC_BIFF8: + { + nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 1 ); //nShortCut + nNameLen = rStrm.ReaduInt8(); + nFmlaSize = rStrm.ReaduInt16(); + nExtSheet = rStrm.ReaduInt16(); + mnXclTab = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + } + break; + + default: DBG_ERROR_BIFF(); + } + + if( GetBiff() <= EXC_BIFF5 ) + maXclName = rStrm.ReadRawByteString( nNameLen ); + else + maXclName = rStrm.ReadUniString( nNameLen ); + + // 2) *** convert sheet index and name *** -------------------------------- + + // functions and VBA + bool bFunction = ::get_flag( nFlags, EXC_NAME_FUNC ); + mbVBName = ::get_flag( nFlags, EXC_NAME_VB ); + mbMacro = ::get_flag( nFlags, EXC_NAME_PROC ); + + // get built-in name, or convert characters invalid in Calc + bool bBuiltIn = ::get_flag( nFlags, EXC_NAME_BUILTIN ); + + // special case for BIFF5 filter range - name appears as plain text without built-in flag + if( (GetBiff() == EXC_BIFF5) && (maXclName == XclTools::GetXclBuiltInDefName(EXC_BUILTIN_FILTERDATABASE)) ) + { + bBuiltIn = true; + maXclName = OUStringChar(EXC_BUILTIN_FILTERDATABASE); + } + + // convert Excel name to Calc name + if( mbVBName ) + { + // VB macro name + maScName = maXclName; + } + else if( bBuiltIn ) + { + // built-in name + if( !maXclName.isEmpty() ) + cBuiltIn = maXclName[0]; + if( cBuiltIn == '?' ) // NUL character is imported as '?' + cBuiltIn = '\0'; + maScName = XclTools::GetBuiltInDefName( cBuiltIn ); + } + else + { + // any other name + maScName = ScfTools::ConvertToScDefinedName( maXclName ); + } + + // add index for local names + if( mnXclTab != EXC_NAME_GLOBAL ) + { + sal_uInt16 nUsedTab = (GetBiff() == EXC_BIFF8) ? mnXclTab : nExtSheet; + // TODO: may not work for BIFF5, handle skipped sheets (all BIFF) + mnScTab = static_cast< SCTAB >( nUsedTab - 1 ); + } + + // 3) *** convert the name definition formula *** ------------------------- + + rFmlaConv.Reset(); + std::unique_ptr<ScTokenArray> pTokArr; + + if( ::get_flag( nFlags, EXC_NAME_BIG ) ) + { + // special, unsupported name + pTokArr = rFmlaConv.GetDummy(); + } + else if( bBuiltIn ) + { + SCTAB const nLocalTab = (mnXclTab == EXC_NAME_GLOBAL) ? SCTAB_MAX : (mnXclTab - 1); + + // --- print ranges or title ranges --- + rStrm.PushPosition(); + switch( cBuiltIn ) + { + case EXC_BUILTIN_PRINTAREA: + if( rFmlaConv.Convert( GetPrintAreaBuffer(), rStrm, nFmlaSize, nLocalTab, FT_RangeName ) == ConvErr::OK ) + meNameType |= ScRangeData::Type::PrintArea; + break; + case EXC_BUILTIN_PRINTTITLES: + if( rFmlaConv.Convert( GetTitleAreaBuffer(), rStrm, nFmlaSize, nLocalTab, FT_RangeName ) == ConvErr::OK ) + meNameType |= ScRangeData::Type::ColHeader | ScRangeData::Type::RowHeader; + break; + } + rStrm.PopPosition(); + + // --- name formula --- + // JEG : double check this. It is clearly false for normal names + // but some of the builtins (sheettitle?) might be able to handle arrays + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize, false, FT_RangeName ); + + // --- auto or advanced filter --- + if ((GetBiff() == EXC_BIFF8) && pTokArr) + { + ScRange aRange; + if (pTokArr->IsReference(aRange, ScAddress())) + { + switch( cBuiltIn ) + { + case EXC_BUILTIN_FILTERDATABASE: + GetFilterManager().Insert( &GetOldRoot(), aRange); + break; + case EXC_BUILTIN_CRITERIA: + GetFilterManager().AddAdvancedRange( aRange ); + meNameType |= ScRangeData::Type::Criteria; + break; + case EXC_BUILTIN_EXTRACT: + if (pTokArr->IsValidReference(aRange, ScAddress())) + GetFilterManager().AddExtractPos( aRange ); + break; + } + } + } + } + else if( nFmlaSize > 0 ) + { + // Regular defined name. We need to convert the tokens after all the + // names have been registered (for cross-referenced names). + mpTokensData.reset(new TokenStrmData(rStrm)); + mpTokensData->mnStrmPos = rStrm.GetSvStreamPos(); + rStrm.StorePosition(mpTokensData->maStrmPos); + mpTokensData->mnStrmSize = nFmlaSize; + } + + if (pTokArr && !bFunction && !mbVBName) + InsertName(pTokArr.get()); +} + +void XclImpName::ConvertTokens() +{ + if (!mpTokensData) + return; + + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + rFmlaConv.Reset(); + std::unique_ptr<ScTokenArray> pArray; + + XclImpStreamPos aOldPos; + XclImpStream& rStrm = mpTokensData->mrStrm; + rStrm.StorePosition(aOldPos); + rStrm.RestorePosition(mpTokensData->maStrmPos); + rFmlaConv.Convert(pArray, rStrm, mpTokensData->mnStrmSize, true, FT_RangeName); + rStrm.RestorePosition(aOldPos); + + if (pArray) + InsertName(pArray.get()); + + mpTokensData.reset(); +} + +void XclImpName::InsertName(const ScTokenArray* pArray) +{ + // create the Calc name data + ScRangeData* pData = new ScRangeData(GetDoc(), maScName, *pArray, ScAddress(), meNameType); + pData->GuessPosition(); // calculate base position for relative refs + pData->SetIndex( mnNameIndex ); // used as unique identifier in formulas + if (mnXclTab == EXC_NAME_GLOBAL) + { + if (!GetDoc().GetRangeName()->insert(pData)) + pData = nullptr; + } + else + { + ScRangeName* pLocalNames = GetDoc().GetRangeName(mnScTab); + if (pLocalNames) + { + if (!pLocalNames->insert(pData)) + pData = nullptr; + } + else + { + delete pData; + pData = nullptr; + } + + if (GetBiff() == EXC_BIFF8 && pData) + { + ScRange aRange; + // discard deleted ranges ( for the moment at least ) + if ( pData->IsValidReference( aRange ) ) + { + GetExtDocOptions().GetOrCreateTabSettings( mnXclTab ); + } + } + } + if (pData) + { + GetDoc().CheckLinkFormulaNeedingCheck( *pData->GetCode()); + mpScData = pData; // cache for later use + } +} + +XclImpNameManager::XclImpNameManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpNameManager::ReadName( XclImpStream& rStrm ) +{ + size_t nCount = maNameList.size(); + if( nCount < 0xFFFF ) + maNameList.push_back( std::make_unique<XclImpName>( rStrm, static_cast< sal_uInt16 >( nCount + 1 ) ) ); +} + +const XclImpName* XclImpNameManager::FindName( std::u16string_view rXclName, SCTAB nScTab ) const +{ + const XclImpName* pGlobalName = nullptr; // a found global name + const XclImpName* pLocalName = nullptr; // a found local name + for( const auto& rxName : maNameList ) + { + if( rxName->GetXclName() == rXclName ) + { + if( rxName->GetScTab() == nScTab ) + pLocalName = rxName.get(); + else if( rxName->IsGlobal() ) + pGlobalName = rxName.get(); + } + + if (pLocalName) + break; + } + return pLocalName ? pLocalName : pGlobalName; +} + +const XclImpName* XclImpNameManager::GetName( sal_uInt16 nXclNameIdx ) const +{ + OSL_ENSURE( nXclNameIdx > 0, "XclImpNameManager::GetName - index must be >0" ); + return ( nXclNameIdx <= 0 || nXclNameIdx > maNameList.size() ) ? nullptr : maNameList.at( nXclNameIdx - 1 ).get(); +} + +void XclImpNameManager::ConvertAllTokens() +{ + for (auto& rxName : maNameList) + rxName->ConvertTokens(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xipage.cxx b/sc/source/filter/excel/xipage.cxx new file mode 100644 index 000000000..c06308ba7 --- /dev/null +++ b/sc/source/filter/excel/xipage.cxx @@ -0,0 +1,401 @@ +/* -*- 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 <xipage.hxx> +#include <svl/itemset.hxx> +#include <vcl/graph.hxx> +#include <scitems.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svx/pageitem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <unotools/configmgr.hxx> +#include <document.hxx> +#include <stlsheet.hxx> +#include <attrib.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xiescher.hxx> + +// Page settings ============================================================== + +XclImpPageSettings::XclImpPageSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ + Initialize(); +} + +void XclImpPageSettings::Initialize() +{ + maData.SetDefaults(); + mbValidPaper = false; +} + +void XclImpPageSettings::ReadSetup( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF4 ); + if( GetBiff() < EXC_BIFF4 ) + return; + + // BIFF4 - BIFF8 + sal_uInt16 nFlags; + maData.mnPaperSize = rStrm.ReaduInt16(); + maData.mnScaling = rStrm.ReaduInt16(); + maData.mnStartPage = rStrm.ReaduInt16(); + maData.mnFitToWidth = rStrm.ReaduInt16(); + maData.mnFitToHeight = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + + mbValidPaper = maData.mbValid = !::get_flag( nFlags, EXC_SETUP_INVALID ); + maData.mbPrintInRows = ::get_flag( nFlags, EXC_SETUP_INROWS ); + maData.mbPortrait = ::get_flag( nFlags, EXC_SETUP_PORTRAIT ); + maData.mbBlackWhite = ::get_flag( nFlags, EXC_SETUP_BLACKWHITE ); + maData.mbManualStart = true; + + // new in BIFF5 - BIFF8 + if( GetBiff() >= EXC_BIFF5 ) + { + maData.mnHorPrintRes = rStrm.ReaduInt16(); + maData.mnVerPrintRes = rStrm.ReaduInt16(); + maData.mfHeaderMargin = rStrm.ReadDouble(); + maData.mfFooterMargin = rStrm.ReadDouble(); + maData.mnCopies = rStrm.ReaduInt16(); + + maData.mbDraftQuality = ::get_flag( nFlags, EXC_SETUP_DRAFT ); + maData.mbPrintNotes = ::get_flag( nFlags, EXC_SETUP_PRINTNOTES ); + maData.mbManualStart = ::get_flag( nFlags, EXC_SETUP_STARTPAGE ); + } +} + +void XclImpPageSettings::ReadMargin( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_LEFTMARGIN: maData.mfLeftMargin = rStrm.ReadDouble(); break; + case EXC_ID_RIGHTMARGIN: maData.mfRightMargin = rStrm.ReadDouble(); break; + case EXC_ID_TOPMARGIN: maData.mfTopMargin = rStrm.ReadDouble(); break; + case EXC_ID_BOTTOMMARGIN: maData.mfBottomMargin = rStrm.ReadDouble(); break; + default: OSL_FAIL( "XclImpPageSettings::ReadMargin - unknown record" ); + } +} + +void XclImpPageSettings::ReadCenter( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF3 ); // read it anyway + bool bCenter = (rStrm.ReaduInt16() != 0); + switch( rStrm.GetRecId() ) + { + case EXC_ID_HCENTER: maData.mbHorCenter = bCenter; break; + case EXC_ID_VCENTER: maData.mbVerCenter = bCenter; break; + default: OSL_FAIL( "XclImpPageSettings::ReadCenter - unknown record" ); + } +} + +void XclImpPageSettings::ReadHeaderFooter( XclImpStream& rStrm ) +{ + OUString aString; + if( rStrm.GetRecLeft() ) + aString = (GetBiff() <= EXC_BIFF5) ? rStrm.ReadByteString( false ) : rStrm.ReadUniString(); + + switch( rStrm.GetRecId() ) + { + case EXC_ID_HEADER: maData.maHeader = aString; break; + case EXC_ID_FOOTER: maData.maFooter = aString; break; + case EXC_ID_HEADER_EVEN: maData.maHeaderEven = aString; break; + case EXC_ID_FOOTER_EVEN: maData.maFooterEven = aString; break; + default: OSL_FAIL( "XclImpPageSettings::ReadHeaderFooter - unknown record" ); + } + + if (utl::ConfigManager::IsFuzzing()) + { + if (maData.maHeader.getLength() > 10) + maData.maHeader = maData.maHeader.copy(0, 10); + if (maData.maFooter.getLength() > 10) + maData.maFooter = maData.maFooter.copy(0, 10); + if (maData.maHeaderEven.getLength() > 10) + maData.maHeaderEven = maData.maHeaderEven.copy(0, 10); + if (maData.maFooterEven.getLength() > 10) + maData.maFooterEven = maData.maFooterEven.copy(0, 10); + } +} + +void XclImpPageSettings::ReadPageBreaks( XclImpStream& rStrm ) +{ + ScfUInt16Vec* pVec = nullptr; + switch( rStrm.GetRecId() ) + { + case EXC_ID_HORPAGEBREAKS: pVec = &maData.maHorPageBreaks; break; + case EXC_ID_VERPAGEBREAKS: pVec = &maData.maVerPageBreaks; break; + default: OSL_FAIL( "XclImpPageSettings::ReadPageBreaks - unknown record" ); + } + + if( !pVec ) + return; + + bool bIgnore = GetBiff() == EXC_BIFF8; // ignore start/end columns or rows in BIFF8 + + sal_uInt16 nCount, nBreak; + nCount = rStrm.ReaduInt16(); + pVec->clear(); + pVec->reserve( nCount ); + + while( nCount-- ) + { + nBreak = rStrm.ReaduInt16(); + if( nBreak ) + pVec->push_back( nBreak ); + if( bIgnore ) + rStrm.Ignore( 4 ); + } +} + +void XclImpPageSettings::ReadPrintHeaders( XclImpStream& rStrm ) +{ + maData.mbPrintHeadings = (rStrm.ReaduInt16() != 0); +} + +void XclImpPageSettings::ReadPrintGridLines( XclImpStream& rStrm ) +{ + maData.mbPrintGrid = (rStrm.ReaduInt16() != 0); +} + +void XclImpPageSettings::ReadImgData( XclImpStream& rStrm ) +{ + Graphic aGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); + if( aGraphic.GetType() != GraphicType::NONE ) + maData.mxBrushItem.reset( new SvxBrushItem( aGraphic, GPOS_TILED, ATTR_BACKGROUND ) ); +} + +void XclImpPageSettings::SetPaperSize( sal_uInt16 nXclPaperSize, bool bPortrait ) +{ + maData.mnPaperSize = nXclPaperSize; + maData.mbPortrait = bPortrait; + mbValidPaper = true; +} + +namespace { + +void lclPutMarginItem( SfxItemSet& rItemSet, sal_uInt16 nRecId, double fMarginInch ) +{ + sal_uInt16 nMarginTwips = XclTools::GetTwipsFromInch( fMarginInch ); + switch( nRecId ) + { + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: + { + SvxULSpaceItem aItem( rItemSet.Get( ATTR_ULSPACE ) ); + if( nRecId == EXC_ID_TOPMARGIN ) + aItem.SetUpperValue( nMarginTwips ); + else + aItem.SetLowerValue( nMarginTwips ); + rItemSet.Put( aItem ); + } + break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + { + SvxLRSpaceItem aItem( rItemSet.Get( ATTR_LRSPACE ) ); + if( nRecId == EXC_ID_LEFTMARGIN ) + aItem.SetLeftValue( nMarginTwips ); + else + aItem.SetRightValue( nMarginTwips ); + rItemSet.Put( aItem ); + } + break; + default: + OSL_FAIL( "XclImpPageSettings::SetMarginItem - unknown record id" ); + } +} + +} // namespace + +void XclImpPageSettings::Finalize() +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + + // *** create page style sheet *** + + OUString aStyleName; + OUString aTableName; + if( GetDoc().GetName( nScTab, aTableName ) ) + aStyleName = "PageStyle_" + aTableName; + else + aStyleName = "PageStyle_" + OUString::number(static_cast<sal_Int32>(nScTab+1)); + + ScStyleSheet& rStyleSheet = ScfTools::MakePageStyleSheet( + GetStyleSheetPool(), aStyleName, false); + + SfxItemSet& rItemSet = rStyleSheet.GetItemSet(); + + // *** page settings *** + + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_TOPDOWN, !maData.mbPrintInRows ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_HORCENTER, maData.mbHorCenter ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_VERCENTER, maData.mbVerCenter ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_HEADERS, maData.mbPrintHeadings ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_GRID, maData.mbPrintGrid ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_NOTES, maData.mbPrintNotes ), true ); + + sal_uInt16 nStartPage = maData.mbManualStart ? maData.mnStartPage : 0; + ScfTools::PutItem( rItemSet, SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, nStartPage ), true ); + + if( maData.mxBrushItem ) + rItemSet.Put( *maData.mxBrushItem ); + + if( mbValidPaper ) + { + SvxPageItem aPageItem( rItemSet.Get( ATTR_PAGE ) ); + aPageItem.SetLandscape( !maData.mbPortrait ); + rItemSet.Put( aPageItem ); + ScfTools::PutItem( rItemSet, SvxSizeItem( ATTR_PAGE_SIZE, maData.GetScPaperSize() ), true ); + } + + if( maData.mbFitToPages ) + rItemSet.Put( ScPageScaleToItem( maData.mnFitToWidth, maData.mnFitToHeight ) ); + else if( maData.mbValid ) + rItemSet.Put( SfxUInt16Item( ATTR_PAGE_SCALE, maData.mnScaling ) ); + + // *** margin preparations *** + + double fLeftMargin = maData.mfLeftMargin; + double fRightMargin = maData.mfRightMargin; + double fTopMargin = maData.mfTopMargin; + double fBottomMargin = maData.mfBottomMargin; + // distances between header/footer and page area + double fHeaderHeight = 0.0; + double fHeaderDist = 0.0; + double fFooterHeight = 0.0; + double fFooterDist = 0.0; + // in Calc, "header/footer left/right margin" is X distance between header/footer and page margin + double fHdrLeftMargin = maData.mfHdrLeftMargin - maData.mfLeftMargin; + double fHdrRightMargin = maData.mfHdrRightMargin - maData.mfRightMargin; + double fFtrLeftMargin = maData.mfFtrLeftMargin - maData.mfLeftMargin; + double fFtrRightMargin = maData.mfFtrRightMargin - maData.mfRightMargin; + + // *** header and footer *** + + XclImpHFConverter aHFConv( GetRoot() ); + + // header + bool bHasHeader = !maData.maHeader.isEmpty(); + SvxSetItem aHdrSetItem( rItemSet.Get( ATTR_PAGE_HEADERSET ) ); + SfxItemSet& rHdrItemSet = aHdrSetItem.GetItemSet(); + rHdrItemSet.Put( SfxBoolItem( ATTR_PAGE_ON, bHasHeader ) ); + if( bHasHeader ) + { + aHFConv.ParseString( maData.maHeader ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_HEADERLEFT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_HEADERRIGHT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_HEADERFIRST ); + // #i23296# In Calc, "top margin" is distance to header + fTopMargin = maData.mfHeaderMargin; + // Calc uses distance between header and sheet data area + fHeaderHeight = XclTools::GetInchFromTwips( aHFConv.GetTotalHeight() ); + fHeaderDist = maData.mfTopMargin - maData.mfHeaderMargin - fHeaderHeight; + } + if( fHeaderDist < 0.0 ) + { + /* #i23296# Header overlays sheet data: + -> set fixed header height to get correct sheet data position. */ + ScfTools::PutItem( rHdrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, false ), true ); + // shrink header height + tools::Long nHdrHeight = XclTools::GetTwipsFromInch( fHeaderHeight + fHeaderDist ); + ScfTools::PutItem( rHdrItemSet, SvxSizeItem( ATTR_PAGE_SIZE, Size( 0, nHdrHeight ) ), true ); + lclPutMarginItem( rHdrItemSet, EXC_ID_BOTTOMMARGIN, 0.0 ); + } + else + { + // use dynamic header height + ScfTools::PutItem( rHdrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, true ), true ); + lclPutMarginItem( rHdrItemSet, EXC_ID_BOTTOMMARGIN, fHeaderDist ); + } + lclPutMarginItem( rHdrItemSet, EXC_ID_LEFTMARGIN, fHdrLeftMargin ); + lclPutMarginItem( rHdrItemSet, EXC_ID_RIGHTMARGIN, fHdrRightMargin ); + rItemSet.Put( aHdrSetItem ); + + // footer + bool bHasFooter = !maData.maFooter.isEmpty(); + SvxSetItem aFtrSetItem( rItemSet.Get( ATTR_PAGE_FOOTERSET ) ); + SfxItemSet& rFtrItemSet = aFtrSetItem.GetItemSet(); + rFtrItemSet.Put( SfxBoolItem( ATTR_PAGE_ON, bHasFooter ) ); + if( bHasFooter ) + { + aHFConv.ParseString( maData.maFooter ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_FOOTERLEFT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_FOOTERRIGHT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_FOOTERFIRST ); + // #i23296# In Calc, "bottom margin" is distance to footer + fBottomMargin = maData.mfFooterMargin; + // Calc uses distance between footer and sheet data area + fFooterHeight = XclTools::GetInchFromTwips( aHFConv.GetTotalHeight() ); + fFooterDist = maData.mfBottomMargin - maData.mfFooterMargin - fFooterHeight; + } + if( fFooterDist < 0.0 ) + { + /* #i23296# Footer overlays sheet data: + -> set fixed footer height to get correct sheet data end position. */ + ScfTools::PutItem( rFtrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, false ), true ); + // shrink footer height + tools::Long nFtrHeight = XclTools::GetTwipsFromInch( fFooterHeight + fFooterDist ); + ScfTools::PutItem( rFtrItemSet, SvxSizeItem( ATTR_PAGE_SIZE, Size( 0, nFtrHeight ) ), true ); + lclPutMarginItem( rFtrItemSet, EXC_ID_TOPMARGIN, 0.0 ); + } + else + { + // use dynamic footer height + ScfTools::PutItem( rFtrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, true ), true ); + lclPutMarginItem( rFtrItemSet, EXC_ID_TOPMARGIN, fFooterDist ); + } + lclPutMarginItem( rFtrItemSet, EXC_ID_LEFTMARGIN, fFtrLeftMargin ); + lclPutMarginItem( rFtrItemSet, EXC_ID_RIGHTMARGIN, fFtrRightMargin ); + rItemSet.Put( aFtrSetItem ); + + // *** set final margins *** + + lclPutMarginItem( rItemSet, EXC_ID_LEFTMARGIN, fLeftMargin ); + lclPutMarginItem( rItemSet, EXC_ID_RIGHTMARGIN, fRightMargin ); + lclPutMarginItem( rItemSet, EXC_ID_TOPMARGIN, fTopMargin ); + lclPutMarginItem( rItemSet, EXC_ID_BOTTOMMARGIN, fBottomMargin ); + + // *** put style sheet into document *** + + rDoc.SetPageStyle( nScTab, rStyleSheet.GetName() ); + + // *** page breaks *** + + for( const auto& rHorPageBreak : maData.maHorPageBreaks ) + { + SCROW nScRow = static_cast< SCROW >( rHorPageBreak ); + if( nScRow <= rDoc.MaxRow() ) + rDoc.SetRowBreak(nScRow, nScTab, false, true); + } + + for( const auto& rVerPageBreak : maData.maVerPageBreaks ) + { + SCCOL nScCol = static_cast< SCCOL >( rVerPageBreak ); + if( nScCol <= rDoc.MaxCol() ) + rDoc.SetColBreak(nScCol, nScTab, false, true); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xipivot.cxx b/sc/source/filter/excel/xipivot.cxx new file mode 100644 index 000000000..d8d4eaa63 --- /dev/null +++ b/sc/source/filter/excel/xipivot.cxx @@ -0,0 +1,1737 @@ +/* -*- 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 <xipivot.hxx> + +#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldReference.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp> + +#include <tools/datetime.hxx> +#include <svl/intitem.hxx> +#include <svl/numformat.hxx> +#include <sal/log.hxx> +#include <sot/storage.hxx> +#include <unotools/configmgr.hxx> + +#include <document.hxx> +#include <formulacell.hxx> +#include <dpsave.hxx> +#include <dpdimsave.hxx> +#include <dpobject.hxx> +#include <dpshttab.hxx> +#include <dpoutputgeometry.hxx> +#include <scitems.hxx> +#include <attrib.hxx> + +#include <xltracer.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xilink.hxx> +#include <xiescher.hxx> + +//TODO ExcelToSc usage +#include <excform.hxx> +#include <documentimport.hxx> + +#include <vector> + +using namespace com::sun::star; + +using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA; +using ::com::sun::star::sheet::DataPilotFieldSortInfo; +using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo; +using ::com::sun::star::sheet::DataPilotFieldLayoutInfo; +using ::com::sun::star::sheet::DataPilotFieldReference; +using ::std::vector; + +// Pivot cache + +XclImpPCItem::XclImpPCItem( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_SXDOUBLE: ReadSxdouble( rStrm ); break; + case EXC_ID_SXBOOLEAN: ReadSxboolean( rStrm ); break; + case EXC_ID_SXERROR: ReadSxerror( rStrm ); break; + case EXC_ID_SXINTEGER: ReadSxinteger( rStrm ); break; + case EXC_ID_SXSTRING: ReadSxstring( rStrm ); break; + case EXC_ID_SXDATETIME: ReadSxdatetime( rStrm ); break; + case EXC_ID_SXEMPTY: ReadSxempty( rStrm ); break; + default: OSL_FAIL( "XclImpPCItem::XclImpPCItem - unknown record id" ); + } +} + +namespace { + +void lclSetValue( XclImpRoot& rRoot, const ScAddress& rScPos, double fValue, SvNumFormatType nFormatType ) +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + rDoc.setNumericCell(rScPos, fValue); + sal_uInt32 nScNumFmt = rRoot.GetFormatter().GetStandardFormat( nFormatType, rRoot.GetDocLanguage() ); + rDoc.getDoc().ApplyAttr( + rScPos.Col(), rScPos.Row(), rScPos.Tab(), SfxUInt32Item(ATTR_VALUE_FORMAT, nScNumFmt)); +} + +} // namespace + +void XclImpPCItem::WriteToSource( XclImpRoot& rRoot, const ScAddress& rScPos ) const +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + if( const OUString* pText = GetText() ) + rDoc.setStringCell(rScPos, *pText); + else if( const double* pfValue = GetDouble() ) + rDoc.setNumericCell(rScPos, *pfValue); + else if( const sal_Int16* pnValue = GetInteger() ) + rDoc.setNumericCell(rScPos, *pnValue); + else if( const bool* pbValue = GetBool() ) + lclSetValue( rRoot, rScPos, *pbValue ? 1.0 : 0.0, SvNumFormatType::LOGICAL ); + else if( const DateTime* pDateTime = GetDateTime() ) + { + // set number format date, time, or date/time, depending on the value + double fValue = rRoot.GetDoubleFromDateTime( *pDateTime ); + double fInt = 0.0; + double fFrac = modf( fValue, &fInt ); + SvNumFormatType nFormatType = ((fFrac == 0.0) && (fInt != 0.0)) ? SvNumFormatType::DATE : + ((fInt == 0.0) ? SvNumFormatType::TIME : SvNumFormatType::DATETIME); + lclSetValue( rRoot, rScPos, fValue, nFormatType ); + } + else if( const sal_uInt16* pnError = GetError() ) + { + double fValue; + sal_uInt8 nErrCode = static_cast< sal_uInt8 >( *pnError ); + std::unique_ptr<ScTokenArray> pScTokArr = rRoot.GetOldFmlaConverter().GetBoolErr( + XclTools::ErrorToEnum( fValue, true, nErrCode ) ); + ScFormulaCell* pCell = pScTokArr + ? new ScFormulaCell(rDoc.getDoc(), rScPos, std::move(pScTokArr)) + : new ScFormulaCell(rDoc.getDoc(), rScPos); + pCell->SetHybridDouble( fValue ); + rDoc.setFormulaCell(rScPos, pCell); + } +} + +void XclImpPCItem::ReadSxdouble( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 8, "XclImpPCItem::ReadSxdouble - wrong record size" ); + SetDouble( rStrm.ReadDouble() ); +} + +void XclImpPCItem::ReadSxboolean( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxboolean - wrong record size" ); + SetBool( rStrm.ReaduInt16() != 0 ); +} + +void XclImpPCItem::ReadSxerror( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxerror - wrong record size" ); + SetError( rStrm.ReaduInt16() ); +} + +void XclImpPCItem::ReadSxinteger( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxinteger - wrong record size" ); + SetInteger( rStrm.ReadInt16() ); +} + +void XclImpPCItem::ReadSxstring( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() >= 3, "XclImpPCItem::ReadSxstring - wrong record size" ); + SetText( rStrm.ReadUniString() ); +} + +void XclImpPCItem::ReadSxdatetime( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 8, "XclImpPCItem::ReadSxdatetime - wrong record size" ); + sal_uInt16 nYear, nMonth; + sal_uInt8 nDay, nHour, nMin, nSec; + nYear = rStrm.ReaduInt16(); + nMonth = rStrm.ReaduInt16(); + nDay = rStrm.ReaduInt8(); + nHour = rStrm.ReaduInt8(); + nMin = rStrm.ReaduInt8(); + nSec = rStrm.ReaduInt8(); + SetDateTime( DateTime( Date( nDay, nMonth, nYear ), tools::Time( nHour, nMin, nSec ) ) ); +} + +void XclImpPCItem::ReadSxempty( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 0, "XclImpPCItem::ReadSxempty - wrong record size" ); + SetEmpty(); +} + +XclImpPCField::XclImpPCField( const XclImpRoot& rRoot, XclImpPivotCache& rPCache, sal_uInt16 nFieldIdx ) : + XclPCField( EXC_PCFIELD_UNKNOWN, nFieldIdx ), + XclImpRoot( rRoot ), + mrPCache( rPCache ), + mnSourceScCol( -1 ), + mbNumGroupInfoRead( false ) +{ +} + +XclImpPCField::~XclImpPCField() +{ +} + +// general field/item access -------------------------------------------------- + +const OUString& XclImpPCField::GetFieldName( const ScfStringVec& rVisNames ) const +{ + if( IsGroupChildField() && (mnFieldIdx < rVisNames.size()) ) + { + const OUString& rVisName = rVisNames[ mnFieldIdx ]; + if (!rVisName.isEmpty()) + return rVisName; + } + return maFieldInfo.maName; +} + +const XclImpPCField* XclImpPCField::GetGroupBaseField() const +{ + OSL_ENSURE( IsGroupChildField(), "XclImpPCField::GetGroupBaseField - this field type does not have a base field" ); + return IsGroupChildField() ? mrPCache.GetField( maFieldInfo.mnGroupBase ) : nullptr; +} + +const XclImpPCItem* XclImpPCField::GetItem( sal_uInt16 nItemIdx ) const +{ + return (nItemIdx < maItems.size()) ? maItems[ nItemIdx ].get() : nullptr; +} + +const XclImpPCItem* XclImpPCField::GetLimitItem( sal_uInt16 nItemIdx ) const +{ + OSL_ENSURE( nItemIdx < 3, "XclImpPCField::GetLimitItem - invalid item index" ); + OSL_ENSURE( nItemIdx < maNumGroupItems.size(), "XclImpPCField::GetLimitItem - no item found" ); + return (nItemIdx < maNumGroupItems.size()) ? maNumGroupItems[ nItemIdx ].get() : nullptr; +} + +void XclImpPCField::WriteFieldNameToSource( SCCOL nScCol, SCTAB nScTab ) +{ + OSL_ENSURE( HasOrigItems(), "XclImpPCField::WriteFieldNameToSource - only for standard fields" ); + GetDocImport().setStringCell(ScAddress(nScCol, 0, nScTab), maFieldInfo.maName); + mnSourceScCol = nScCol; +} + +void XclImpPCField::WriteOrigItemToSource( SCROW nScRow, SCTAB nScTab, sal_uInt16 nItemIdx ) +{ + if( nItemIdx < maOrigItems.size() ) + maOrigItems[ nItemIdx ]->WriteToSource( GetRoot(), ScAddress( mnSourceScCol, nScRow, nScTab ) ); +} + +void XclImpPCField::WriteLastOrigItemToSource( SCROW nScRow, SCTAB nScTab ) +{ + if( !maOrigItems.empty() ) + maOrigItems.back()->WriteToSource( GetRoot(), ScAddress( mnSourceScCol, nScRow, nScTab ) ); +} + +// records -------------------------------------------------------------------- + +void XclImpPCField::ReadSxfield( XclImpStream& rStrm ) +{ + rStrm >> maFieldInfo; + + /* Detect the type of this field. This is done very restrictive to detect + any unexpected state. */ + meFieldType = EXC_PCFIELD_UNKNOWN; + + bool bItems = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS ); + bool bPostp = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_POSTPONE ); + bool bCalced = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_CALCED ); + bool bChild = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ); + bool bNum = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP ); + + sal_uInt16 nVisC = maFieldInfo.mnVisItems; + sal_uInt16 nGroupC = maFieldInfo.mnGroupItems; + sal_uInt16 nBaseC = maFieldInfo.mnBaseItems; + sal_uInt16 nOrigC = maFieldInfo.mnOrigItems; + OSL_ENSURE( nVisC > 0, "XclImpPCField::ReadSxfield - field without visible items" ); + + sal_uInt16 nType = maFieldInfo.mnFlags & EXC_SXFIELD_DATA_MASK; + bool bType = + (nType == EXC_SXFIELD_DATA_STR) || + (nType == EXC_SXFIELD_DATA_INT) || + (nType == EXC_SXFIELD_DATA_DBL) || + (nType == EXC_SXFIELD_DATA_STR_INT) || + (nType == EXC_SXFIELD_DATA_STR_DBL) || + (nType == EXC_SXFIELD_DATA_DATE) || + (nType == EXC_SXFIELD_DATA_DATE_EMP) || + (nType == EXC_SXFIELD_DATA_DATE_NUM) || + (nType == EXC_SXFIELD_DATA_DATE_STR); + bool bTypeNone = + (nType == EXC_SXFIELD_DATA_NONE); + // for now, ignore data type of calculated fields + OSL_ENSURE( bCalced || bType || bTypeNone, "XclImpPCField::ReadSxfield - unknown item data type" ); + + if( !(nVisC > 0 || bPostp) ) + return; + + if( bItems && !bPostp ) + { + if( !bCalced ) + { + // 1) standard fields and standard grouping fields + if( !bNum ) + { + // 1a) standard field without grouping + if( bType && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == nVisC) ) + meFieldType = EXC_PCFIELD_STANDARD; + + // 1b) standard grouping field + else if( bTypeNone && (nGroupC == nVisC) && (nBaseC > 0) && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_STDGROUP; + } + // 2) numerical grouping fields + else if( (nGroupC == nVisC) && (nBaseC == 0) ) + { + // 2a) single num/date grouping field without child grouping field + if( !bChild && bType && (nOrigC > 0) ) + { + switch( nType ) + { + case EXC_SXFIELD_DATA_INT: + case EXC_SXFIELD_DATA_DBL: meFieldType = EXC_PCFIELD_NUMGROUP; break; + case EXC_SXFIELD_DATA_DATE: meFieldType = EXC_PCFIELD_DATEGROUP; break; + default: OSL_FAIL( "XclImpPCField::ReadSxfield - numeric group with wrong data type" ); + } + } + + // 2b) first date grouping field with child grouping field + else if( bChild && (nType == EXC_SXFIELD_DATA_DATE) && (nOrigC > 0) ) + meFieldType = EXC_PCFIELD_DATEGROUP; + + // 2c) additional date grouping field + else if( bTypeNone && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_DATECHILD; + } + OSL_ENSURE( meFieldType != EXC_PCFIELD_UNKNOWN, "XclImpPCField::ReadSxfield - invalid standard or grouped field" ); + } + + // 3) calculated field + else + { + if( !bChild && !bNum && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_CALCED; + OSL_ENSURE( meFieldType == EXC_PCFIELD_CALCED, "XclImpPCField::ReadSxfield - invalid calculated field" ); + } + } + + else if( !bItems && bPostp ) + { + // 4) standard field with postponed items + if( !bCalced && !bChild && !bNum && bType && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_STANDARD; + OSL_ENSURE( meFieldType == EXC_PCFIELD_STANDARD, "XclImpPCField::ReadSxfield - invalid postponed field" ); + } +} + +void XclImpPCField::ReadItem( XclImpStream& rStrm ) +{ + OSL_ENSURE( HasInlineItems() || HasPostponedItems(), "XclImpPCField::ReadItem - field does not expect items" ); + + // read the item + XclImpPCItemRef xItem = std::make_shared<XclImpPCItem>( rStrm ); + + // try to insert into an item list + if( mbNumGroupInfoRead ) + { + // there are 3 items after SXNUMGROUP that contain grouping limits and step count + if( maNumGroupItems.size() < 3 ) + maNumGroupItems.push_back( xItem ); + else + maOrigItems.push_back( xItem ); + } + else if( HasInlineItems() || HasPostponedItems() ) + { + maItems.push_back( xItem ); + // visible item is original item in standard fields + if( IsStandardField() ) + maOrigItems.push_back( xItem ); + } +} + +void XclImpPCField::ReadSxnumgroup( XclImpStream& rStrm ) +{ + OSL_ENSURE( IsNumGroupField() || IsDateGroupField(), "XclImpPCField::ReadSxnumgroup - SXNUMGROUP outside numeric grouping field" ); + OSL_ENSURE( !mbNumGroupInfoRead, "XclImpPCField::ReadSxnumgroup - multiple SXNUMGROUP records" ); + OSL_ENSURE( maItems.size() == maFieldInfo.mnGroupItems, "XclImpPCField::ReadSxnumgroup - SXNUMGROUP out of record order" ); + rStrm >> maNumGroupInfo; + mbNumGroupInfoRead = IsNumGroupField() || IsDateGroupField(); +} + +void XclImpPCField::ReadSxgroupinfo( XclImpStream& rStrm ) +{ + OSL_ENSURE( IsStdGroupField(), "XclImpPCField::ReadSxgroupinfo - SXGROUPINFO outside grouping field" ); + OSL_ENSURE( maGroupOrder.empty(), "XclImpPCField::ReadSxgroupinfo - multiple SXGROUPINFO records" ); + OSL_ENSURE( maItems.size() == maFieldInfo.mnGroupItems, "XclImpPCField::ReadSxgroupinfo - SXGROUPINFO out of record order" ); + OSL_ENSURE( (rStrm.GetRecLeft() / 2) == maFieldInfo.mnBaseItems, "XclImpPCField::ReadSxgroupinfo - wrong SXGROUPINFO size" ); + maGroupOrder.clear(); + size_t nSize = rStrm.GetRecLeft() / 2; + maGroupOrder.resize( nSize, 0 ); + for( size_t nIdx = 0; nIdx < nSize; ++nIdx ) + maGroupOrder[ nIdx ] = rStrm.ReaduInt16(); +} + +// grouping ------------------------------------------------------------------- + +void XclImpPCField::ConvertGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + if (!GetFieldName(rVisNames).isEmpty()) + { + if( IsStdGroupField() ) + ConvertStdGroupField( rSaveData, rVisNames ); + else if( IsNumGroupField() ) + ConvertNumGroupField( rSaveData, rVisNames ); + else if( IsDateGroupField() ) + ConvertDateGroupField( rSaveData, rVisNames ); + } +} + +// private -------------------------------------------------------------------- + +void XclImpPCField::ConvertStdGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + const XclImpPCField* pBaseField = GetGroupBaseField(); + if(!pBaseField) + return; + + const OUString& rBaseFieldName = pBaseField->GetFieldName( rVisNames ); + if( rBaseFieldName.isEmpty() ) + return; + + // *** create a ScDPSaveGroupItem for each own item, they collect base item names *** + ScDPSaveGroupItemVec aGroupItems; + aGroupItems.reserve( maItems.size() ); + // initialize with own item names + for( const auto& rxItem : maItems ) + aGroupItems.emplace_back( rxItem->ConvertToText() ); + + // *** iterate over all base items, set their names at corresponding own items *** + for( sal_uInt16 nItemIdx = 0, nItemCount = static_cast< sal_uInt16 >( maGroupOrder.size() ); nItemIdx < nItemCount; ++nItemIdx ) + if( maGroupOrder[ nItemIdx ] < aGroupItems.size() ) + if( const XclImpPCItem* pBaseItem = pBaseField->GetItem( nItemIdx ) ) + if( const XclImpPCItem* pGroupItem = GetItem( maGroupOrder[ nItemIdx ] ) ) + if( *pBaseItem != *pGroupItem ) + aGroupItems[ maGroupOrder[ nItemIdx ] ].AddElement( pBaseItem->ConvertToText() ); + + // *** create the ScDPSaveGroupDimension object, fill with grouping info *** + ScDPSaveGroupDimension aGroupDim( rBaseFieldName, GetFieldName( rVisNames ) ); + for( const auto& rGroupItem : aGroupItems ) + if( !rGroupItem.IsEmpty() ) + aGroupDim.AddGroupItem( rGroupItem ); + rSaveData.GetDimensionData()->AddGroupDimension( aGroupDim ); +} + +void XclImpPCField::ConvertNumGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + ScDPNumGroupInfo aNumInfo( GetScNumGroupInfo() ); + ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), aNumInfo ); + rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim ); +} + +void XclImpPCField::ConvertDateGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + ScDPNumGroupInfo aDateInfo( GetScDateGroupInfo() ); + sal_Int32 nScDateType = maNumGroupInfo.GetScDateType(); + + switch( meFieldType ) + { + case EXC_PCFIELD_DATEGROUP: + { + if( aDateInfo.mbDateValues ) + { + // special case for days only with step value - create numeric grouping + ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), aDateInfo ); + rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim ); + } + else + { + ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), ScDPNumGroupInfo() ); + aNumGroupDim.SetDateInfo( aDateInfo, nScDateType ); + rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim ); + } + } + break; + + case EXC_PCFIELD_DATECHILD: + { + if( const XclImpPCField* pBaseField = GetGroupBaseField() ) + { + const OUString& rBaseFieldName = pBaseField->GetFieldName( rVisNames ); + if( !rBaseFieldName.isEmpty() ) + { + ScDPSaveGroupDimension aGroupDim( rBaseFieldName, GetFieldName( rVisNames ) ); + aGroupDim.SetDateInfo( aDateInfo, nScDateType ); + rSaveData.GetDimensionData()->AddGroupDimension( aGroupDim ); + } + } + } + break; + + default: + OSL_FAIL( "XclImpPCField::ConvertDateGroupField - unknown date field type" ); + } +} + +ScDPNumGroupInfo XclImpPCField::GetScNumGroupInfo() const +{ + ScDPNumGroupInfo aNumInfo; + aNumInfo.mbEnable = true; + aNumInfo.mbDateValues = false; + aNumInfo.mbAutoStart = true; + aNumInfo.mbAutoEnd = true; + + if( const double* pfMinValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_MIN ) ) + { + aNumInfo.mfStart = *pfMinValue; + aNumInfo.mbAutoStart = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN ); + } + if( const double* pfMaxValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_MAX ) ) + { + aNumInfo.mfEnd = *pfMaxValue; + aNumInfo.mbAutoEnd = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX ); + } + if( const double* pfStepValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_STEP ) ) + aNumInfo.mfStep = *pfStepValue; + + return aNumInfo; +} + +ScDPNumGroupInfo XclImpPCField::GetScDateGroupInfo() const +{ + ScDPNumGroupInfo aDateInfo; + aDateInfo.mbEnable = true; + aDateInfo.mbDateValues = false; + aDateInfo.mbAutoStart = true; + aDateInfo.mbAutoEnd = true; + + if( const DateTime* pMinDate = GetDateGroupLimit( EXC_SXFIELD_INDEX_MIN ) ) + { + aDateInfo.mfStart = GetDoubleFromDateTime( *pMinDate ); + aDateInfo.mbAutoStart = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN ); + } + if( const DateTime* pMaxDate = GetDateGroupLimit( EXC_SXFIELD_INDEX_MAX ) ) + { + aDateInfo.mfEnd = GetDoubleFromDateTime( *pMaxDate ); + aDateInfo.mbAutoEnd = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX ); + } + // GetDateGroupStep() returns a value for date type "day" in single date groups only + if( const sal_Int16* pnStepValue = GetDateGroupStep() ) + { + aDateInfo.mfStep = *pnStepValue; + aDateInfo.mbDateValues = true; + } + + return aDateInfo; +} + +const double* XclImpPCField::GetNumGroupLimit( sal_uInt16 nLimitIdx ) const +{ + OSL_ENSURE( IsNumGroupField(), "XclImpPCField::GetNumGroupLimit - only for numeric grouping fields" ); + if( const XclImpPCItem* pItem = GetLimitItem( nLimitIdx ) ) + { + OSL_ENSURE( pItem->GetDouble(), "XclImpPCField::GetNumGroupLimit - SXDOUBLE item expected" ); + return pItem->GetDouble(); + } + return nullptr; +} + +const DateTime* XclImpPCField::GetDateGroupLimit( sal_uInt16 nLimitIdx ) const +{ + OSL_ENSURE( IsDateGroupField(), "XclImpPCField::GetDateGroupLimit - only for date grouping fields" ); + if( const XclImpPCItem* pItem = GetLimitItem( nLimitIdx ) ) + { + OSL_ENSURE( pItem->GetDateTime(), "XclImpPCField::GetDateGroupLimit - SXDATETIME item expected" ); + return pItem->GetDateTime(); + } + return nullptr; +} + +const sal_Int16* XclImpPCField::GetDateGroupStep() const +{ + // only for single date grouping fields, not for grouping chains + if( !IsGroupBaseField() && !IsGroupChildField() ) + { + // only days may have a step value, return 0 for all other date types + if( maNumGroupInfo.GetXclDataType() == EXC_SXNUMGROUP_TYPE_DAY ) + { + if( const XclImpPCItem* pItem = GetLimitItem( EXC_SXFIELD_INDEX_STEP ) ) + { + OSL_ENSURE( pItem->GetInteger(), "XclImpPCField::GetDateGroupStep - SXINTEGER item expected" ); + if( const sal_Int16* pnStep = pItem->GetInteger() ) + { + OSL_ENSURE( *pnStep > 0, "XclImpPCField::GetDateGroupStep - invalid step count" ); + // return nothing for step count 1 - this is also a standard date group in Excel + return (*pnStep > 1) ? pnStep : nullptr; + } + } + } + } + return nullptr; +} + +XclImpPivotCache::XclImpPivotCache( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maSrcRange( ScAddress::INITIALIZE_INVALID ), + mnStrmId( 0 ), + mnSrcType( EXC_SXVS_UNKNOWN ), + mbSelfRef( false ) +{ +} + +XclImpPivotCache::~XclImpPivotCache() +{ +} + +// data access ---------------------------------------------------------------- + +const XclImpPCField* XclImpPivotCache::GetField( sal_uInt16 nFieldIdx ) const +{ + return (nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr; +} + +// records -------------------------------------------------------------------- + +void XclImpPivotCache::ReadSxidstm( XclImpStream& rStrm ) +{ + mnStrmId = rStrm.ReaduInt16(); +} + +void XclImpPivotCache::ReadSxvs( XclImpStream& rStrm ) +{ + mnSrcType = rStrm.ReaduInt16(); + GetTracer().TracePivotDataSource( mnSrcType != EXC_SXVS_SHEET ); +} + +void XclImpPivotCache::ReadDconref( XclImpStream& rStrm ) +{ + /* Read DCONREF only once (by checking maTabName), there may be other + DCONREF records in another context. Read reference only if a leading + SXVS record is present (by checking mnSrcType). */ + if( !maTabName.isEmpty() || (mnSrcType != EXC_SXVS_SHEET) ) + return; + + XclRange aXclRange( ScAddress::UNINITIALIZED ); + aXclRange.Read( rStrm, false ); + OUString aEncUrl = rStrm.ReadUniString(); + + XclImpUrlHelper::DecodeUrl( maUrl, maTabName, mbSelfRef, GetRoot(), aEncUrl ); + + /* Do not convert maTabName to Calc sheet name -> original name is used to + find the sheet in the document. Sheet index of source range will be + found later in XclImpPivotCache::ReadPivotCacheStream(), because sheet + may not exist yet. */ + if( mbSelfRef ) + GetAddressConverter().ConvertRange( maSrcRange, aXclRange, 0, 0, true ); +} + +void XclImpPivotCache::ReadDConName( XclImpStream& rStrm ) +{ + maSrcRangeName = rStrm.ReadUniString(); + + // This 2-byte value equals the length of string that follows, or if 0 it + // indicates that the name has a workbook scope. For now, we only support + // internal defined name with a workbook scope. + sal_uInt16 nFlag; + nFlag = rStrm.ReaduInt16(); + mbSelfRef = (nFlag == 0); + + if (!mbSelfRef) + // External name is not supported yet. + maSrcRangeName.clear(); +} + +void XclImpPivotCache::ReadPivotCacheStream( const XclImpStream& rStrm ) +{ + if( (mnSrcType != EXC_SXVS_SHEET) && (mnSrcType != EXC_SXVS_EXTERN) ) + return; + + ScDocument& rDoc = GetDoc(); + SCCOL nFieldScCol = 0; // column index of source data for next field + SCROW nItemScRow = 0; // row index of source data for current items + SCTAB nScTab = 0; // sheet index of source data + bool bGenerateSource = false; // true = write source data from cache to dummy table + + if( mbSelfRef ) + { + if (maSrcRangeName.isEmpty()) + { + // try to find internal sheet containing the source data + nScTab = GetTabInfo().GetScTabFromXclName( maTabName ); + if( rDoc.HasTable( nScTab ) ) + { + // set sheet index to source range + maSrcRange.aStart.SetTab( nScTab ); + maSrcRange.aEnd.SetTab( nScTab ); + } + else + { + // create dummy sheet for deleted internal sheet + bGenerateSource = true; + } + } + } + else + { + // create dummy sheet for external sheet + bGenerateSource = true; + } + + // create dummy sheet for source data from external or deleted sheet + if( bGenerateSource ) + { + if( rDoc.GetTableCount() >= MAXTABCOUNT ) + // cannot create more sheets -> exit + return; + + nScTab = rDoc.GetTableCount(); + rDoc.MakeTable( nScTab ); + OUStringBuffer aDummyName("DPCache"); + if( maTabName.getLength() > 0 ) + aDummyName.append( '_' ).append( maTabName ); + OUString aName = aDummyName.makeStringAndClear(); + rDoc.CreateValidTabName( aName ); + rDoc.RenameTab( nScTab, aName ); + // set sheet index to source range + maSrcRange.aStart.SetTab( nScTab ); + maSrcRange.aEnd.SetTab( nScTab ); + } + + // open pivot cache storage stream + tools::SvRef<SotStorage> xSvStrg = OpenStorage( EXC_STORAGE_PTCACHE ); + tools::SvRef<SotStorageStream> xSvStrm = OpenStream( xSvStrg, ScfTools::GetHexStr( mnStrmId ) ); + if( !xSvStrm.is() ) + return; + + // create Excel record stream object + XclImpStream aPCStrm( *xSvStrm, GetRoot() ); + aPCStrm.CopyDecrypterFrom( rStrm ); // pivot cache streams are encrypted + + XclImpPCFieldRef xCurrField; // current field for new items + XclImpPCFieldVec aOrigFields; // all standard fields with inline original items + XclImpPCFieldVec aPostpFields; // all standard fields with postponed original items + size_t nPostpIdx = 0; // index to current field with postponed items + bool bLoop = true; // true = continue loop + + while( bLoop && aPCStrm.StartNextRecord() ) + { + switch( aPCStrm.GetRecId() ) + { + case EXC_ID_EOF: + bLoop = false; + break; + + case EXC_ID_SXDB: + aPCStrm >> maPCInfo; + break; + + case EXC_ID_SXFIELD: + { + xCurrField.reset(); + sal_uInt16 nNewFieldIdx = static_cast< sal_uInt16 >( maFields.size() ); + if( nNewFieldIdx < EXC_PC_MAXFIELDCOUNT ) + { + xCurrField = std::make_shared<XclImpPCField>( GetRoot(), *this, nNewFieldIdx ); + maFields.push_back( xCurrField ); + xCurrField->ReadSxfield( aPCStrm ); + if( xCurrField->HasOrigItems() ) + { + if( xCurrField->HasPostponedItems() ) + aPostpFields.push_back( xCurrField ); + else + aOrigFields.push_back( xCurrField ); + // insert field name into generated source data, field remembers its column index + if( bGenerateSource && (nFieldScCol <= rDoc.MaxCol()) ) + xCurrField->WriteFieldNameToSource( nFieldScCol++, nScTab ); + } + // do not read items into invalid/postponed fields + if( !xCurrField->HasInlineItems() ) + xCurrField.reset(); + } + } + break; + + case EXC_ID_SXINDEXLIST: + // read index list and insert all items into generated source data + if( bGenerateSource && (nItemScRow <= rDoc.MaxRow()) && (++nItemScRow <= rDoc.MaxRow()) ) + { + for( const auto& rxOrigField : aOrigFields ) + { + sal_uInt16 nItemIdx = rxOrigField->Has16BitIndexes() ? aPCStrm.ReaduInt16() : aPCStrm.ReaduInt8(); + rxOrigField->WriteOrigItemToSource( nItemScRow, nScTab, nItemIdx ); + } + } + xCurrField.reset(); + break; + + case EXC_ID_SXDOUBLE: + case EXC_ID_SXBOOLEAN: + case EXC_ID_SXERROR: + case EXC_ID_SXINTEGER: + case EXC_ID_SXSTRING: + case EXC_ID_SXDATETIME: + case EXC_ID_SXEMPTY: + if( xCurrField ) // inline items + { + xCurrField->ReadItem( aPCStrm ); + } + else if( !aPostpFields.empty() ) // postponed items + { + // read postponed item + aPostpFields[ nPostpIdx ]->ReadItem( aPCStrm ); + // write item to source + if( bGenerateSource && (nItemScRow <= rDoc.MaxRow()) ) + { + // start new row, if there are only postponed fields + if( aOrigFields.empty() && (nPostpIdx == 0) ) + ++nItemScRow; + if( nItemScRow <= rDoc.MaxRow() ) + aPostpFields[ nPostpIdx ]->WriteLastOrigItemToSource( nItemScRow, nScTab ); + } + // get index of next postponed field + ++nPostpIdx; + if( nPostpIdx >= aPostpFields.size() ) + nPostpIdx = 0; + } + break; + + case EXC_ID_SXNUMGROUP: + if( xCurrField ) + xCurrField->ReadSxnumgroup( aPCStrm ); + break; + + case EXC_ID_SXGROUPINFO: + if( xCurrField ) + xCurrField->ReadSxgroupinfo( aPCStrm ); + break; + + // known but ignored records + case EXC_ID_SXRULE: + case EXC_ID_SXFILT: + case EXC_ID_00F5: + case EXC_ID_SXNAME: + case EXC_ID_SXPAIR: + case EXC_ID_SXFMLA: + case EXC_ID_SXFORMULA: + case EXC_ID_SXDBEX: + case EXC_ID_SXFDBTYPE: + break; + + default: + SAL_WARN("sc.filter", "XclImpPivotCache::ReadPivotCacheStream - unknown record 0x" << std::hex << aPCStrm.GetRecId() ); + } + } + + OSL_ENSURE( maPCInfo.mnTotalFields == maFields.size(), + "XclImpPivotCache::ReadPivotCacheStream - field count mismatch" ); + + if (static_cast<bool>(maPCInfo.mnFlags & EXC_SXDB_SAVEDATA)) + { + SCROW nNewEnd = maSrcRange.aStart.Row() + maPCInfo.mnSrcRecs; + maSrcRange.aEnd.SetRow(nNewEnd); + } + + // set source range for external source data + if( bGenerateSource && (nFieldScCol > 0) ) + { + maSrcRange.aStart.SetCol( 0 ); + maSrcRange.aStart.SetRow( 0 ); + // nFieldScCol points to first unused column + maSrcRange.aEnd.SetCol( nFieldScCol - 1 ); + // nItemScRow points to last used row + maSrcRange.aEnd.SetRow( nItemScRow ); + } +} + +bool XclImpPivotCache::IsRefreshOnLoad() const +{ + return static_cast<bool>(maPCInfo.mnFlags & EXC_SXDB_REFRESH_LOAD); +} + +bool XclImpPivotCache::IsValid() const +{ + if (!maSrcRangeName.isEmpty()) + return true; + + return maSrcRange.IsValid(); +} + +// Pivot table + +XclImpPTItem::XclImpPTItem( const XclImpPCField* pCacheField ) : + mpCacheField( pCacheField ) +{ +} + +const OUString* XclImpPTItem::GetItemName() const +{ + if( mpCacheField ) + if( const XclImpPCItem* pCacheItem = mpCacheField->GetItem( maItemInfo.mnCacheIdx ) ) + //TODO: use XclImpPCItem::ConvertToText(), if all conversions are available + return pCacheItem->IsEmpty() ? nullptr : pCacheItem->GetText(); + return nullptr; +} + +std::pair<bool, OUString> XclImpPTItem::GetItemName(const ScDPSaveDimension& rSaveDim, ScDPObject* pObj, const XclImpRoot& rRoot) const +{ + if(!mpCacheField) + return std::pair<bool, OUString>(false, OUString()); + + const XclImpPCItem* pCacheItem = mpCacheField->GetItem( maItemInfo.mnCacheIdx ); + if(!pCacheItem) + return std::pair<bool, OUString>(false, OUString()); + + OUString sItemName; + if(pCacheItem->GetType() == EXC_PCITEM_TEXT || pCacheItem->GetType() == EXC_PCITEM_ERROR) + { + const OUString* pItemName = pCacheItem->GetText(); + if(!pItemName) + return std::pair<bool, OUString>(false, OUString()); + sItemName = *pItemName; + } + else if (pCacheItem->GetType() == EXC_PCITEM_DOUBLE) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), *pCacheItem->GetDouble()); + } + else if (pCacheItem->GetType() == EXC_PCITEM_INTEGER) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), static_cast<double>(*pCacheItem->GetInteger())); + } + else if (pCacheItem->GetType() == EXC_PCITEM_BOOL) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), static_cast<double>(*pCacheItem->GetBool())); + } + else if (pCacheItem->GetType() == EXC_PCITEM_DATETIME) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), rRoot.GetDoubleFromDateTime(*pCacheItem->GetDateTime())); + } + else if (pCacheItem->GetType() == EXC_PCITEM_EMPTY) + { + // sItemName is an empty string + } + else // EXC_PCITEM_INVALID + return std::pair<bool, OUString>(false, OUString()); + + return std::pair<bool, OUString>(true, sItemName); +} + +void XclImpPTItem::ReadSxvi( XclImpStream& rStrm ) +{ + rStrm >> maItemInfo; +} + +void XclImpPTItem::ConvertItem( ScDPSaveDimension& rSaveDim, ScDPObject* pObj, const XclImpRoot& rRoot ) const +{ + // Find member and set properties + std::pair<bool, OUString> aReturnedName = GetItemName(rSaveDim, pObj, rRoot); + if(aReturnedName.first) + { + ScDPSaveMember* pMember = rSaveDim.GetExistingMemberByName(aReturnedName.second); + if(pMember) + { + pMember->SetIsVisible( !::get_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN ) ); + pMember->SetShowDetails( !::get_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL ) ); + if (maItemInfo.HasVisName()) + pMember->SetLayoutName(*maItemInfo.GetVisName()); + } + } +} + +XclImpPTField::XclImpPTField( const XclImpPivotTable& rPTable, sal_uInt16 nCacheIdx ) : + mrPTable( rPTable ) +{ + maFieldInfo.mnCacheIdx = nCacheIdx; +} + +// general field/item access -------------------------------------------------- + +const XclImpPCField* XclImpPTField::GetCacheField() const +{ + XclImpPivotCacheRef xPCache = mrPTable.GetPivotCache(); + return xPCache ? xPCache->GetField( maFieldInfo.mnCacheIdx ) : nullptr; +} + +OUString XclImpPTField::GetFieldName() const +{ + const XclImpPCField* pField = GetCacheField(); + return pField ? pField->GetFieldName( mrPTable.GetVisFieldNames() ) : OUString(); +} + +OUString XclImpPTField::GetVisFieldName() const +{ + const OUString* pVisName = maFieldInfo.GetVisName(); + return pVisName ? *pVisName : OUString(); +} + +const XclImpPTItem* XclImpPTField::GetItem( sal_uInt16 nItemIdx ) const +{ + return (nItemIdx < maItems.size()) ? maItems[ nItemIdx ].get() : nullptr; +} + +const OUString* XclImpPTField::GetItemName( sal_uInt16 nItemIdx ) const +{ + const XclImpPTItem* pItem = GetItem( nItemIdx ); + return pItem ? pItem->GetItemName() : nullptr; +} + +// records -------------------------------------------------------------------- + +void XclImpPTField::ReadSxvd( XclImpStream& rStrm ) +{ + rStrm >> maFieldInfo; +} + +void XclImpPTField::ReadSxvdex( XclImpStream& rStrm ) +{ + rStrm >> maFieldExtInfo; +} + +void XclImpPTField::ReadSxvi( XclImpStream& rStrm ) +{ + XclImpPTItemRef xItem = std::make_shared<XclImpPTItem>( GetCacheField() ); + maItems.push_back( xItem ); + xItem->ReadSxvi( rStrm ); +} + +// row/column fields ---------------------------------------------------------- + +void XclImpPTField::ConvertRowColField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_ROWCOL, "XclImpPTField::ConvertRowColField - no row/column field" ); + // special data orientation field? + if( maFieldInfo.mnCacheIdx == EXC_SXIVD_DATA ) + rSaveData.GetDataLayoutDimension()->SetOrientation( maFieldInfo.GetApiOrient( EXC_SXVD_AXIS_ROWCOL ) ); + else + ConvertRCPField( rSaveData ); +} + +// page fields ---------------------------------------------------------------- + +void XclImpPTField::SetPageFieldInfo( const XclPTPageFieldInfo& rPageInfo ) +{ + maPageInfo = rPageInfo; +} + +void XclImpPTField::ConvertPageField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_PAGE, "XclImpPTField::ConvertPageField - no page field" ); + ConvertRCPField( rSaveData ); +} + +// hidden fields -------------------------------------------------------------- + +void XclImpPTField::ConvertHiddenField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( (maFieldInfo.mnAxes & EXC_SXVD_AXIS_ROWCOLPAGE) == 0, "XclImpPTField::ConvertHiddenField - field not hidden" ); + ConvertRCPField( rSaveData ); +} + +// data fields ---------------------------------------------------------------- + +bool XclImpPTField::HasDataFieldInfo() const +{ + return !maDataInfoVector.empty(); +} + +void XclImpPTField::AddDataFieldInfo( const XclPTDataFieldInfo& rDataInfo ) +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_DATA, "XclImpPTField::AddDataFieldInfo - no data field" ); + maDataInfoVector.push_back( rDataInfo ); +} + +void XclImpPTField::ConvertDataField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_DATA, "XclImpPTField::ConvertDataField - no data field" ); + OSL_ENSURE( !maDataInfoVector.empty(), "XclImpPTField::ConvertDataField - no data field info" ); + if (maDataInfoVector.empty()) + return; + + OUString aFieldName = GetFieldName(); + if (aFieldName.isEmpty()) + return; + + ScDPSaveDimension* pSaveDim = rSaveData.GetNewDimensionByName(aFieldName); + if (!pSaveDim) + { + SAL_WARN("sc.filter","XclImpPTField::ConvertDataField - field name not found: " << aFieldName); + return; + } + + auto aIt = maDataInfoVector.begin(), aEnd = maDataInfoVector.end(); + + ConvertDataField( *pSaveDim, *aIt ); + + // multiple data fields -> clone dimension + for( ++aIt; aIt != aEnd; ++aIt ) + { + ScDPSaveDimension& rDupDim = rSaveData.DuplicateDimension( *pSaveDim ); + ConvertDataFieldInfo( rDupDim, *aIt ); + } +} + +// private -------------------------------------------------------------------- + +/** + * Convert Excel-encoded subtotal name to a Calc-encoded one. + */ +static OUString lcl_convertExcelSubtotalName(const OUString& rName) +{ + OUStringBuffer aBuf; + const sal_Unicode* p = rName.getStr(); + sal_Int32 n = rName.getLength(); + for (sal_Int32 i = 0; i < n; ++i) + { + const sal_Unicode c = p[i]; + if (c == '\\') + { + aBuf.append(c); + aBuf.append(c); + } + else + aBuf.append(c); + } + return aBuf.makeStringAndClear(); +} + +void XclImpPTField::ConvertRCPField( ScDPSaveData& rSaveData ) const +{ + const OUString& rFieldName = GetFieldName(); + if( rFieldName.isEmpty() ) + return; + + const XclImpPCField* pCacheField = GetCacheField(); + if( !pCacheField || !pCacheField->IsSupportedField() ) + return; + + ScDPSaveDimension* pTest = rSaveData.GetNewDimensionByName(rFieldName); + if (!pTest) + return; + + ScDPSaveDimension& rSaveDim = *pTest; + + // orientation + rSaveDim.SetOrientation( maFieldInfo.GetApiOrient( EXC_SXVD_AXIS_ROWCOLPAGE ) ); + + // visible name + if (const OUString* pVisName = maFieldInfo.GetVisName()) + if (!pVisName->isEmpty()) + rSaveDim.SetLayoutName( *pVisName ); + + // subtotal function(s) + XclPTSubtotalVec aSubtotalVec; + maFieldInfo.GetSubtotals( aSubtotalVec ); + if( !aSubtotalVec.empty() ) + rSaveDim.SetSubTotals( std::move(aSubtotalVec) ); + + // sorting + DataPilotFieldSortInfo aSortInfo; + aSortInfo.Field = mrPTable.GetDataFieldName( maFieldExtInfo.mnSortField ); + aSortInfo.IsAscending = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC ); + aSortInfo.Mode = maFieldExtInfo.GetApiSortMode(); + rSaveDim.SetSortInfo( &aSortInfo ); + + // auto show + DataPilotFieldAutoShowInfo aShowInfo; + aShowInfo.IsEnabled = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW ); + aShowInfo.ShowItemsMode = maFieldExtInfo.GetApiAutoShowMode(); + aShowInfo.ItemCount = maFieldExtInfo.GetApiAutoShowCount(); + aShowInfo.DataField = mrPTable.GetDataFieldName( maFieldExtInfo.mnShowField ); + rSaveDim.SetAutoShowInfo( &aShowInfo ); + + // layout + DataPilotFieldLayoutInfo aLayoutInfo; + aLayoutInfo.LayoutMode = maFieldExtInfo.GetApiLayoutMode(); + aLayoutInfo.AddEmptyLines = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK ); + rSaveDim.SetLayoutInfo( &aLayoutInfo ); + + // grouping info + pCacheField->ConvertGroupField( rSaveData, mrPTable.GetVisFieldNames() ); + + // custom subtotal name + if (maFieldExtInfo.mpFieldTotalName) + { + OUString aSubName = lcl_convertExcelSubtotalName(*maFieldExtInfo.mpFieldTotalName); + rSaveDim.SetSubtotalName(aSubName); + } +} + +void XclImpPTField::ConvertFieldInfo( const ScDPSaveData& rSaveData, ScDPObject* pObj, const XclImpRoot& rRoot, bool bPageField ) const +{ + const OUString& rFieldName = GetFieldName(); + if( rFieldName.isEmpty() ) + return; + + const XclImpPCField* pCacheField = GetCacheField(); + if( !pCacheField || !pCacheField->IsSupportedField() ) + return; + + ScDPSaveDimension* pSaveDim = rSaveData.GetExistingDimensionByName(rFieldName); + if (!pSaveDim) + return; + + pSaveDim->SetShowEmpty( ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL ) ); + for( const auto& rxItem : maItems ) + rxItem->ConvertItem( *pSaveDim, pObj, rRoot ); + + if(bPageField && maPageInfo.mnSelItem != EXC_SXPI_ALLITEMS) + { + const XclImpPTItem* pItem = GetItem( maPageInfo.mnSelItem ); + if(pItem) + { + std::pair<bool, OUString> aReturnedName = pItem->GetItemName(*pSaveDim, pObj, rRoot); + if(aReturnedName.first) + pSaveDim->SetCurrentPage(&aReturnedName.second); + } + } +} + +void XclImpPTField::ConvertDataField( ScDPSaveDimension& rSaveDim, const XclPTDataFieldInfo& rDataInfo ) const +{ + // orientation + rSaveDim.SetOrientation( DataPilotFieldOrientation_DATA ); + // extended data field info + ConvertDataFieldInfo( rSaveDim, rDataInfo ); +} + +void XclImpPTField::ConvertDataFieldInfo( ScDPSaveDimension& rSaveDim, const XclPTDataFieldInfo& rDataInfo ) const +{ + // visible name + const OUString* pVisName = rDataInfo.GetVisName(); + if (pVisName && !pVisName->isEmpty()) + rSaveDim.SetLayoutName(*pVisName); + + // aggregation function + rSaveDim.SetFunction( rDataInfo.GetApiAggFunc() ); + + // result field reference + sal_Int32 nRefType = rDataInfo.GetApiRefType(); + DataPilotFieldReference aFieldRef; + aFieldRef.ReferenceType = nRefType; + const XclImpPTField* pRefField = mrPTable.GetField(rDataInfo.mnRefField); + if (pRefField) + { + aFieldRef.ReferenceField = pRefField->GetFieldName(); + aFieldRef.ReferenceItemType = rDataInfo.GetApiRefItemType(); + if (aFieldRef.ReferenceItemType == sheet::DataPilotFieldReferenceItemType::NAMED) + { + const OUString* pRefItemName = pRefField->GetItemName(rDataInfo.mnRefItem); + if (pRefItemName) + aFieldRef.ReferenceItemName = *pRefItemName; + } + } + + rSaveDim.SetReferenceValue(&aFieldRef); +} + +XclImpPivotTable::XclImpPivotTable( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maDataOrientField( *this, EXC_SXIVD_DATA ), + mpDPObj(nullptr) +{ +} + +XclImpPivotTable::~XclImpPivotTable() +{ +} + +// cache/field access, misc. -------------------------------------------------- + +sal_uInt16 XclImpPivotTable::GetFieldCount() const +{ + return static_cast< sal_uInt16 >( maFields.size() ); +} + +const XclImpPTField* XclImpPivotTable::GetField( sal_uInt16 nFieldIdx ) const +{ + return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField : + ((nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr); +} + +XclImpPTField* XclImpPivotTable::GetFieldAcc( sal_uInt16 nFieldIdx ) +{ + // do not return maDataOrientField + return (nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr; +} + +const XclImpPTField* XclImpPivotTable::GetDataField( sal_uInt16 nDataFieldIdx ) const +{ + if( nDataFieldIdx < maOrigDataFields.size() ) + return GetField( maOrigDataFields[ nDataFieldIdx ] ); + return nullptr; +} + +OUString XclImpPivotTable::GetDataFieldName( sal_uInt16 nDataFieldIdx ) const +{ + if( const XclImpPTField* pField = GetDataField( nDataFieldIdx ) ) + return pField->GetFieldName(); + return OUString(); +} + +// records -------------------------------------------------------------------- + +void XclImpPivotTable::ReadSxview( XclImpStream& rStrm ) +{ + rStrm >> maPTInfo; + + GetAddressConverter().ConvertRange( + maOutScRange, maPTInfo.maOutXclRange, GetCurrScTab(), GetCurrScTab(), true ); + + mxPCache = GetPivotTableManager().GetPivotCache( maPTInfo.mnCacheIdx ); + mxCurrField.reset(); +} + +void XclImpPivotTable::ReadSxvd( XclImpStream& rStrm ) +{ + sal_uInt16 nFieldCount = GetFieldCount(); + if( nFieldCount < EXC_PT_MAXFIELDCOUNT ) + { + // cache index for the field is equal to the SXVD record index + mxCurrField = std::make_shared<XclImpPTField>( *this, nFieldCount ); + maFields.push_back( mxCurrField ); + mxCurrField->ReadSxvd( rStrm ); + // add visible name of new field to list of visible names + maVisFieldNames.push_back( mxCurrField->GetVisFieldName() ); + OSL_ENSURE( maFields.size() == maVisFieldNames.size(), + "XclImpPivotTable::ReadSxvd - wrong size of visible name array" ); + } + else + mxCurrField.reset(); +} + +void XclImpPivotTable::ReadSxvi( XclImpStream& rStrm ) +{ + if( mxCurrField ) + mxCurrField->ReadSxvi( rStrm ); +} + +void XclImpPivotTable::ReadSxvdex( XclImpStream& rStrm ) +{ + if( mxCurrField ) + mxCurrField->ReadSxvdex( rStrm ); +} + +void XclImpPivotTable::ReadSxivd( XclImpStream& rStrm ) +{ + mxCurrField.reset(); + + // find the index vector to fill (row SXIVD doesn't exist without row fields) + ScfUInt16Vec* pFieldVec = nullptr; + if( maRowFields.empty() && (maPTInfo.mnRowFields > 0) ) + pFieldVec = &maRowFields; + else if( maColFields.empty() && (maPTInfo.mnColFields > 0) ) + pFieldVec = &maColFields; + + // fill the vector from record data + if( !pFieldVec ) + return; + + sal_uInt16 nSize = ulimit_cast< sal_uInt16 >( rStrm.GetRecSize() / 2, EXC_PT_MAXROWCOLCOUNT ); + pFieldVec->reserve( nSize ); + for( sal_uInt16 nIdx = 0; nIdx < nSize; ++nIdx ) + { + sal_uInt16 nFieldIdx; + nFieldIdx = rStrm.ReaduInt16(); + pFieldVec->push_back( nFieldIdx ); + + // set orientation at special data orientation field + if( nFieldIdx == EXC_SXIVD_DATA ) + { + sal_uInt16 nAxis = (pFieldVec == &maRowFields) ? EXC_SXVD_AXIS_ROW : EXC_SXVD_AXIS_COL; + maDataOrientField.SetAxes( nAxis ); + } + } +} + +void XclImpPivotTable::ReadSxpi( XclImpStream& rStrm ) +{ + mxCurrField.reset(); + + sal_uInt16 nSize = ulimit_cast< sal_uInt16 >( rStrm.GetRecSize() / 6 ); + for( sal_uInt16 nEntry = 0; nEntry < nSize; ++nEntry ) + { + XclPTPageFieldInfo aPageInfo; + rStrm >> aPageInfo; + if( XclImpPTField* pField = GetFieldAcc( aPageInfo.mnField ) ) + { + maPageFields.push_back( aPageInfo.mnField ); + pField->SetPageFieldInfo( aPageInfo ); + } + GetCurrSheetDrawing().SetSkipObj( aPageInfo.mnObjId ); + } +} + +void XclImpPivotTable::ReadSxdi( XclImpStream& rStrm ) +{ + mxCurrField.reset(); + + XclPTDataFieldInfo aDataInfo; + rStrm >> aDataInfo; + if( XclImpPTField* pField = GetFieldAcc( aDataInfo.mnField ) ) + { + maOrigDataFields.push_back( aDataInfo.mnField ); + // DataPilot does not support double data fields -> add first appearance to index list only + if( !pField->HasDataFieldInfo() ) + maFiltDataFields.push_back( aDataInfo.mnField ); + pField->AddDataFieldInfo( aDataInfo ); + } +} + +void XclImpPivotTable::ReadSxex( XclImpStream& rStrm ) +{ + rStrm >> maPTExtInfo; +} + +void XclImpPivotTable::ReadSxViewEx9( XclImpStream& rStrm ) +{ + rStrm >> maPTViewEx9Info; +} + +void XclImpPivotTable::ReadSxAddl( XclImpStream& rStrm ) +{ + rStrm >> maPTAddlInfo; +} + +void XclImpPivotTable::Convert() +{ + if( !mxPCache || !mxPCache->IsValid() ) + return; + + if (utl::ConfigManager::IsFuzzing()) //just too slow + return; + + ScDPSaveData aSaveData; + + // *** global settings *** + + aSaveData.SetRowGrand( ::get_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND ) ); + aSaveData.SetColumnGrand( ::get_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND ) ); + aSaveData.SetFilterButton( false ); + aSaveData.SetDrillDown( ::get_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN ) ); + aSaveData.SetIgnoreEmptyRows( false ); + aSaveData.SetRepeatIfEmpty( false ); + + // *** fields *** + + // row fields + for( const auto& rRowField : maRowFields ) + if( const XclImpPTField* pField = GetField( rRowField ) ) + pField->ConvertRowColField( aSaveData ); + + // column fields + for( const auto& rColField : maColFields ) + if( const XclImpPTField* pField = GetField( rColField ) ) + pField->ConvertRowColField( aSaveData ); + + // page fields + for( const auto& rPageField : maPageFields ) + if( const XclImpPTField* pField = GetField( rPageField ) ) + pField->ConvertPageField( aSaveData ); + + // We need to import hidden fields because hidden fields may contain + // special settings for subtotals (aggregation function, filters, custom + // name etc.) and members (hidden, custom name etc.). + + // hidden fields + for( sal_uInt16 nField = 0, nCount = GetFieldCount(); nField < nCount; ++nField ) + if( const XclImpPTField* pField = GetField( nField ) ) + if (!pField->GetAxes()) + pField->ConvertHiddenField( aSaveData ); + + // data fields + for( const auto& rFiltDataField : maFiltDataFields ) + if( const XclImpPTField* pField = GetField( rFiltDataField ) ) + pField->ConvertDataField( aSaveData ); + + // *** insert into Calc document *** + + // create source descriptor + ScSheetSourceDesc aDesc(&GetDoc()); + const OUString& rSrcName = mxPCache->GetSourceRangeName(); + if (!rSrcName.isEmpty()) + // Range name is the data source. + aDesc.SetRangeName(rSrcName); + else + // Normal cell range. + aDesc.SetSourceRange(mxPCache->GetSourceRange()); + + // adjust output range to include the page fields + ScRange aOutRange( maOutScRange ); + if( !maPageFields.empty() ) + { + SCROW nDecRows = ::std::min< SCROW >( aOutRange.aStart.Row(), maPageFields.size() + 1 ); + aOutRange.aStart.IncRow( -nDecRows ); + } + + // create the DataPilot + std::unique_ptr<ScDPObject> pDPObj(new ScDPObject( &GetDoc() )); + pDPObj->SetName( maPTInfo.maTableName ); + if (!maPTInfo.maDataName.isEmpty()) + aSaveData.GetDataLayoutDimension()->SetLayoutName(maPTInfo.maDataName); + + if (!maPTViewEx9Info.maGrandTotalName.isEmpty()) + aSaveData.SetGrandTotalName(maPTViewEx9Info.maGrandTotalName); + + pDPObj->SetSaveData( aSaveData ); + pDPObj->SetSheetDesc( aDesc ); + pDPObj->SetOutRange( aOutRange ); + pDPObj->SetHeaderLayout( maPTViewEx9Info.mnGridLayout == 0 ); + + mpDPObj = GetDoc().GetDPCollection()->InsertNewTable(std::move(pDPObj)); + + ApplyFieldInfo(); + ApplyMergeFlags(aOutRange, aSaveData); +} + +void XclImpPivotTable::MaybeRefresh() +{ + if (mpDPObj && mxPCache->IsRefreshOnLoad()) + { + // 'refresh table on load' flag is set. Refresh the table now. Some + // Excel files contain partial table output when this flag is set. + ScRange aOutRange = mpDPObj->GetOutRange(); + mpDPObj->Output(aOutRange.aStart); + } +} + +void XclImpPivotTable::ApplyMergeFlags(const ScRange& rOutRange, const ScDPSaveData& rSaveData) +{ + // Apply merge flags for various datapilot controls. + + ScDPOutputGeometry aGeometry(rOutRange, false); + aGeometry.setColumnFieldCount(maPTInfo.mnColFields); + aGeometry.setPageFieldCount(maPTInfo.mnPageFields); + aGeometry.setDataFieldCount(maPTInfo.mnDataFields); + aGeometry.setRowFieldCount(maPTInfo.mnRowFields); + + // Make sure we set headerlayout when input file has additional raw header + if(maPTInfo.mnColFields == 0) + { + mpDPObj->SetHeaderLayout( maPTInfo.mnFirstHeadRow - 2 == static_cast<sal_uInt16>(aGeometry.getRowFieldHeaderRow()) ); + } + aGeometry.setHeaderLayout(mpDPObj->GetHeaderLayout()); + aGeometry.setCompactMode(maPTAddlInfo.mbCompactMode); + + ScDocument& rDoc = GetDoc(); + + vector<const ScDPSaveDimension*> aFieldDims; + vector<ScAddress> aFieldBtns; + + aGeometry.getPageFieldPositions(aFieldBtns); + for (const auto& rFieldBtn : aFieldBtns) + { + rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), ScMF::Button); + + ScMF nMFlag = ScMF::ButtonPopup; + OUString aName = rDoc.GetString(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab()); + if (rSaveData.HasInvisibleMember(aName)) + nMFlag |= ScMF::HiddenMember; + + rDoc.ApplyFlagsTab(rFieldBtn.Col()+1, rFieldBtn.Row(), rFieldBtn.Col()+1, rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag); + } + + aGeometry.getColumnFieldPositions(aFieldBtns); + rSaveData.GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aFieldDims); + if (aFieldBtns.size() == aFieldDims.size()) + { + vector<const ScDPSaveDimension*>::const_iterator itDim = aFieldDims.begin(); + for (const auto& rFieldBtn : aFieldBtns) + { + ScMF nMFlag = ScMF::Button; + const ScDPSaveDimension* pDim = *itDim; + if (pDim->HasInvisibleMember()) + nMFlag |= ScMF::HiddenMember; + if (!pDim->IsDataLayout()) + nMFlag |= ScMF::ButtonPopup; + rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag); + ++itDim; + } + } + + aGeometry.getRowFieldPositions(aFieldBtns); + rSaveData.GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aFieldDims); + if (!((aFieldBtns.size() == aFieldDims.size()) || (maPTAddlInfo.mbCompactMode && aFieldBtns.size() == 1))) + return; + + vector<const ScDPSaveDimension*>::const_iterator itDim = aFieldDims.begin(); + for (const auto& rFieldBtn : aFieldBtns) + { + ScMF nMFlag = ScMF::Button; + const ScDPSaveDimension* pDim = itDim != aFieldDims.end() ? *itDim++ : nullptr; + if (pDim && pDim->HasInvisibleMember()) + nMFlag |= ScMF::HiddenMember; + if (!pDim || !pDim->IsDataLayout()) + nMFlag |= ScMF::ButtonPopup; + rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag); + } +} + + +void XclImpPivotTable::ApplyFieldInfo() +{ + mpDPObj->BuildAllDimensionMembers(); + ScDPSaveData& rSaveData = *mpDPObj->GetSaveData(); + + // row fields + for( const auto& rRowField : maRowFields ) + if( const XclImpPTField* pField = GetField( rRowField ) ) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this ); + + // column fields + for( const auto& rColField : maColFields ) + if( const XclImpPTField* pField = GetField( rColField ) ) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this ); + + // page fields + for( const auto& rPageField : maPageFields ) + if( const XclImpPTField* pField = GetField( rPageField ) ) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this, true ); + + // hidden fields + for( sal_uInt16 nField = 0, nCount = GetFieldCount(); nField < nCount; ++nField ) + if( const XclImpPTField* pField = GetField( nField ) ) + if (!pField->GetAxes()) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this ); +} + +XclImpPivotTableManager::XclImpPivotTableManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +XclImpPivotTableManager::~XclImpPivotTableManager() +{ +} + +// pivot cache records -------------------------------------------------------- + +XclImpPivotCacheRef XclImpPivotTableManager::GetPivotCache( sal_uInt16 nCacheIdx ) +{ + XclImpPivotCacheRef xPCache; + if( nCacheIdx < maPCaches.size() ) + xPCache = maPCaches[ nCacheIdx ]; + return xPCache; +} + +void XclImpPivotTableManager::ReadSxidstm( XclImpStream& rStrm ) +{ + XclImpPivotCacheRef xPCache = std::make_shared<XclImpPivotCache>( GetRoot() ); + maPCaches.push_back( xPCache ); + xPCache->ReadSxidstm( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvs( XclImpStream& rStrm ) +{ + if( !maPCaches.empty() ) + maPCaches.back()->ReadSxvs( rStrm ); +} + +void XclImpPivotTableManager::ReadDconref( XclImpStream& rStrm ) +{ + if( !maPCaches.empty() ) + maPCaches.back()->ReadDconref( rStrm ); +} + +void XclImpPivotTableManager::ReadDConName( XclImpStream& rStrm ) +{ + if( !maPCaches.empty() ) + maPCaches.back()->ReadDConName( rStrm ); +} + +// pivot table records -------------------------------------------------------- + +void XclImpPivotTableManager::ReadSxview( XclImpStream& rStrm ) +{ + XclImpPivotTableRef xPTable = std::make_shared<XclImpPivotTable>( GetRoot() ); + maPTables.push_back( xPTable ); + xPTable->ReadSxview( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvd( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxvd( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvdex( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxvdex( rStrm ); +} + +void XclImpPivotTableManager::ReadSxivd( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxivd( rStrm ); +} + +void XclImpPivotTableManager::ReadSxpi( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxpi( rStrm ); +} + +void XclImpPivotTableManager::ReadSxdi( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxdi( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvi( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxvi( rStrm ); +} + +void XclImpPivotTableManager::ReadSxex( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxex( rStrm ); +} + +void XclImpPivotTableManager::ReadSxViewEx9( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxViewEx9( rStrm ); +} + +void XclImpPivotTableManager::ReadSxAddl( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxAddl( rStrm ); +} + +void XclImpPivotTableManager::ReadPivotCaches( const XclImpStream& rStrm ) +{ + for( auto& rxPCache : maPCaches ) + rxPCache->ReadPivotCacheStream( rStrm ); +} + +void XclImpPivotTableManager::ConvertPivotTables() +{ + for( auto& rxPTable : maPTables ) + rxPTable->Convert(); +} + +void XclImpPivotTableManager::MaybeRefreshPivotTables() +{ + for( auto& rxPTable : maPTables ) + rxPTable->MaybeRefresh(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiroot.cxx b/sc/source/filter/excel/xiroot.cxx new file mode 100644 index 000000000..af04654de --- /dev/null +++ b/sc/source/filter/excel/xiroot.cxx @@ -0,0 +1,298 @@ +/* -*- 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 <xiroot.hxx> +#include <addincol.hxx> +#include <colrowst.hxx> +#include <document.hxx> +#include <formel.hxx> +#include <scextopt.hxx> +#include <xihelper.hxx> +#include <xiformula.hxx> +#include <xilink.hxx> +#include <xiname.hxx> +#include <xistyle.hxx> +#include <xicontent.hxx> +#include <xiescher.hxx> +#include <xipivot.hxx> +#include <xipage.hxx> +#include <xiview.hxx> + +#include <root.hxx> +#include <excimp8.hxx> +#include <documentimport.hxx> + +// Global data ================================================================ + +XclImpRootData::XclImpRootData( XclBiff eBiff, SfxMedium& rMedium, + const tools::SvRef<SotStorage>& xRootStrg, ScDocument& rDoc, rtl_TextEncoding eTextEnc ) : + XclRootData( eBiff, rMedium, xRootStrg, rDoc, eTextEnc, false ), + mxDocImport(std::make_shared<ScDocumentImport>(rDoc)), + mbHasCodePage( false ), + mbHasBasic( false ) +{ +} + +XclImpRootData::~XclImpRootData() +{ +} + +XclImpRoot::XclImpRoot( XclImpRootData& rImpRootData ) : + XclRoot( rImpRootData ), + mrImpData( rImpRootData ) +{ + mrImpData.mxAddrConv = std::make_shared<XclImpAddressConverter>( GetRoot() ); + mrImpData.mxFmlaComp = std::make_shared<XclImpFormulaCompiler>( GetRoot() ); + mrImpData.mxPalette = std::make_shared<XclImpPalette>( GetRoot() ); + mrImpData.mxFontBfr = std::make_shared<XclImpFontBuffer>( GetRoot() ); + mrImpData.mxNumFmtBfr = std::make_shared<XclImpNumFmtBuffer>( GetRoot() ); + mrImpData.mpXFBfr = std::make_shared<XclImpXFBuffer>( GetRoot() ); + mrImpData.mxXFRangeBfr = std::make_shared<XclImpXFRangeBuffer>( GetRoot() ); + mrImpData.mxTabInfo = std::make_shared<XclImpTabInfo>(); + mrImpData.mxNameMgr = std::make_shared<XclImpNameManager>( GetRoot() ); + mrImpData.mxObjMgr = std::make_shared<XclImpObjectManager>( GetRoot() ); + + if( GetBiff() == EXC_BIFF8 ) + { + mrImpData.mxLinkMgr = std::make_shared<XclImpLinkManager>( GetRoot() ); + mrImpData.mxSst = std::make_shared<XclImpSst>( GetRoot() ); + mrImpData.mxCondFmtMgr = std::make_shared<XclImpCondFormatManager>( GetRoot() ); + mrImpData.mxValidMgr = std::make_shared<XclImpValidationManager>( GetRoot() ); + // TODO still in old RootData (deleted by RootData) + GetOldRoot().pAutoFilterBuffer.reset( new XclImpAutoFilterBuffer ); + mrImpData.mxWebQueryBfr = std::make_shared<XclImpWebQueryBuffer>( GetRoot() ); + mrImpData.mxPTableMgr = std::make_shared<XclImpPivotTableManager>( GetRoot() ); + mrImpData.mxTabProtect = std::make_shared<XclImpSheetProtectBuffer>( GetRoot() ); + mrImpData.mxDocProtect = std::make_shared<XclImpDocProtectBuffer>( GetRoot() ); + } + + mrImpData.mxPageSett = std::make_shared<XclImpPageSettings>( GetRoot() ); + mrImpData.mxDocViewSett = std::make_shared<XclImpDocViewSettings>( GetRoot() ); + mrImpData.mxTabViewSett = std::make_shared<XclImpTabViewSettings>( GetRoot() ); + mrImpData.mpPrintRanges = std::make_unique<ScRangeListTabs>( GetRoot() ); + mrImpData.mpPrintTitles = std::make_unique<ScRangeListTabs>( GetRoot() ); +} + +void XclImpRoot::SetCodePage( sal_uInt16 nCodePage ) +{ + SetTextEncoding( XclTools::GetTextEncoding( nCodePage ) ); + mrImpData.mbHasCodePage = true; +} + +void XclImpRoot::InitializeTable( SCTAB nScTab ) +{ + if( GetBiff() <= EXC_BIFF4 ) + { + GetPalette().Initialize(); + GetFontBuffer().Initialize(); + GetNumFmtBuffer().Initialize(); + GetXFBuffer().Initialize(); + } + GetXFRangeBuffer().Initialize(); + GetPageSettings().Initialize(); + GetTabViewSettings().Initialize(); + // delete the automatically generated codename + GetDoc().SetCodeName( nScTab, OUString() ); +} + +void XclImpRoot::FinalizeTable() +{ + GetXFRangeBuffer().Finalize(); + GetOldRoot().pColRowBuff->Convert( GetCurrScTab() ); + GetPageSettings().Finalize(); + GetTabViewSettings().Finalize(); +} + +XclImpAddressConverter& XclImpRoot::GetAddressConverter() const +{ + return *mrImpData.mxAddrConv; +} + +XclImpFormulaCompiler& XclImpRoot::GetFormulaCompiler() const +{ + return *mrImpData.mxFmlaComp; +} + +ExcelToSc& XclImpRoot::GetOldFmlaConverter() const +{ + // TODO still in old RootData + return *GetOldRoot().pFmlaConverter; +} + +XclImpSst& XclImpRoot::GetSst() const +{ + assert(mrImpData.mxSst && "XclImpRoot::GetSst - invalid call, wrong BIFF"); + return *mrImpData.mxSst; +} + +XclImpPalette& XclImpRoot::GetPalette() const +{ + return *mrImpData.mxPalette; +} + +XclImpFontBuffer& XclImpRoot::GetFontBuffer() const +{ + return *mrImpData.mxFontBfr; +} + +XclImpNumFmtBuffer& XclImpRoot::GetNumFmtBuffer() const +{ + return *mrImpData.mxNumFmtBfr; +} + +XclImpXFBuffer& XclImpRoot::GetXFBuffer() const +{ + return *mrImpData.mpXFBfr; +} + +XclImpXFRangeBuffer& XclImpRoot::GetXFRangeBuffer() const +{ + return *mrImpData.mxXFRangeBfr; +} + +ScRangeListTabs& XclImpRoot::GetPrintAreaBuffer() const +{ + return *mrImpData.mpPrintRanges; +} + +ScRangeListTabs& XclImpRoot::GetTitleAreaBuffer() const +{ + return *mrImpData.mpPrintTitles; +} + +XclImpTabInfo& XclImpRoot::GetTabInfo() const +{ + return *mrImpData.mxTabInfo; +} + +XclImpNameManager& XclImpRoot::GetNameManager() const +{ + return *mrImpData.mxNameMgr; +} + +XclImpLinkManager& XclImpRoot::GetLinkManager() const +{ + assert(mrImpData.mxLinkMgr && "XclImpRoot::GetLinkManager - invalid call, wrong BIFF"); + return *mrImpData.mxLinkMgr; +} + +XclImpObjectManager& XclImpRoot::GetObjectManager() const +{ + return *mrImpData.mxObjMgr; +} + +XclImpSheetDrawing& XclImpRoot::GetCurrSheetDrawing() const +{ + OSL_ENSURE( !IsInGlobals(), "XclImpRoot::GetCurrSheetDrawing - must not be called from workbook globals" ); + return mrImpData.mxObjMgr->GetSheetDrawing( GetCurrScTab() ); +} + +XclImpCondFormatManager& XclImpRoot::GetCondFormatManager() const +{ + assert(mrImpData.mxCondFmtMgr && "XclImpRoot::GetCondFormatManager - invalid call, wrong BIFF"); + return *mrImpData.mxCondFmtMgr; +} + +XclImpValidationManager& XclImpRoot::GetValidationManager() const +{ + assert(mrImpData.mxValidMgr && "XclImpRoot::GetValidationManager - invalid call, wrong BIFF"); + return *mrImpData.mxValidMgr; +} + +XclImpAutoFilterBuffer& XclImpRoot::GetFilterManager() const +{ + // TODO still in old RootData + assert(GetOldRoot().pAutoFilterBuffer && "XclImpRoot::GetFilterManager - invalid call, wrong BIFF"); + return *GetOldRoot().pAutoFilterBuffer; +} + +XclImpWebQueryBuffer& XclImpRoot::GetWebQueryBuffer() const +{ + assert(mrImpData.mxWebQueryBfr && "XclImpRoot::GetWebQueryBuffer - invalid call, wrong BIFF"); + return *mrImpData.mxWebQueryBfr; +} + +XclImpPivotTableManager& XclImpRoot::GetPivotTableManager() const +{ + assert(mrImpData.mxPTableMgr && "XclImpRoot::GetPivotTableManager - invalid call, wrong BIFF"); + return *mrImpData.mxPTableMgr; +} + +XclImpSheetProtectBuffer& XclImpRoot::GetSheetProtectBuffer() const +{ + assert(mrImpData.mxTabProtect && "XclImpRoot::GetSheetProtectBuffer - invalid call, wrong BIFF"); + return *mrImpData.mxTabProtect; +} + +XclImpDocProtectBuffer& XclImpRoot::GetDocProtectBuffer() const +{ + assert(mrImpData.mxDocProtect && "XclImpRoot::GetDocProtectBuffer - invalid call, wrong BIFF"); + return *mrImpData.mxDocProtect; +} + +XclImpPageSettings& XclImpRoot::GetPageSettings() const +{ + return *mrImpData.mxPageSett; +} + +XclImpDocViewSettings& XclImpRoot::GetDocViewSettings() const +{ + return *mrImpData.mxDocViewSett; +} + +XclImpTabViewSettings& XclImpRoot::GetTabViewSettings() const +{ + return *mrImpData.mxTabViewSett; +} + +OUString XclImpRoot::GetScAddInName( const OUString& rXclName ) +{ + OUString aScName; + if( ScGlobal::GetAddInCollection()->GetCalcName( rXclName, aScName ) ) + return aScName; + return rXclName; +} + +void XclImpRoot::ReadCodeName( XclImpStream& rStrm, bool bGlobals ) +{ + if( !(mrImpData.mbHasBasic && (GetBiff() == EXC_BIFF8)) ) + return; + + OUString aName = rStrm.ReadUniString(); + if( aName.isEmpty() ) + return; + + if( bGlobals ) + { + GetExtDocOptions().GetDocSettings().maGlobCodeName = aName; + GetDoc().SetCodeName( aName ); + } + else + { + GetExtDocOptions().SetCodeName( GetCurrScTab(), aName ); + GetDoc().SetCodeName( GetCurrScTab(), aName ); + } +} + +ScDocumentImport& XclImpRoot::GetDocImport() +{ + return *mrImpData.mxDocImport; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xistream.cxx b/sc/source/filter/excel/xistream.cxx new file mode 100644 index 000000000..0a6c24aca --- /dev/null +++ b/sc/source/filter/excel/xistream.cxx @@ -0,0 +1,1084 @@ +/* -*- 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 <comphelper/docpasswordhelper.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <o3tl/safeint.hxx> +#include <osl/thread.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/solar.h> +#include <ftools.hxx> +#include <xistream.hxx> +#include <xlstring.hxx> +#include <xiroot.hxx> + +#include <vector> +#include <memory> + +using namespace ::com::sun::star; + +// Decryption +XclImpDecrypter::XclImpDecrypter() : + mnError( EXC_ENCR_ERROR_UNSUPP_CRYPT ), + mnOldPos( STREAM_SEEK_TO_END ), + mnRecSize( 0 ) +{ +} + +XclImpDecrypter::XclImpDecrypter( const XclImpDecrypter& rSrc ) : + ::comphelper::IDocPasswordVerifier(), + mnError( rSrc.mnError ), + mnOldPos( STREAM_SEEK_TO_END ), + mnRecSize( 0 ) +{ +} + +XclImpDecrypter::~XclImpDecrypter() +{ +} + +XclImpDecrypterRef XclImpDecrypter::Clone() const +{ + XclImpDecrypterRef xNewDecr; + if( IsValid() ) + xNewDecr.reset( OnClone() ); + return xNewDecr; +} + +::comphelper::DocPasswordVerifierResult XclImpDecrypter::verifyPassword( const OUString& rPassword, uno::Sequence< beans::NamedValue >& o_rEncryptionData ) +{ + o_rEncryptionData = OnVerifyPassword( rPassword ); + mnError = o_rEncryptionData.hasElements() ? ERRCODE_NONE : ERRCODE_ABORT; + return o_rEncryptionData.hasElements() ? ::comphelper::DocPasswordVerifierResult::OK : ::comphelper::DocPasswordVerifierResult::WrongPassword; +} + +::comphelper::DocPasswordVerifierResult XclImpDecrypter::verifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + bool bValid = OnVerifyEncryptionData( rEncryptionData ); + mnError = bValid ? ERRCODE_NONE : ERRCODE_ABORT; + return bValid ? ::comphelper::DocPasswordVerifierResult::OK : ::comphelper::DocPasswordVerifierResult::WrongPassword; +} + +void XclImpDecrypter::Update( const SvStream& rStrm, sal_uInt16 nRecSize ) +{ + if( IsValid() ) + { + sal_uInt64 const nNewPos = rStrm.Tell(); + if( (mnOldPos != nNewPos) || (mnRecSize != nRecSize) ) + { + OnUpdate( mnOldPos, nNewPos, nRecSize ); + mnOldPos = nNewPos; + mnRecSize = nRecSize; + } + } +} + +sal_uInt16 XclImpDecrypter::Read( SvStream& rStrm, void* pData, sal_uInt16 nBytes ) +{ + sal_uInt16 nRet = 0; + if( pData && nBytes ) + { + if( IsValid() ) + { + Update( rStrm, mnRecSize ); + nRet = OnRead( rStrm, static_cast< sal_uInt8* >( pData ), nBytes ); + mnOldPos = rStrm.Tell(); + } + else + nRet = static_cast<sal_uInt16>(rStrm.ReadBytes(pData, nBytes)); + } + return nRet; +} + +XclImpBiff5Decrypter::XclImpBiff5Decrypter( sal_uInt16 nKey, sal_uInt16 nHash ) : + mnKey( nKey ), + mnHash( nHash ) +{ +} + +XclImpBiff5Decrypter::XclImpBiff5Decrypter( const XclImpBiff5Decrypter& rSrc ) : + XclImpDecrypter( rSrc ), + maEncryptionData( rSrc.maEncryptionData ), + mnKey( rSrc.mnKey ), + mnHash( rSrc.mnHash ) +{ + if( IsValid() ) + maCodec.InitCodec( maEncryptionData ); +} + +XclImpBiff5Decrypter* XclImpBiff5Decrypter::OnClone() const +{ + return new XclImpBiff5Decrypter( *this ); +} + +uno::Sequence< beans::NamedValue > XclImpBiff5Decrypter::OnVerifyPassword( const OUString& rPassword ) +{ + maEncryptionData.realloc( 0 ); + + /* Convert password to a byte string. TODO: this needs some fine tuning + according to the spec... */ + OString aBytePassword = OUStringToOString( rPassword, osl_getThreadTextEncoding() ); + sal_Int32 nLen = aBytePassword.getLength(); + if( (0 < nLen) && (nLen < 16) ) + { + // init codec + maCodec.InitKey( reinterpret_cast<sal_uInt8 const *>(aBytePassword.getStr()) ); + + if ( maCodec.VerifyKey( mnKey, mnHash ) ) + { + maEncryptionData = maCodec.GetEncryptionData(); + + // since the export uses Std97 encryption always we have to request it here + ::std::vector< sal_uInt16 > aPassVect( 16 ); + sal_Int32 nInd = 0; + std::for_each(aPassVect.begin(), aPassVect.begin() + nLen, + [&rPassword, &nInd](sal_uInt16& rPass) { + rPass = static_cast< sal_uInt16 >( rPassword[nInd] ); + ++nInd; + }); + + uno::Sequence< sal_Int8 > aDocId = ::comphelper::DocPasswordHelper::GenerateRandomByteSequence( 16 ); + OSL_ENSURE( aDocId.getLength() == 16, "Unexpected length of the sequence!" ); + + ::msfilter::MSCodec_Std97 aCodec97; + aCodec97.InitKey(aPassVect.data(), reinterpret_cast<sal_uInt8 const *>(aDocId.getConstArray())); + + // merge the EncryptionData, there should be no conflicts + ::comphelper::SequenceAsHashMap aEncryptionHash( maEncryptionData ); + aEncryptionHash.update( ::comphelper::SequenceAsHashMap( aCodec97.GetEncryptionData() ) ); + aEncryptionHash >> maEncryptionData; + } + } + + return maEncryptionData; +} + +bool XclImpBiff5Decrypter::OnVerifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + maEncryptionData.realloc( 0 ); + + if( rEncryptionData.hasElements() ) + { + // init codec + maCodec.InitCodec( rEncryptionData ); + + if ( maCodec.VerifyKey( mnKey, mnHash ) ) + maEncryptionData = rEncryptionData; + } + + return maEncryptionData.hasElements(); +} + +void XclImpBiff5Decrypter::OnUpdate( std::size_t /*nOldStrmPos*/, std::size_t nNewStrmPos, sal_uInt16 nRecSize ) +{ + maCodec.InitCipher(); + maCodec.Skip( (nNewStrmPos + nRecSize) & 0x0F ); +} + +sal_uInt16 XclImpBiff5Decrypter::OnRead( SvStream& rStrm, sal_uInt8* pnData, sal_uInt16 nBytes ) +{ + sal_uInt16 nRet = static_cast<sal_uInt16>(rStrm.ReadBytes(pnData, nBytes)); + maCodec.Decode( pnData, nRet ); + return nRet; +} + +XclImpBiff8Decrypter::XclImpBiff8Decrypter( std::vector<sal_uInt8>&& rSalt, + std::vector<sal_uInt8>&& rVerifier, + std::vector<sal_uInt8>&& rVerifierHash) + : maSalt(std::move(rSalt)) + , maVerifier(std::move(rVerifier)) + , maVerifierHash(std::move(rVerifierHash)) + , mpCodec(nullptr) +{ +} + +XclImpBiff8Decrypter::XclImpBiff8Decrypter(const XclImpBiff8Decrypter& rSrc) + : XclImpDecrypter(rSrc) + , maEncryptionData(rSrc.maEncryptionData) + , maSalt(rSrc.maSalt) + , maVerifier(rSrc.maVerifier) + , maVerifierHash(rSrc.maVerifierHash) + , mpCodec(nullptr) +{ +} + +XclImpBiff8StdDecrypter::XclImpBiff8StdDecrypter(const XclImpBiff8StdDecrypter& rSrc) + : XclImpBiff8Decrypter(rSrc) +{ + mpCodec = &maCodec; + if (IsValid()) + maCodec.InitCodec(maEncryptionData); +} + +XclImpBiff8StdDecrypter* XclImpBiff8StdDecrypter::OnClone() const +{ + return new XclImpBiff8StdDecrypter(*this); +} + +XclImpBiff8CryptoAPIDecrypter::XclImpBiff8CryptoAPIDecrypter(const XclImpBiff8CryptoAPIDecrypter& rSrc) + : XclImpBiff8Decrypter(rSrc) +{ + mpCodec = &maCodec; + if (IsValid()) + maCodec.InitCodec(maEncryptionData); +} + +XclImpBiff8CryptoAPIDecrypter* XclImpBiff8CryptoAPIDecrypter::OnClone() const +{ + return new XclImpBiff8CryptoAPIDecrypter(*this); +} + +uno::Sequence< beans::NamedValue > XclImpBiff8Decrypter::OnVerifyPassword( const OUString& rPassword ) +{ + maEncryptionData.realloc( 0 ); + + sal_Int32 nLen = rPassword.getLength(); + if( (0 < nLen) && (nLen < 16) ) + { + // copy string to sal_uInt16 array + ::std::vector< sal_uInt16 > aPassVect( 16 ); + const sal_Unicode* pcChar = rPassword.getStr(); + std::for_each(aPassVect.begin(), aPassVect.begin() + nLen, + [&pcChar](sal_uInt16& rPass) { + rPass = static_cast< sal_uInt16 >( *pcChar ); + ++pcChar; + }); + + // init codec + mpCodec->InitKey(aPassVect.data(), maSalt.data()); + if (mpCodec->VerifyKey(maVerifier.data(), maVerifierHash.data())) + maEncryptionData = mpCodec->GetEncryptionData(); + } + + return maEncryptionData; +} + +bool XclImpBiff8Decrypter::OnVerifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + maEncryptionData.realloc( 0 ); + + if( rEncryptionData.hasElements() ) + { + // init codec + mpCodec->InitCodec( rEncryptionData ); + + if (mpCodec->VerifyKey(maVerifier.data(), maVerifierHash.data())) + maEncryptionData = rEncryptionData; + } + + return maEncryptionData.hasElements(); +} + +void XclImpBiff8Decrypter::OnUpdate( std::size_t nOldStrmPos, std::size_t nNewStrmPos, sal_uInt16 /*nRecSize*/ ) +{ + if( nNewStrmPos == nOldStrmPos ) + return; + + sal_uInt32 nOldBlock = GetBlock( nOldStrmPos ); + sal_uInt16 nOldOffset = GetOffset( nOldStrmPos ); + + sal_uInt32 nNewBlock = GetBlock( nNewStrmPos ); + sal_uInt16 nNewOffset = GetOffset( nNewStrmPos ); + + /* Rekey cipher, if block changed or if previous offset in same block. */ + if( (nNewBlock != nOldBlock) || (nNewOffset < nOldOffset) ) + { + mpCodec->InitCipher( nNewBlock ); + nOldOffset = 0; // reset nOldOffset for next if() statement + } + + /* Seek to correct offset. */ + if( nNewOffset > nOldOffset ) + mpCodec->Skip( nNewOffset - nOldOffset ); +} + +sal_uInt16 XclImpBiff8Decrypter::OnRead( SvStream& rStrm, sal_uInt8* pnData, sal_uInt16 nBytes ) +{ + sal_uInt16 nRet = 0; + + sal_uInt8* pnCurrData = pnData; + sal_uInt16 nBytesLeft = nBytes; + while( nBytesLeft ) + { + sal_uInt16 nBlockLeft = EXC_ENCR_BLOCKSIZE - GetOffset( rStrm.Tell() ); + sal_uInt16 nDecBytes = ::std::min< sal_uInt16 >( nBytesLeft, nBlockLeft ); + + // read the block from stream + nRet = nRet + static_cast<sal_uInt16>(rStrm.ReadBytes(pnCurrData, nDecBytes)); + // decode the block inplace + mpCodec->Decode( pnCurrData, nDecBytes, pnCurrData, nDecBytes ); + if( GetOffset( rStrm.Tell() ) == 0 ) + mpCodec->InitCipher( GetBlock( rStrm.Tell() ) ); + + pnCurrData += nDecBytes; + nBytesLeft = nBytesLeft - nDecBytes; + } + + return nRet; +} + +sal_uInt32 XclImpBiff8Decrypter::GetBlock( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt32 >( nStrmPos / EXC_ENCR_BLOCKSIZE ); +} + +sal_uInt16 XclImpBiff8Decrypter::GetOffset( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt16 >( nStrmPos % EXC_ENCR_BLOCKSIZE ); +} + +// Stream +XclImpStreamPos::XclImpStreamPos() : + mnPos( STREAM_SEEK_TO_BEGIN ), + mnNextPos( STREAM_SEEK_TO_BEGIN ), + mnCurrSize( 0 ), + mnRawRecId( EXC_ID_UNKNOWN ), + mnRawRecSize( 0 ), + mnRawRecLeft( 0 ), + mbValid( false ) +{ +} + +void XclImpStreamPos::Set( + const SvStream& rStrm, std::size_t nNextPos, std::size_t nCurrSize, + sal_uInt16 nRawRecId, sal_uInt16 nRawRecSize, sal_uInt16 nRawRecLeft, + bool bValid ) +{ + mnPos = rStrm.Tell(); + mnNextPos = nNextPos; + mnCurrSize = nCurrSize; + mnRawRecId = nRawRecId; + mnRawRecSize = nRawRecSize; + mnRawRecLeft = nRawRecLeft; + mbValid = bValid; +} + +void XclImpStreamPos::Get( + SvStream& rStrm, std::size_t& rnNextPos, std::size_t& rnCurrSize, + sal_uInt16& rnRawRecId, sal_uInt16& rnRawRecSize, sal_uInt16& rnRawRecLeft, + bool& rbValid ) const +{ + rStrm.Seek( mnPos ); + rnNextPos = mnNextPos; + rnCurrSize = mnCurrSize; + rnRawRecId = mnRawRecId; + rnRawRecSize = mnRawRecSize; + rnRawRecLeft = mnRawRecLeft; + rbValid = mbValid; +} + +XclBiff XclImpStream::DetectBiffVersion( SvStream& rStrm ) +{ + XclBiff eBiff = EXC_BIFF_UNKNOWN; + + rStrm.Seek( STREAM_SEEK_TO_BEGIN ); + sal_uInt16 nBofId(0), nBofSize(0); + rStrm.ReadUInt16( nBofId ).ReadUInt16( nBofSize ); + + if (rStrm.good() && 4 <= nBofSize && nBofSize <= 16) switch( nBofId ) + { + case EXC_ID2_BOF: + eBiff = EXC_BIFF2; + break; + case EXC_ID3_BOF: + eBiff = EXC_BIFF3; + break; + case EXC_ID4_BOF: + eBiff = EXC_BIFF4; + break; + case EXC_ID5_BOF: + { + sal_uInt16 nVersion(0); + rStrm.ReadUInt16( nVersion ); + // #i23425# #i44031# #i62752# there are some *really* broken documents out there... + switch( nVersion & 0xFF00 ) + { + case 0: eBiff = EXC_BIFF5; break; // #i44031# #i62752# + case EXC_BOF_BIFF2: eBiff = EXC_BIFF2; break; + case EXC_BOF_BIFF3: eBiff = EXC_BIFF3; break; + case EXC_BOF_BIFF4: eBiff = EXC_BIFF4; break; + case EXC_BOF_BIFF5: eBiff = EXC_BIFF5; break; + case EXC_BOF_BIFF8: eBiff = EXC_BIFF8; break; + default: SAL_WARN("sc", "XclImpStream::DetectBiffVersion - unknown BIFF version: 0x" << std::hex << nVersion ); + } + } + break; + } + return eBiff; +} + +XclImpStream::XclImpStream( SvStream& rInStrm, const XclImpRoot& rRoot ) : + mrStrm( rInStrm ), + mrRoot( rRoot ), + mnGlobRecId( EXC_ID_UNKNOWN ), + mbGlobValidRec( false ), + mbHasGlobPos( false ), + mnNextRecPos( STREAM_SEEK_TO_BEGIN ), + mnCurrRecSize( 0 ), + mnComplRecSize( 0 ), + mbHasComplRec( false ), + mnRecId( EXC_ID_UNKNOWN ), + mnAltContId( EXC_ID_UNKNOWN ), + mnRawRecId( EXC_ID_UNKNOWN ), + mnRawRecSize( 0 ), + mnRawRecLeft( 0 ), + mcNulSubst( '?' ), + mbCont( true ), + mbUseDecr( false ), + mbValidRec( false ), + mbValid( false ) +{ + mnStreamSize = mrStrm.TellEnd(); + mrStrm.Seek( STREAM_SEEK_TO_BEGIN ); +} + +XclImpStream::~XclImpStream() +{ +} + +bool XclImpStream::StartNextRecord() +{ + maPosStack.clear(); + + /* #i4266# Counter to ignore zero records (id==len==0) (i.e. the application + "Crystal Report" writes zero records between other records) */ + std::size_t nZeroRecCount = 5; + bool bIsZeroRec = false; + + do + { + mbValidRec = ReadNextRawRecHeader(); + bIsZeroRec = (mnRawRecId == 0) && (mnRawRecSize == 0); + if( bIsZeroRec ) --nZeroRecCount; + mnNextRecPos = mrStrm.Tell() + mnRawRecSize; + } + while( mbValidRec && ((mbCont && IsContinueId( mnRawRecId )) || (bIsZeroRec && nZeroRecCount)) ); + + mbValidRec = mbValidRec && !bIsZeroRec; + mbValid = mbValidRec; + SetupRecord(); + + return mbValidRec; +} + +bool XclImpStream::StartNextRecord( std::size_t nNextRecPos ) +{ + mnNextRecPos = nNextRecPos; + return StartNextRecord(); +} + +void XclImpStream::ResetRecord( bool bContLookup, sal_uInt16 nAltContId ) +{ + if( mbValidRec ) + { + maPosStack.clear(); + RestorePosition( maFirstRec ); + mnCurrRecSize = mnComplRecSize = mnRawRecSize; + mbHasComplRec = !bContLookup; + mbCont = bContLookup; + mnAltContId = nAltContId; + EnableDecryption(); + } +} + +void XclImpStream::RewindRecord() +{ + mnNextRecPos = maFirstRec.GetPos(); + mbValid = mbValidRec = false; +} + +void XclImpStream::SetDecrypter( XclImpDecrypterRef const & xDecrypter ) +{ + mxDecrypter = xDecrypter; + EnableDecryption(); + SetupDecrypter(); +} + +void XclImpStream::CopyDecrypterFrom( const XclImpStream& rStrm ) +{ + XclImpDecrypterRef xNewDecr; + if( rStrm.mxDecrypter ) + xNewDecr = rStrm.mxDecrypter->Clone(); + SetDecrypter( xNewDecr ); +} + +void XclImpStream::EnableDecryption( bool bEnable ) +{ + mbUseDecr = bEnable && mxDecrypter && mxDecrypter->IsValid(); +} + +void XclImpStream::PushPosition() +{ + maPosStack.emplace_back( ); + StorePosition( maPosStack.back() ); +} + +void XclImpStream::PopPosition() +{ + OSL_ENSURE( !maPosStack.empty(), "XclImpStream::PopPosition - stack empty" ); + if( !maPosStack.empty() ) + { + RestorePosition( maPosStack.back() ); + maPosStack.pop_back(); + } +} + +void XclImpStream::StoreGlobalPosition() +{ + StorePosition( maGlobPos ); + mnGlobRecId = mnRecId; + mbGlobValidRec = mbValidRec; + mbHasGlobPos = true; +} + +void XclImpStream::SeekGlobalPosition() +{ + OSL_ENSURE( mbHasGlobPos, "XclImpStream::SeekGlobalPosition - no position stored" ); + if( mbHasGlobPos ) + { + RestorePosition( maGlobPos ); + mnRecId = mnGlobRecId; + mnComplRecSize = mnCurrRecSize; + mbHasComplRec = !mbCont; + mbValidRec = mbGlobValidRec; + } +} + +std::size_t XclImpStream::GetRecPos() const +{ + return mbValid ? (mnCurrRecSize - mnRawRecLeft) : EXC_REC_SEEK_TO_END; +} + +std::size_t XclImpStream::GetRecSize() +{ + if( !mbHasComplRec ) + { + PushPosition(); + while( JumpToNextContinue() ) ; // JumpToNextContinue() adds up mnCurrRecSize + mnComplRecSize = mnCurrRecSize; + mbHasComplRec = true; + PopPosition(); + } + return mnComplRecSize; +} + +std::size_t XclImpStream::GetRecLeft() +{ + return mbValid ? (GetRecSize() - GetRecPos()) : 0; +} + +sal_uInt16 XclImpStream::GetNextRecId() +{ + sal_uInt16 nRecId = EXC_ID_UNKNOWN; + if( mbValidRec ) + { + PushPosition(); + while( JumpToNextContinue() ) ; // skip following CONTINUE records + if( mnNextRecPos < mnStreamSize ) + { + mrStrm.Seek( mnNextRecPos ); + mrStrm.ReadUInt16( nRecId ); + } + PopPosition(); + } + return nRecId; +} + +sal_uInt16 XclImpStream::PeekRecId( std::size_t nPos ) +{ + sal_uInt16 nRecId = EXC_ID_UNKNOWN; + if (mbValidRec && nPos < mnStreamSize) + { + sal_uInt64 const nCurPos = mrStrm.Tell(); + mrStrm.Seek(nPos); + mrStrm.ReadUInt16( nRecId ); + mrStrm.Seek(nCurPos); + } + return nRecId; +} + +sal_uInt8 XclImpStream::ReaduInt8() +{ + sal_uInt8 nValue = 0; + if( EnsureRawReadSize( 1 ) ) + { + if( mbUseDecr ) + mxDecrypter->Read( mrStrm, &nValue, 1 ); + else + mrStrm.ReadUChar( nValue ); + --mnRawRecLeft; + } + return nValue; +} + +sal_Int16 XclImpStream::ReadInt16() +{ + sal_Int16 nValue = 0; + if( EnsureRawReadSize( 2 ) ) + { + if( mbUseDecr ) + { + SVBT16 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 2 ); + nValue = static_cast< sal_Int16 >( SVBT16ToUInt16( pnBuffer ) ); + } + else + mrStrm.ReadInt16( nValue ); + mnRawRecLeft -= 2; + } + return nValue; +} + +sal_uInt16 XclImpStream::ReaduInt16() +{ + sal_uInt16 nValue = 0; + if( EnsureRawReadSize( 2 ) ) + { + if( mbUseDecr ) + { + SVBT16 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 2 ); + nValue = SVBT16ToUInt16( pnBuffer ); + } + else + mrStrm.ReadUInt16( nValue ); + mnRawRecLeft -= 2; + } + return nValue; +} + +sal_Int32 XclImpStream::ReadInt32() +{ + sal_Int32 nValue = 0; + if( EnsureRawReadSize( 4 ) ) + { + if( mbUseDecr ) + { + SVBT32 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 4 ); + nValue = static_cast< sal_Int32 >( SVBT32ToUInt32( pnBuffer ) ); + } + else + mrStrm.ReadInt32( nValue ); + mnRawRecLeft -= 4; + } + return nValue; +} + +sal_uInt32 XclImpStream::ReaduInt32() +{ + sal_uInt32 nValue = 0; + if( EnsureRawReadSize( 4 ) ) + { + if( mbUseDecr ) + { + SVBT32 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 4 ); + nValue = SVBT32ToUInt32( pnBuffer ); + } + else + mrStrm.ReadUInt32( nValue ); + mnRawRecLeft -= 4; + } + return nValue; +} + +double XclImpStream::ReadDouble() +{ + double nValue = 0; + if( EnsureRawReadSize( 8 ) ) + { + if( mbUseDecr ) + { + SVBT64 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 8 ); + nValue = SVBT64ToDouble( pnBuffer ); + } + else + mrStrm.ReadDouble( nValue ); + mnRawRecLeft -= 8; + } + return nValue; +} + +std::size_t XclImpStream::Read( void* pData, std::size_t nBytes ) +{ + std::size_t nRet = 0; + if( mbValid && pData && (nBytes > 0) ) + { + sal_uInt8* pnBuffer = static_cast< sal_uInt8* >( pData ); + std::size_t nBytesLeft = nBytes; + + while( mbValid && (nBytesLeft > 0) ) + { + sal_uInt16 nReadSize = GetMaxRawReadSize( nBytesLeft ); + sal_uInt16 nReadRet = ReadRawData( pnBuffer, nReadSize ); + nRet += nReadRet; + mbValid = (nReadSize == nReadRet); + OSL_ENSURE( mbValid, "XclImpStream::Read - stream read error" ); + pnBuffer += nReadRet; + nBytesLeft -= nReadRet; + if( mbValid && (nBytesLeft > 0) ) + JumpToNextContinue(); + OSL_ENSURE( mbValid, "XclImpStream::Read - record overread" ); + } + } + return nRet; +} + +std::size_t XclImpStream::CopyToStream( SvStream& rOutStrm, std::size_t nBytes ) +{ + std::size_t nRet = 0; + if (mbValid && nBytes) + { + const std::size_t nMaxBuffer = 4096; + std::vector<sal_uInt8> aBuffer(o3tl::sanitizing_min(nBytes, nMaxBuffer)); + std::size_t nBytesLeft = nBytes; + + while (mbValid) + { + if (!nBytesLeft) + break; + std::size_t nReadSize = o3tl::sanitizing_min(nBytesLeft, nMaxBuffer); + nRet += Read(aBuffer.data(), nReadSize); + // writing more bytes than read results in invalid memory access + SAL_WARN_IF(nRet != nReadSize, "sc", "read less bytes than requested"); + rOutStrm.WriteBytes(aBuffer.data(), nReadSize); + nBytesLeft -= nReadSize; + } + } + return nRet; +} + +void XclImpStream::CopyRecordToStream( SvStream& rOutStrm ) +{ + if( mbValidRec ) + { + PushPosition(); + RestorePosition( maFirstRec ); + CopyToStream( rOutStrm, GetRecSize() ); + PopPosition(); + } +} + +void XclImpStream::Seek( std::size_t nPos ) +{ + if( !mbValidRec ) + return; + + std::size_t nCurrPos = GetRecPos(); + if( !mbValid || (nPos < nCurrPos) ) // from invalid state or backward + { + RestorePosition( maFirstRec ); + Ignore( nPos ); + } + else if( nPos > nCurrPos ) // forward + { + Ignore( nPos - nCurrPos ); + } +} + +void XclImpStream::Ignore( std::size_t nBytes ) +{ + // implementation similar to Read(), but without really reading anything + std::size_t nBytesLeft = nBytes; + while (mbValid) + { + if (!nBytesLeft) + break; + sal_uInt16 nReadSize = GetMaxRawReadSize( nBytesLeft ); + mbValid = checkSeek(mrStrm, mrStrm.Tell() + nReadSize); + mnRawRecLeft = mnRawRecLeft - nReadSize; + nBytesLeft -= nReadSize; + if (mbValid && nBytesLeft > 0) + JumpToNextContinue(); + OSL_ENSURE( mbValid, "XclImpStream::Ignore - record overread" ); + } +} + +std::size_t XclImpStream::ReadUniStringExtHeader( + bool& rb16Bit, bool& rbRich, bool& rbFareast, + sal_uInt16& rnFormatRuns, sal_uInt32& rnExtInf, sal_uInt8 nFlags ) +{ + OSL_ENSURE( !::get_flag( nFlags, EXC_STRF_UNKNOWN ), "XclImpStream::ReadUniStringExt - unknown flags" ); + rb16Bit = ::get_flag( nFlags, EXC_STRF_16BIT ); + rbRich = ::get_flag( nFlags, EXC_STRF_RICH ); + rbFareast = ::get_flag( nFlags, EXC_STRF_FAREAST ); + rnFormatRuns = rbRich ? ReaduInt16() : 0; + rnExtInf = rbFareast ? ReaduInt32() : 0; + return rnExtInf + 4 * rnFormatRuns; +} + +std::size_t XclImpStream::ReadUniStringExtHeader( bool& rb16Bit, sal_uInt8 nFlags ) +{ + bool bRich, bFareast; + sal_uInt16 nCrun; + sal_uInt32 nExtInf; + return ReadUniStringExtHeader( rb16Bit, bRich, bFareast, nCrun, nExtInf, nFlags ); +} + +OUString XclImpStream::ReadRawUniString( sal_uInt16 nChars, bool b16Bit ) +{ + OUStringBuffer aRet(o3tl::sanitizing_min<sal_uInt16>(nChars, mnRawRecLeft / (b16Bit ? 2 : 1))); + sal_uInt16 nCharsLeft = nChars; + sal_uInt16 nReadSize; + + while (IsValid() && nCharsLeft) + { + if( b16Bit ) + { + nReadSize = o3tl::sanitizing_min<sal_uInt16>(nCharsLeft, mnRawRecLeft / 2); + OSL_ENSURE( (nReadSize <= nCharsLeft) || !(mnRawRecLeft & 0x1), + "XclImpStream::ReadRawUniString - missing a byte" ); + } + else + nReadSize = GetMaxRawReadSize( nCharsLeft ); + + std::unique_ptr<sal_Unicode[]> pcBuffer(new sal_Unicode[nReadSize + 1]); + + sal_Unicode* pcUniChar = pcBuffer.get(); + sal_Unicode* pcEndChar = pcBuffer.get() + nReadSize; + + if( b16Bit ) + { + sal_uInt16 nReadChar; + for( ; IsValid() && (pcUniChar < pcEndChar); ++pcUniChar ) + { + nReadChar = ReaduInt16(); + (*pcUniChar) = (nReadChar == EXC_NUL) ? mcNulSubst : static_cast< sal_Unicode >( nReadChar ); + } + } + else + { + sal_uInt8 nReadChar; + for( ; IsValid() && (pcUniChar < pcEndChar); ++pcUniChar ) + { + nReadChar = ReaduInt8(); + (*pcUniChar) = (nReadChar == EXC_NUL_C) ? mcNulSubst : static_cast< sal_Unicode >( nReadChar ); + } + } + + *pcEndChar = '\0'; + // this has the side-effect of only copying as far as the first null, which appears to be intentional. e.g. + // see tdf#124318 + aRet.append( pcBuffer.get() ); + + nCharsLeft = nCharsLeft - nReadSize; + if( nCharsLeft > 0 ) + JumpToNextStringContinue( b16Bit ); + } + + return aRet.makeStringAndClear(); +} + +OUString XclImpStream::ReadUniString( sal_uInt16 nChars, sal_uInt8 nFlags ) +{ + bool b16Bit; + std::size_t nExtSize = ReadUniStringExtHeader( b16Bit, nFlags ); + OUString aRet( ReadRawUniString( nChars, b16Bit ) ); + Ignore( nExtSize ); + return aRet; +} + +OUString XclImpStream::ReadUniString( sal_uInt16 nChars ) +{ + return ReadUniString( nChars, ReaduInt8() ); +} + +OUString XclImpStream::ReadUniString() +{ + return ReadUniString( ReaduInt16() ); +} + +void XclImpStream::IgnoreRawUniString( sal_uInt16 nChars, bool b16Bit ) +{ + sal_uInt16 nCharsLeft = nChars; + sal_uInt16 nReadSize; + + while( IsValid() && (nCharsLeft > 0) ) + { + if( b16Bit ) + { + nReadSize = o3tl::sanitizing_min<sal_uInt16>(nCharsLeft, mnRawRecLeft / 2); + OSL_ENSURE( (nReadSize <= nCharsLeft) || !(mnRawRecLeft & 0x1), + "XclImpStream::IgnoreRawUniString - missing a byte" ); + Ignore( nReadSize * 2 ); + } + else + { + nReadSize = GetMaxRawReadSize( nCharsLeft ); + Ignore( nReadSize ); + } + + nCharsLeft = nCharsLeft - nReadSize; + if( nCharsLeft > 0 ) + JumpToNextStringContinue( b16Bit ); + } +} + +void XclImpStream::IgnoreUniString( sal_uInt16 nChars, sal_uInt8 nFlags ) +{ + bool b16Bit; + std::size_t nExtSize = ReadUniStringExtHeader( b16Bit, nFlags ); + IgnoreRawUniString( nChars, b16Bit ); + Ignore( nExtSize ); +} + +void XclImpStream::IgnoreUniString( sal_uInt16 nChars ) +{ + IgnoreUniString( nChars, ReaduInt8() ); +} + +OUString XclImpStream::ReadRawByteString( sal_uInt16 nChars ) +{ + nChars = GetMaxRawReadSize(nChars); + std::unique_ptr<char[]> pcBuffer(new char[ nChars + 1 ]); + sal_uInt16 nCharsRead = ReadRawData( pcBuffer.get(), nChars ); + pcBuffer[ nCharsRead ] = '\0'; + OUString aRet( pcBuffer.get(), strlen(pcBuffer.get()), mrRoot.GetTextEncoding() ); + return aRet; +} + +OUString XclImpStream::ReadByteString( bool b16BitLen ) +{ + return ReadRawByteString( b16BitLen ? ReaduInt16() : ReaduInt8() ); +} + +// private -------------------------------------------------------------------- + +void XclImpStream::StorePosition( XclImpStreamPos& rPos ) +{ + rPos.Set( mrStrm, mnNextRecPos, mnCurrRecSize, mnRawRecId, mnRawRecSize, mnRawRecLeft, mbValid ); +} + +void XclImpStream::RestorePosition( const XclImpStreamPos& rPos ) +{ + rPos.Get( mrStrm, mnNextRecPos, mnCurrRecSize, mnRawRecId, mnRawRecSize, mnRawRecLeft, mbValid ); + SetupDecrypter(); +} + +bool XclImpStream::ReadNextRawRecHeader() +{ + bool bRet = checkSeek(mrStrm, mnNextRecPos) && (mnNextRecPos + 4 <= mnStreamSize); + if (bRet) + { + mrStrm.ReadUInt16( mnRawRecId ).ReadUInt16( mnRawRecSize ); + bRet = mrStrm.good(); + } + return bRet; +} + +void XclImpStream::SetupDecrypter() +{ + if( mxDecrypter ) + mxDecrypter->Update( mrStrm, mnRawRecSize ); +} + +void XclImpStream::SetupRawRecord() +{ + // pre: mnRawRecSize contains current raw record size + // pre: mrStrm points to start of raw record data + mnNextRecPos = mrStrm.Tell() + mnRawRecSize; + mnRawRecLeft = mnRawRecSize; + mnCurrRecSize += mnRawRecSize; + SetupDecrypter(); // decrypter works on raw record level +} + +void XclImpStream::SetupRecord() +{ + mnRecId = mnRawRecId; + mnAltContId = EXC_ID_UNKNOWN; + mnCurrRecSize = 0; + mnComplRecSize = mnRawRecSize; + mbHasComplRec = !mbCont; + SetupRawRecord(); + SetNulSubstChar(); + EnableDecryption(); + StorePosition( maFirstRec ); +} + +bool XclImpStream::IsContinueId( sal_uInt16 nRecId ) const +{ + return (nRecId == EXC_ID_CONT) || (nRecId == mnAltContId); +} + +bool XclImpStream::JumpToNextContinue() +{ + mbValid = mbValid && mbCont && ReadNextRawRecHeader() && IsContinueId( mnRawRecId ); + if( mbValid ) // do not setup a following non-CONTINUE record + SetupRawRecord(); + return mbValid; +} + +bool XclImpStream::JumpToNextStringContinue( bool& rb16Bit ) +{ + OSL_ENSURE( mnRawRecLeft == 0, "XclImpStream::JumpToNextStringContinue - unexpected garbage" ); + + if( mbCont && (GetRecLeft() > 0) ) + { + JumpToNextContinue(); + } + else if( mnRecId == EXC_ID_CONT ) + { + // CONTINUE handling is off, but we have started reading in a CONTINUE record + // -> start next CONTINUE for TXO import + mbValidRec = ReadNextRawRecHeader() && ((mnRawRecId != 0) || (mnRawRecSize > 0)); + mbValid = mbValidRec && (mnRawRecId == EXC_ID_CONT); + // we really start a new record here - no chance to return to string origin + if( mbValid ) + SetupRecord(); + } + else + mbValid = false; + + if( mbValid ) + rb16Bit = ::get_flag( ReaduInt8(), EXC_STRF_16BIT ); + return mbValid; +} + +bool XclImpStream::EnsureRawReadSize( sal_uInt16 nBytes ) +{ + if( mbValid && nBytes ) + { + while( mbValid && !mnRawRecLeft ) JumpToNextContinue(); + mbValid = mbValid && (nBytes <= mnRawRecLeft); + OSL_ENSURE( mbValid, "XclImpStream::EnsureRawReadSize - record overread" ); + } + return mbValid; +} + +sal_uInt16 XclImpStream::GetMaxRawReadSize( std::size_t nBytes ) const +{ + return static_cast<sal_uInt16>(o3tl::sanitizing_min<std::size_t>(nBytes, mnRawRecLeft)); +} + +sal_uInt16 XclImpStream::ReadRawData( void* pData, sal_uInt16 nBytes ) +{ + OSL_ENSURE( (nBytes <= mnRawRecLeft), "XclImpStream::ReadRawData - record overread" ); + sal_uInt16 nRet = 0; + if( mbUseDecr ) + nRet = mxDecrypter->Read( mrStrm, pData, nBytes ); + else + nRet = static_cast<sal_uInt16>(mrStrm.ReadBytes(pData, nBytes)); + mnRawRecLeft = mnRawRecLeft - nRet; + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xistring.cxx b/sc/source/filter/excel/xistring.cxx new file mode 100644 index 000000000..f0878a617 --- /dev/null +++ b/sc/source/filter/excel/xistring.cxx @@ -0,0 +1,212 @@ +/* -*- 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 <xistring.hxx> +#include <xlstyle.hxx> +#include <xistream.hxx> +#include <xiroot.hxx> +#include <xltools.hxx> +#include <sal/log.hxx> + +// Byte/Unicode strings ======================================================= + +/** All allowed flags for import. */ +const XclStrFlags nAllowedFlags = XclStrFlags::EightBitLength | XclStrFlags::SmartFlags | XclStrFlags::SeparateFormats; + +XclImpString::XclImpString() +{ +} + +XclImpString::XclImpString( const OUString& rString ) : + maString( rString ) +{ +} + +void XclImpString::Read( XclImpStream& rStrm, XclStrFlags nFlags ) +{ + if( !( nFlags & XclStrFlags::SeparateFormats ) ) + maFormats.clear(); + + SAL_WARN_IF( + nFlags & ~nAllowedFlags, "sc.filter", + "XclImpString::Read - unknown flag"); + bool b16BitLen = !( nFlags & XclStrFlags::EightBitLength ); + + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + // no integrated formatting in BIFF2-BIFF7 + maString = rStrm.ReadByteString( b16BitLen ); + break; + + case EXC_BIFF8: + { + // --- string header --- + sal_uInt16 nChars = b16BitLen ? rStrm.ReaduInt16() : rStrm.ReaduInt8(); + sal_uInt8 nFlagField = 0; + if( nChars || !( nFlags & XclStrFlags::SmartFlags ) ) + nFlagField = rStrm.ReaduInt8(); + + bool b16Bit, bRich, bFarEast; + sal_uInt16 nRunCount; + sal_uInt32 nExtInf; + rStrm.ReadUniStringExtHeader( b16Bit, bRich, bFarEast, nRunCount, nExtInf, nFlagField ); + // ignore the flags, they may be wrong + + // --- character array --- + maString = rStrm.ReadRawUniString( nChars, b16Bit ); + + // --- formatting --- + if (nRunCount) + ReadFormats( rStrm, maFormats, nRunCount ); + + // --- extended (FarEast) information --- + rStrm.Ignore( nExtInf ); + } + break; + + default: + DBG_ERROR_BIFF(); + } +} + +void XclImpString::AppendFormat( XclFormatRunVec& rFormats, sal_uInt16 nChar, sal_uInt16 nFontIdx ) +{ + // #i33341# real life -- same character index may occur several times + OSL_ENSURE( rFormats.empty() || (rFormats.back().mnChar <= nChar), "XclImpString::AppendFormat - wrong char order" ); + if( rFormats.empty() || (rFormats.back().mnChar < nChar) ) + rFormats.emplace_back( nChar, nFontIdx ); + else + rFormats.back().mnFontIdx = nFontIdx; +} + +void XclImpString::ReadFormats( XclImpStream& rStrm, XclFormatRunVec& rFormats ) +{ + bool bBiff8 = rStrm.GetRoot().GetBiff() == EXC_BIFF8; + sal_uInt16 nRunCount = bBiff8 ? rStrm.ReaduInt16() : rStrm.ReaduInt8(); + ReadFormats( rStrm, rFormats, nRunCount ); +} + +void XclImpString::ReadFormats( XclImpStream& rStrm, XclFormatRunVec& rFormats, sal_uInt16 nRunCount ) +{ + rFormats.clear(); + + size_t nElementSize = rStrm.GetRoot().GetBiff() == EXC_BIFF8 ? 4 : 2; + size_t nAvailableBytes = rStrm.GetRecLeft(); + size_t nMaxElements = nAvailableBytes / nElementSize; + if (nRunCount > nMaxElements) + { + SAL_WARN("sc.filter", "XclImpString::ReadFormats - more formats claimed than stream could contain"); + rStrm.SetSvStreamError(SVSTREAM_FILEFORMAT_ERROR); + return; + } + + rFormats.reserve( nRunCount ); + /* #i33341# real life -- same character index may occur several times + -> use AppendFormat() to validate formats */ + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + for( sal_uInt16 nIdx = 0; nIdx < nRunCount; ++nIdx ) + { + sal_uInt16 nChar = rStrm.ReaduInt16(); + sal_uInt16 nFontIdx = rStrm.ReaduInt16(); + AppendFormat( rFormats, nChar, nFontIdx ); + } + } + else + { + for( sal_uInt16 nIdx = 0; nIdx < nRunCount; ++nIdx ) + { + sal_uInt8 nChar = rStrm.ReaduInt8(); + sal_uInt8 nFontIdx = rStrm.ReaduInt8(); + AppendFormat( rFormats, nChar, nFontIdx ); + } + } +} + +void XclImpString::ReadObjFormats( XclImpStream& rStrm, XclFormatRunVec& rFormats, sal_uInt16 nFormatSize ) +{ + // number of formatting runs, each takes 8 bytes + sal_uInt16 nRunCount = nFormatSize / 8; + rFormats.clear(); + rFormats.reserve( nRunCount ); + for( sal_uInt16 nIdx = 0; nIdx < nRunCount; ++nIdx ) + { + sal_uInt16 nChar = rStrm.ReaduInt16(); + sal_uInt16 nFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + AppendFormat( rFormats, nChar, nFontIdx ); + } +} + +// String iterator ============================================================ + +XclImpStringIterator::XclImpStringIterator( const XclImpString& rString ) : + mrText( rString.GetText() ), + mrFormats( rString.GetFormats() ), + mnPortion( 0 ), + mnTextBeg( 0 ), + mnTextEnd( 0 ), + mnFormatsBeg( 0 ), + mnFormatsEnd( 0 ) +{ + // first portion is formatted, adjust vector index to next portion + if( !mrFormats.empty() && (mrFormats.front().mnChar == 0) ) + ++mnFormatsEnd; + // find end position of the first portion + mnTextEnd = (mnFormatsEnd < mrFormats.size() ? + mrFormats[ mnFormatsEnd ].mnChar : mrText.getLength() ); +} + +OUString XclImpStringIterator::GetPortionText() const +{ + return mrText.copy( mnTextBeg, mnTextEnd - mnTextBeg ); +} + +sal_uInt16 XclImpStringIterator::GetPortionFont() const +{ + return (mnFormatsBeg < mnFormatsEnd) ? mrFormats[ mnFormatsBeg ].mnFontIdx : EXC_FONT_NOTFOUND; +} + +XclImpStringIterator& XclImpStringIterator::operator++() +{ + if( Is() ) + { + ++mnPortion; + do + { + // indexes into vector of formatting runs + if( mnFormatsBeg < mnFormatsEnd ) + ++mnFormatsBeg; + if( mnFormatsEnd < mrFormats.size() ) + ++mnFormatsEnd; + // character positions of next portion + mnTextBeg = mnTextEnd; + mnTextEnd = (mnFormatsEnd < mrFormats.size()) ? + mrFormats[ mnFormatsEnd ].mnChar : mrText.getLength(); + } + while( Is() && (mnTextBeg == mnTextEnd) ); + } + return *this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xistyle.cxx b/sc/source/filter/excel/xistyle.cxx new file mode 100644 index 000000000..7361c7ee6 --- /dev/null +++ b/sc/source/filter/excel/xistyle.cxx @@ -0,0 +1,2084 @@ +/* -*- 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 <memory> +#include <xistyle.hxx> +#include <sfx2/objsh.hxx> +#include <svtools/ctrltool.hxx> +#include <editeng/editobj.hxx> +#include <scitems.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/escapementitem.hxx> +#include <svx/algitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/lineitem.hxx> +#include <svx/rotmodit.hxx> +#include <editeng/colritem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/flstitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/editids.hrc> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/fontcharmap.hxx> +#include <vcl/outdev.hxx> +#include <document.hxx> +#include <documentimport.hxx> +#include <docpool.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <stlpool.hxx> +#include <stlsheet.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <attarray.hxx> +#include <xladdress.hxx> +#include <xlcontent.hxx> +#include <xltracer.hxx> +#include <xltools.hxx> +#include <xistream.hxx> +#include <xicontent.hxx> + +#include <root.hxx> +#include <colrowst.hxx> + +#include <string_view> +#include <vector> + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <svl/numformat.hxx> +#include <o3tl/string_view.hxx> + +using ::std::vector; +using namespace ::com::sun::star; + +typedef ::cppu::WeakImplHelper< container::XIndexAccess > XIndexAccess_BASE; +typedef ::std::vector< Color > ColorVec; + +namespace { + +class PaletteIndex : public XIndexAccess_BASE +{ +public: + explicit PaletteIndex( ColorVec&& rColorTable ) : maColor( std::move(rColorTable) ) {} + + // Methods XIndexAccess + virtual ::sal_Int32 SAL_CALL getCount() override + { + return maColor.size(); + } + + virtual uno::Any SAL_CALL getByIndex( ::sal_Int32 Index ) override + { + //--Index; // apparently the palette is already 1 based + return uno::Any( sal_Int32( maColor[ Index ] ) ); + } + + // Methods XElementAccess + virtual uno::Type SAL_CALL getElementType() override + { + return ::cppu::UnoType<sal_Int32>::get(); + } + virtual sal_Bool SAL_CALL hasElements() override + { + return (!maColor.empty()); + } + +private: + ColorVec maColor; +}; + +} + +void +XclImpPalette::ExportPalette() +{ + SfxObjectShell* pDocShell = mrRoot.GetDocShell(); + if(!pDocShell) + return; + + // copy values in color palette + sal_Int16 nColors = maColorTable.size(); + ColorVec aColors; + aColors.resize( nColors ); + for( sal_uInt16 nIndex = 0; nIndex < nColors; ++nIndex ) + aColors[ nIndex ] = GetColor( nIndex ); + + uno::Reference< beans::XPropertySet > xProps( pDocShell->GetModel(), uno::UNO_QUERY ); + if ( xProps.is() ) + { + uno::Reference< container::XIndexAccess > xIndex( new PaletteIndex( std::move(aColors) ) ); + xProps->setPropertyValue( "ColorPalette", uno::Any( xIndex ) ); + } + +} +// PALETTE record - color information ========================================= + +XclImpPalette::XclImpPalette( const XclImpRoot& rRoot ) : + XclDefaultPalette( rRoot ), mrRoot( rRoot ) +{ +} + +void XclImpPalette::Initialize() +{ + maColorTable.clear(); +} + +Color XclImpPalette::GetColor( sal_uInt16 nXclIndex ) const +{ + if( nXclIndex >= EXC_COLOR_USEROFFSET ) + { + sal_uInt32 nIx = nXclIndex - EXC_COLOR_USEROFFSET; + if( nIx < maColorTable.size() ) + return maColorTable[ nIx ]; + } + return GetDefColor( nXclIndex ); +} + +void XclImpPalette::ReadPalette( XclImpStream& rStrm ) +{ + sal_uInt16 nCount; + nCount = rStrm.ReaduInt16(); + + const size_t nMinRecordSize = 4; + const size_t nMaxRecords = rStrm.GetRecLeft() / nMinRecordSize; + if (nCount > nMaxRecords) + { + SAL_WARN("sc", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nCount << " claimed, truncating"); + nCount = nMaxRecords; + } + + maColorTable.resize( nCount ); + Color aColor; + for( sal_uInt16 nIndex = 0; nIndex < nCount; ++nIndex ) + { + rStrm >> aColor; + maColorTable[ nIndex ] = aColor; + } + ExportPalette(); +} + +// FONT record - font information ============================================= +XclImpFont::XclImpFont( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mbHasCharSet( false ), + mbHasWstrn( true ), + mbHasAsian( false ), + mbHasCmplx( false ) +{ + SetAllUsedFlags( false ); +} + +XclImpFont::XclImpFont( const XclImpRoot& rRoot, const XclFontData& rFontData ) : + XclImpRoot( rRoot ) +{ + SetFontData( rFontData, false ); +} + +void XclImpFont::SetAllUsedFlags( bool bUsed ) +{ + mbFontNameUsed = mbHeightUsed = mbColorUsed = mbWeightUsed = mbEscapemUsed = + mbUnderlUsed = mbItalicUsed = mbStrikeUsed = mbOutlineUsed = mbShadowUsed = bUsed; +} + +void XclImpFont::SetFontData( const XclFontData& rFontData, bool bHasCharSet ) +{ + maData = rFontData; + mbHasCharSet = bHasCharSet; + if( !maData.maStyle.isEmpty() ) + { + if( SfxObjectShell* pDocShell = GetDocShell() ) + { + if( const SvxFontListItem* pInfoItem = static_cast< const SvxFontListItem* >( + pDocShell->GetItem( SID_ATTR_CHAR_FONTLIST ) ) ) + { + if( const FontList* pFontList = pInfoItem->GetFontList() ) + { + FontMetric aFontMetric( pFontList->Get( maData.maName, maData.maStyle ) ); + maData.SetScWeight( aFontMetric.GetWeight() ); + maData.SetScPosture( aFontMetric.GetItalic() ); + } + } + } + maData.maStyle.clear(); + } + GuessScriptType(); + SetAllUsedFlags( true ); +} + +rtl_TextEncoding XclImpFont::GetFontEncoding() const +{ + // #i63105# use text encoding from FONT record + // #i67768# BIFF2-BIFF4 FONT records do not contain character set + rtl_TextEncoding eFontEnc = mbHasCharSet ? maData.GetFontEncoding() : GetTextEncoding(); + return (eFontEnc == RTL_TEXTENCODING_DONTKNOW) ? GetTextEncoding() : eFontEnc; +} + +void XclImpFont::ReadFont( XclImpStream& rStrm ) +{ + switch( GetBiff() ) + { + case EXC_BIFF2: + ReadFontData2( rStrm ); + ReadFontName2( rStrm ); + break; + case EXC_BIFF3: + case EXC_BIFF4: + ReadFontData2( rStrm ); + ReadFontColor( rStrm ); + ReadFontName2( rStrm ); + break; + case EXC_BIFF5: + ReadFontData5( rStrm ); + ReadFontName2( rStrm ); + break; + case EXC_BIFF8: + ReadFontData5( rStrm ); + ReadFontName8( rStrm ); + break; + default: + DBG_ERROR_BIFF(); + return; + } + GuessScriptType(); + SetAllUsedFlags( true ); +} + +void XclImpFont::ReadEfont( XclImpStream& rStrm ) +{ + ReadFontColor( rStrm ); +} + +void XclImpFont::ReadCFFontBlock( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); + if( GetBiff() != EXC_BIFF8 ) + return; + + rStrm.Ignore( 64 ); + sal_uInt32 nHeight = rStrm.ReaduInt32(); + sal_uInt32 nStyle = rStrm.ReaduInt32(); + sal_uInt16 nWeight = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); //nEscapem + sal_uInt8 nUnderl = rStrm.ReaduInt8(); + rStrm.Ignore( 3 ); + sal_uInt32 nColor = rStrm.ReaduInt32(); + rStrm.Ignore( 4 ); + sal_uInt32 nFontFlags1 = rStrm.ReaduInt32(); + rStrm.Ignore( 4 ); //nFontFlags2 + sal_uInt32 nFontFlags3 = rStrm.ReaduInt32(); + rStrm.Ignore( 18 ); + + if( (mbHeightUsed = (nHeight <= 0x7FFF)) ) + maData.mnHeight = static_cast< sal_uInt16 >( nHeight ); + if( (mbWeightUsed = !::get_flag( nFontFlags1, EXC_CF_FONT_STYLE ) && (nWeight < 0x7FFF)) ) + maData.mnWeight = nWeight; + if( (mbItalicUsed = !::get_flag( nFontFlags1, EXC_CF_FONT_STYLE )) ) + maData.mbItalic = ::get_flag( nStyle, EXC_CF_FONT_STYLE ); + if( (mbUnderlUsed = !::get_flag( nFontFlags3, EXC_CF_FONT_UNDERL ) && (nUnderl <= 0x7F)) ) + maData.mnUnderline = nUnderl; + if( (mbColorUsed = (nColor <= 0x7FFF)) ) + maData.maColor = GetPalette().GetColor( static_cast< sal_uInt16 >( nColor ) ); + if( (mbStrikeUsed = !::get_flag( nFontFlags1, EXC_CF_FONT_STRIKEOUT )) ) + maData.mbStrikeout = ::get_flag( nStyle, EXC_CF_FONT_STRIKEOUT ); +} + +void XclImpFont::FillToItemSet( SfxItemSet& rItemSet, XclFontItemType eType, bool bSkipPoolDefs ) const +{ + // true = edit engine Which-IDs (EE_CHAR_*); false = Calc Which-IDs (ATTR_*) + bool bEE = eType != XclFontItemType::Cell; + +// item = the item to put into the item set +// sc_which = the Calc Which-ID of the item +// ee_which = the edit engine Which-ID of the item +#define PUTITEM( item, sc_which, ee_which ) \ + ScfTools::PutItem( rItemSet, item, (bEE ? (static_cast<sal_uInt16>(ee_which)) : (sc_which)), bSkipPoolDefs ) + +// Font item + if( mbFontNameUsed ) + { + rtl_TextEncoding eFontEnc = maData.GetFontEncoding(); + rtl_TextEncoding eTempTextEnc = (bEE && (eFontEnc == GetTextEncoding())) ? + ScfTools::GetSystemTextEncoding() : eFontEnc; + + //add corresponding pitch for FontFamily + FontPitch ePitch = PITCH_DONTKNOW; + FontFamily eFtFamily = maData.GetScFamily( GetTextEncoding() ); + switch( eFtFamily ) //refer http://msdn.microsoft.com/en-us/library/aa246306(v=VS.60).aspx + { + case FAMILY_ROMAN: ePitch = PITCH_VARIABLE; break; + case FAMILY_SWISS: ePitch = PITCH_VARIABLE; break; + case FAMILY_MODERN: ePitch = PITCH_FIXED; break; + default: break; + } + SvxFontItem aFontItem( eFtFamily , maData.maName, OUString(), ePitch, eTempTextEnc, ATTR_FONT ); + + // set only for valid script types + if( mbHasWstrn ) + PUTITEM( aFontItem, ATTR_FONT, EE_CHAR_FONTINFO ); + if( mbHasAsian ) + PUTITEM( aFontItem, ATTR_CJK_FONT, EE_CHAR_FONTINFO_CJK ); + if( mbHasCmplx ) + PUTITEM( aFontItem, ATTR_CTL_FONT, EE_CHAR_FONTINFO_CTL ); + } + +// Font height (for all script types) + if( mbHeightUsed ) + { + sal_Int32 nHeight = maData.mnHeight; + if( bEE && (eType != XclFontItemType::HeaderFooter) ) // do not convert header/footer height + nHeight = convertTwipToMm100(nHeight); + + SvxFontHeightItem aHeightItem( nHeight, 100, ATTR_FONT_HEIGHT ); + PUTITEM( aHeightItem, ATTR_FONT_HEIGHT, EE_CHAR_FONTHEIGHT ); + PUTITEM( aHeightItem, ATTR_CJK_FONT_HEIGHT, EE_CHAR_FONTHEIGHT_CJK ); + PUTITEM( aHeightItem, ATTR_CTL_FONT_HEIGHT, EE_CHAR_FONTHEIGHT_CTL ); + } + +// Font color - pass AUTO_COL to item + if( mbColorUsed ) + PUTITEM( SvxColorItem( maData.maColor, ATTR_FONT_COLOR ), ATTR_FONT_COLOR, EE_CHAR_COLOR ); + +// Font weight (for all script types) + if( mbWeightUsed ) + { + SvxWeightItem aWeightItem( maData.GetScWeight(), ATTR_FONT_WEIGHT ); + PUTITEM( aWeightItem, ATTR_FONT_WEIGHT, EE_CHAR_WEIGHT ); + PUTITEM( aWeightItem, ATTR_CJK_FONT_WEIGHT, EE_CHAR_WEIGHT_CJK ); + PUTITEM( aWeightItem, ATTR_CTL_FONT_WEIGHT, EE_CHAR_WEIGHT_CTL ); + } + +// Font underline + if( mbUnderlUsed ) + { + SvxUnderlineItem aUnderlItem( maData.GetScUnderline(), ATTR_FONT_UNDERLINE ); + PUTITEM( aUnderlItem, ATTR_FONT_UNDERLINE, EE_CHAR_UNDERLINE ); + } + +// Font posture (for all script types) + if( mbItalicUsed ) + { + SvxPostureItem aPostItem( maData.GetScPosture(), ATTR_FONT_POSTURE ); + PUTITEM( aPostItem, ATTR_FONT_POSTURE, EE_CHAR_ITALIC ); + PUTITEM( aPostItem, ATTR_CJK_FONT_POSTURE, EE_CHAR_ITALIC_CJK ); + PUTITEM( aPostItem, ATTR_CTL_FONT_POSTURE, EE_CHAR_ITALIC_CTL ); + } + +// Boolean attributes crossed out, contoured, shadowed + if( mbStrikeUsed ) + PUTITEM( SvxCrossedOutItem( maData.GetScStrikeout(), ATTR_FONT_CROSSEDOUT ), ATTR_FONT_CROSSEDOUT, EE_CHAR_STRIKEOUT ); + if( mbOutlineUsed ) + PUTITEM( SvxContourItem( maData.mbOutline, ATTR_FONT_CONTOUR ), ATTR_FONT_CONTOUR, EE_CHAR_OUTLINE ); + if( mbShadowUsed ) + PUTITEM( SvxShadowedItem( maData.mbShadow, ATTR_FONT_SHADOWED ), ATTR_FONT_SHADOWED, EE_CHAR_SHADOW ); + +// Super-/subscript: only on edit engine objects + if( mbEscapemUsed && bEE ) + rItemSet.Put( SvxEscapementItem( maData.GetScEscapement(), EE_CHAR_ESCAPEMENT ) ); + +#undef PUTITEM +} + +void XclImpFont::WriteFontProperties( ScfPropertySet& rPropSet, + XclFontPropSetType eType, const Color* pFontColor ) const +{ + GetFontPropSetHelper().WriteFontProperties( + rPropSet, eType, maData, mbHasWstrn, mbHasAsian, mbHasCmplx, pFontColor ); +} + +void XclImpFont::ReadFontData2( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + maData.mnHeight = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + + maData.mnWeight = ::get_flagvalue( nFlags, EXC_FONTATTR_BOLD, EXC_FONTWGHT_BOLD, EXC_FONTWGHT_NORMAL ); + maData.mnUnderline = ::get_flagvalue( nFlags, EXC_FONTATTR_UNDERLINE, EXC_FONTUNDERL_SINGLE, EXC_FONTUNDERL_NONE ); + maData.mbItalic = ::get_flag( nFlags, EXC_FONTATTR_ITALIC ); + maData.mbStrikeout = ::get_flag( nFlags, EXC_FONTATTR_STRIKEOUT ); + maData.mbOutline = ::get_flag( nFlags, EXC_FONTATTR_OUTLINE ); + maData.mbShadow = ::get_flag( nFlags, EXC_FONTATTR_SHADOW ); + mbHasCharSet = false; +} + +void XclImpFont::ReadFontData5( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + + maData.mnHeight = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + ReadFontColor( rStrm ); + maData.mnWeight = rStrm.ReaduInt16(); + maData.mnEscapem = rStrm.ReaduInt16(); + maData.mnUnderline = rStrm.ReaduInt8(); + maData.mnFamily = rStrm.ReaduInt8(); + maData.mnCharSet = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + + maData.mbItalic = ::get_flag( nFlags, EXC_FONTATTR_ITALIC ); + maData.mbStrikeout = ::get_flag( nFlags, EXC_FONTATTR_STRIKEOUT ); + maData.mbOutline = ::get_flag( nFlags, EXC_FONTATTR_OUTLINE ); + maData.mbShadow = ::get_flag( nFlags, EXC_FONTATTR_SHADOW ); + mbHasCharSet = maData.mnCharSet != 0; +} + +void XclImpFont::ReadFontColor( XclImpStream& rStrm ) +{ + maData.maColor = GetPalette().GetColor( rStrm.ReaduInt16() ); +} + +void XclImpFont::ReadFontName2( XclImpStream& rStrm ) +{ + maData.maName = rStrm.ReadByteString( false ); +} + +void XclImpFont::ReadFontName8( XclImpStream& rStrm ) +{ + maData.maName = rStrm.ReadUniString( rStrm.ReaduInt8() ); +} + +void XclImpFont::GuessScriptType() +{ + mbHasWstrn = true; + mbHasAsian = mbHasCmplx = false; + + // find the script types for which the font contains characters + OutputDevice* pPrinter = GetPrinter(); + if(!pPrinter) + return; + + vcl::Font aFont( maData.maName, Size( 0, 10 ) ); + FontCharMapRef xFontCharMap; + + pPrinter->SetFont( aFont ); + if( !pPrinter->GetFontCharMap( xFontCharMap ) ) + return; + + // CJK fonts + mbHasAsian = + xFontCharMap->HasChar( 0x3041 ) || // 3040-309F: Hiragana + xFontCharMap->HasChar( 0x30A1 ) || // 30A0-30FF: Katakana + xFontCharMap->HasChar( 0x3111 ) || // 3100-312F: Bopomofo + xFontCharMap->HasChar( 0x3131 ) || // 3130-318F: Hangul Compatibility Jamo + xFontCharMap->HasChar( 0x3301 ) || // 3300-33FF: CJK Compatibility + xFontCharMap->HasChar( 0x3401 ) || // 3400-4DBF: CJK Unified Ideographs Extension A + xFontCharMap->HasChar( 0x4E01 ) || // 4E00-9FFF: CJK Unified Ideographs + xFontCharMap->HasChar( 0x7E01 ) || // 4E00-9FFF: CJK Unified Ideographs + xFontCharMap->HasChar( 0xA001 ) || // A001-A48F: Yi Syllables + xFontCharMap->HasChar( 0xAC01 ) || // AC00-D7AF: Hangul Syllables + xFontCharMap->HasChar( 0xCC01 ) || // AC00-D7AF: Hangul Syllables + xFontCharMap->HasChar( 0xF901 ) || // F900-FAFF: CJK Compatibility Ideographs + xFontCharMap->HasChar( 0xFF71 ); // FF00-FFEF: Halfwidth/Fullwidth Forms + // CTL fonts + mbHasCmplx = + xFontCharMap->HasChar( 0x05D1 ) || // 0590-05FF: Hebrew + xFontCharMap->HasChar( 0x0631 ) || // 0600-06FF: Arabic + xFontCharMap->HasChar( 0x0721 ) || // 0700-074F: Syriac + xFontCharMap->HasChar( 0x0911 ) || // 0900-0DFF: Indic scripts + xFontCharMap->HasChar( 0x0E01 ) || // 0E00-0E7F: Thai + xFontCharMap->HasChar( 0xFB21 ) || // FB1D-FB4F: Hebrew Presentation Forms + xFontCharMap->HasChar( 0xFB51 ) || // FB50-FDFF: Arabic Presentation Forms-A + xFontCharMap->HasChar( 0xFE71 ); // FE70-FEFF: Arabic Presentation Forms-B + // Western fonts + mbHasWstrn = (!mbHasAsian && !mbHasCmplx) || xFontCharMap->HasChar( 'A' ); +} + +XclImpFontBuffer::XclImpFontBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maFont4( rRoot ), + maCtrlFont( rRoot ) +{ + Initialize(); + + // default font for form controls without own font information + XclFontData aCtrlFontData; + switch( GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + aCtrlFontData.maName = "Helv"; + aCtrlFontData.mnHeight = 160; + aCtrlFontData.mnWeight = EXC_FONTWGHT_BOLD; + break; + case EXC_BIFF8: + aCtrlFontData.maName = "Tahoma"; + aCtrlFontData.mnHeight = 160; + aCtrlFontData.mnWeight = EXC_FONTWGHT_NORMAL; + break; + default: + DBG_ERROR_BIFF(); + } + maCtrlFont.SetFontData( aCtrlFontData, false ); +} + +void XclImpFontBuffer::Initialize() +{ + maFontList.clear(); + + // application font for column width calculation, later filled with first font from font list + XclFontData aAppFontData; + aAppFontData.maName = "Arial"; + aAppFontData.mnHeight = 200; + aAppFontData.mnWeight = EXC_FONTWGHT_NORMAL; + UpdateAppFont( aAppFontData, false ); +} + +const XclImpFont* XclImpFontBuffer::GetFont( sal_uInt16 nFontIndex ) const +{ + /* Font with index 4 is not stored in an Excel file, but used e.g. by + BIFF5 form pushbutton objects. It is the bold default font. + This also means that entries above 4 are out by one in the list. */ + + if (nFontIndex == 4) + return &maFont4; + + if (nFontIndex < 4) + { + // Font ID is zero-based when it's less than 4. + return nFontIndex >= maFontList.size() ? nullptr : &maFontList[nFontIndex]; + } + + // Font ID is greater than 4. It is now 1-based. + return nFontIndex > maFontList.size() ? nullptr : &maFontList[nFontIndex-1]; +} + +void XclImpFontBuffer::ReadFont( XclImpStream& rStrm ) +{ + maFontList.emplace_back( GetRoot() ); + XclImpFont& rFont = maFontList.back(); + rFont.ReadFont( rStrm ); + + if( maFontList.size() == 1 ) + { + UpdateAppFont( rFont.GetFontData(), rFont.HasCharSet() ); + } +} + +void XclImpFontBuffer::ReadEfont( XclImpStream& rStrm ) +{ + if( !maFontList.empty() ) + maFontList.back().ReadEfont( rStrm ); +} + +void XclImpFontBuffer::FillToItemSet( + SfxItemSet& rItemSet, XclFontItemType eType, + sal_uInt16 nFontIdx, bool bSkipPoolDefs ) const +{ + if( const XclImpFont* pFont = GetFont( nFontIdx ) ) + pFont->FillToItemSet( rItemSet, eType, bSkipPoolDefs ); +} + +void XclImpFontBuffer::WriteFontProperties( ScfPropertySet& rPropSet, + XclFontPropSetType eType, sal_uInt16 nFontIdx, const Color* pFontColor ) const +{ + if( const XclImpFont* pFont = GetFont( nFontIdx ) ) + pFont->WriteFontProperties( rPropSet, eType, pFontColor ); +} + +void XclImpFontBuffer::WriteDefaultCtrlFontProperties( ScfPropertySet& rPropSet ) const +{ + maCtrlFont.WriteFontProperties( rPropSet, EXC_FONTPROPSET_CONTROL ); +} + +void XclImpFontBuffer::UpdateAppFont( const XclFontData& rFontData, bool bHasCharSet ) +{ + maAppFont = rFontData; + // #i3006# Calculate the width of '0' from first font and current printer. + SetCharWidth( maAppFont ); + + // font 4 is bold font 0 + XclFontData aFont4Data( maAppFont ); + aFont4Data.mnWeight = EXC_FONTWGHT_BOLD; + maFont4.SetFontData( aFont4Data, bHasCharSet ); +} + +// FORMAT record - number formats ============================================= + +XclImpNumFmtBuffer::XclImpNumFmtBuffer( const XclImpRoot& rRoot ) : + XclNumFmtBuffer( rRoot ), + XclImpRoot( rRoot ), + mnNextXclIdx( 0 ) +{ +} + +void XclImpNumFmtBuffer::Initialize() +{ + maIndexMap.clear(); + mnNextXclIdx = 0; + InitializeImport(); // base class +} + +void XclImpNumFmtBuffer::ReadFormat( XclImpStream& rStrm ) +{ + OUString aFormat; + switch( GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + aFormat = rStrm.ReadByteString( false ); + break; + + case EXC_BIFF4: + rStrm.Ignore( 2 ); // in BIFF4 the index field exists, but is undefined + aFormat = rStrm.ReadByteString( false ); + break; + + case EXC_BIFF5: + mnNextXclIdx = rStrm.ReaduInt16(); + aFormat = rStrm.ReadByteString( false ); + break; + + case EXC_BIFF8: + mnNextXclIdx = rStrm.ReaduInt16(); + aFormat = rStrm.ReadUniString(); + break; + + default: + DBG_ERROR_BIFF(); + return; + } + + if( mnNextXclIdx < 0xFFFF ) + { + InsertFormat( mnNextXclIdx, aFormat ); + ++mnNextXclIdx; + } +} + +sal_uInt16 XclImpNumFmtBuffer::ReadCFFormat( XclImpStream& rStrm, bool bIFmt ) +{ + // internal number format ? + if(bIFmt) + { + rStrm.Ignore(1); + sal_uInt8 nIndex; + nIndex = rStrm.ReaduInt8(); + return nIndex; + } + else + { + OUString aFormat = rStrm.ReadUniString(); + InsertFormat( mnNextXclIdx, aFormat ); + ++mnNextXclIdx; + return mnNextXclIdx - 1; + } +} + +void XclImpNumFmtBuffer::CreateScFormats() +{ + OSL_ENSURE( maIndexMap.empty(), "XclImpNumFmtBuffer::CreateScFormats - already created" ); + + SvNumberFormatter& rFormatter = GetFormatter(); + for( const auto& [rXclNumFmt, rNumFmt] : GetFormatMap() ) + { + // insert/convert the Excel number format + sal_uInt32 nKey; + if( !rNumFmt.maFormat.isEmpty() ) + { + OUString aFormat( rNumFmt.maFormat ); + sal_Int32 nCheckPos; + SvNumFormatType nType = SvNumFormatType::DEFINED; + rFormatter.PutandConvertEntry( aFormat, nCheckPos, + nType, nKey, LANGUAGE_ENGLISH_US, rNumFmt.meLanguage, false); + } + else + nKey = rFormatter.GetFormatIndex( rNumFmt.meOffset, rNumFmt.meLanguage ); + + // insert the resulting format key into the Excel->Calc index map + maIndexMap[ rXclNumFmt ] = nKey; + } +} + +sal_uInt32 XclImpNumFmtBuffer::GetScFormat( sal_uInt16 nXclNumFmt ) const +{ + XclImpIndexMap::const_iterator aIt = maIndexMap.find( nXclNumFmt ); + return (aIt != maIndexMap.end()) ? aIt->second : NUMBERFORMAT_ENTRY_NOT_FOUND; +} + +void XclImpNumFmtBuffer::FillToItemSet( SfxItemSet& rItemSet, sal_uInt16 nXclNumFmt, bool bSkipPoolDefs ) const +{ + sal_uInt32 nScNumFmt = GetScFormat( nXclNumFmt ); + if( nScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND ) + nScNumFmt = GetStdScNumFmt(); + FillScFmtToItemSet( rItemSet, nScNumFmt, bSkipPoolDefs ); +} + +void XclImpNumFmtBuffer::FillScFmtToItemSet( SfxItemSet& rItemSet, sal_uInt32 nScNumFmt, bool bSkipPoolDefs ) const +{ + OSL_ENSURE( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND, "XclImpNumFmtBuffer::FillScFmtToItemSet - invalid number format" ); + ScfTools::PutItem( rItemSet, SfxUInt32Item( ATTR_VALUE_FORMAT, nScNumFmt ), bSkipPoolDefs ); + if( rItemSet.GetItemState( ATTR_VALUE_FORMAT, false ) == SfxItemState::SET ) + ScGlobal::AddLanguage( rItemSet, GetFormatter() ); +} + +// XF, STYLE record - Cell formatting ========================================= + +void XclImpCellProt::FillFromXF2( sal_uInt8 nNumFmt ) +{ + mbLocked = ::get_flag( nNumFmt, EXC_XF2_LOCKED ); + mbHidden = ::get_flag( nNumFmt, EXC_XF2_HIDDEN ); +} + +void XclImpCellProt::FillFromXF3( sal_uInt16 nProt ) +{ + mbLocked = ::get_flag( nProt, EXC_XF_LOCKED ); + mbHidden = ::get_flag( nProt, EXC_XF_HIDDEN ); +} + +void XclImpCellProt::FillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs ) const +{ + ScfTools::PutItem( rItemSet, ScProtectionAttr( mbLocked, mbHidden ), bSkipPoolDefs ); +} + +void XclImpCellAlign::FillFromXF2( sal_uInt8 nFlags ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nFlags, 0, 3 ); +} + +void XclImpCellAlign::FillFromXF3( sal_uInt16 nAlign ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mbLineBreak = ::get_flag( nAlign, EXC_XF_LINEBREAK ); // new in BIFF3 +} + +void XclImpCellAlign::FillFromXF4( sal_uInt16 nAlign ) +{ + FillFromXF3( nAlign ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 2 ); // new in BIFF4 + mnOrient = ::extract_value< sal_uInt8 >( nAlign, 6, 2 ); // new in BIFF4 +} + +void XclImpCellAlign::FillFromXF5( sal_uInt16 nAlign ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 3 ); + mbLineBreak = ::get_flag( nAlign, EXC_XF_LINEBREAK ); + mnOrient = ::extract_value< sal_uInt8 >( nAlign, 8, 2 ); +} + +void XclImpCellAlign::FillFromXF8( sal_uInt16 nAlign, sal_uInt16 nMiscAttrib ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 3 ); + mbLineBreak = ::get_flag( nAlign, EXC_XF_LINEBREAK ); + mnRotation = ::extract_value< sal_uInt8 >( nAlign, 8, 8 ); // new in BIFF8 + mnIndent = ::extract_value< sal_uInt8 >( nMiscAttrib, 0, 4 ); // new in BIFF8 + mbShrink = ::get_flag( nMiscAttrib, EXC_XF8_SHRINK ); // new in BIFF8 + mnTextDir = ::extract_value< sal_uInt8 >( nMiscAttrib, 6, 2 ); // new in BIFF8 +} + +void XclImpCellAlign::FillFromCF( sal_uInt16 nAlign, sal_uInt16 nMiscAttrib ) +{ + mnHorAlign = extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mbLineBreak = get_flag< sal_uInt8 >( nAlign, EXC_XF_LINEBREAK ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 3 ); + mnRotation = ::extract_value< sal_uInt8 >( nAlign, 8, 8 ); + mnIndent = ::extract_value< sal_uInt8 >( nMiscAttrib, 0, 4 ); + mbShrink = ::get_flag( nMiscAttrib, EXC_XF8_SHRINK ); + mnTextDir = ::extract_value< sal_uInt8 >( nMiscAttrib, 6, 2 ); +} + +void XclImpCellAlign::FillToItemSet( SfxItemSet& rItemSet, const XclImpFont* pFont, bool bSkipPoolDefs ) const +{ + // horizontal alignment + ScfTools::PutItem( rItemSet, SvxHorJustifyItem( GetScHorAlign(), ATTR_HOR_JUSTIFY ), bSkipPoolDefs ); + ScfTools::PutItem( rItemSet, SvxJustifyMethodItem( GetScHorJustifyMethod(), ATTR_HOR_JUSTIFY_METHOD ), bSkipPoolDefs ); + + // text wrap (#i74508# always if vertical alignment is justified or distributed) + bool bLineBreak = mbLineBreak || (mnVerAlign == EXC_XF_VER_JUSTIFY) || (mnVerAlign == EXC_XF_VER_DISTRIB); + ScfTools::PutItem( rItemSet, ScLineBreakCell( bLineBreak ), bSkipPoolDefs ); + + // vertical alignment + ScfTools::PutItem( rItemSet, SvxVerJustifyItem( GetScVerAlign(), ATTR_VER_JUSTIFY ), bSkipPoolDefs ); + ScfTools::PutItem( rItemSet, SvxJustifyMethodItem( GetScVerJustifyMethod(), ATTR_VER_JUSTIFY_METHOD ), bSkipPoolDefs ); + + // indent + sal_uInt16 nScIndent = mnIndent * 200; // 1 Excel unit == 10 pt == 200 twips + ScfTools::PutItem( rItemSet, ScIndentItem( nScIndent ), bSkipPoolDefs ); + + // shrink to fit + ScfTools::PutItem( rItemSet, ScShrinkToFitCell( mbShrink ), bSkipPoolDefs ); + + // text orientation/rotation (BIFF2-BIFF7 sets mnOrient) + sal_uInt8 nXclRot = (mnOrient == EXC_ORIENT_NONE) ? mnRotation : XclTools::GetXclRotFromOrient( mnOrient ); + bool bStacked = (nXclRot == EXC_ROT_STACKED); + ScfTools::PutItem( rItemSet, ScVerticalStackCell( bStacked ), bSkipPoolDefs ); + // set an angle in the range from -90 to 90 degrees + Degree100 nAngle = XclTools::GetScRotation( nXclRot, 0_deg100 ); + ScfTools::PutItem( rItemSet, ScRotateValueItem( nAngle ), bSkipPoolDefs ); + // set "Use asian vertical layout", if cell is stacked and font contains CKJ characters + bool bAsianVert = bStacked && pFont && pFont->HasAsianChars(); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_VERTICAL_ASIAN, bAsianVert ), bSkipPoolDefs ); + + // CTL text direction + ScfTools::PutItem( rItemSet, SvxFrameDirectionItem( GetScFrameDir(), ATTR_WRITINGDIR ), bSkipPoolDefs ); +} + +XclImpCellBorder::XclImpCellBorder() +{ + SetUsedFlags( false, false ); +} + +void XclImpCellBorder::SetUsedFlags( bool bOuterUsed, bool bDiagUsed ) +{ + mbLeftUsed = mbRightUsed = mbTopUsed = mbBottomUsed = bOuterUsed; + mbDiagUsed = bDiagUsed; +} + +void XclImpCellBorder::FillFromXF2( sal_uInt8 nFlags ) +{ + mnLeftLine = ::get_flagvalue( nFlags, EXC_XF2_LEFTLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnRightLine = ::get_flagvalue( nFlags, EXC_XF2_RIGHTLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnTopLine = ::get_flagvalue( nFlags, EXC_XF2_TOPLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnBottomLine = ::get_flagvalue( nFlags, EXC_XF2_BOTTOMLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnLeftColor = mnRightColor = mnTopColor = mnBottomColor = EXC_COLOR_BIFF2_BLACK; + SetUsedFlags( true, false ); +} + +void XclImpCellBorder::FillFromXF3( sal_uInt32 nBorder ) +{ + mnTopLine = ::extract_value< sal_uInt8 >( nBorder, 0, 3 ); + mnLeftLine = ::extract_value< sal_uInt8 >( nBorder, 8, 3 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nBorder, 16, 3 ); + mnRightLine = ::extract_value< sal_uInt8 >( nBorder, 24, 3 ); + mnTopColor = ::extract_value< sal_uInt16 >( nBorder, 3, 5 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nBorder, 11, 5 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nBorder, 19, 5 ); + mnRightColor = ::extract_value< sal_uInt16 >( nBorder, 27, 5 ); + SetUsedFlags( true, false ); +} + +void XclImpCellBorder::FillFromXF5( sal_uInt32 nBorder, sal_uInt32 nArea ) +{ + mnTopLine = ::extract_value< sal_uInt8 >( nBorder, 0, 3 ); + mnLeftLine = ::extract_value< sal_uInt8 >( nBorder, 3, 3 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nArea, 22, 3 ); + mnRightLine = ::extract_value< sal_uInt8 >( nBorder, 6, 3 ); + mnTopColor = ::extract_value< sal_uInt16 >( nBorder, 9, 7 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nBorder, 16, 7 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nArea, 25, 7 ); + mnRightColor = ::extract_value< sal_uInt16 >( nBorder, 23, 7 ); + SetUsedFlags( true, false ); +} + +void XclImpCellBorder::FillFromXF8( sal_uInt32 nBorder1, sal_uInt32 nBorder2 ) +{ + mnLeftLine = ::extract_value< sal_uInt8 >( nBorder1, 0, 4 ); + mnRightLine = ::extract_value< sal_uInt8 >( nBorder1, 4, 4 ); + mnTopLine = ::extract_value< sal_uInt8 >( nBorder1, 8, 4 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nBorder1, 12, 4 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nBorder1, 16, 7 ); + mnRightColor = ::extract_value< sal_uInt16 >( nBorder1, 23, 7 ); + mnTopColor = ::extract_value< sal_uInt16 >( nBorder2, 0, 7 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nBorder2, 7, 7 ); + mbDiagTLtoBR = ::get_flag( nBorder1, EXC_XF_DIAGONAL_TL_TO_BR ); + mbDiagBLtoTR = ::get_flag( nBorder1, EXC_XF_DIAGONAL_BL_TO_TR ); + if( mbDiagTLtoBR || mbDiagBLtoTR ) + { + mnDiagLine = ::extract_value< sal_uInt8 >( nBorder2, 21, 4 ); + mnDiagColor = ::extract_value< sal_uInt16 >( nBorder2, 14, 7 ); + } + SetUsedFlags( true, true ); +} + +void XclImpCellBorder::FillFromCF8( sal_uInt16 nLineStyle, sal_uInt32 nLineColor, sal_uInt32 nFlags ) +{ + mnLeftLine = ::extract_value< sal_uInt8 >( nLineStyle, 0, 4 ); + mnRightLine = ::extract_value< sal_uInt8 >( nLineStyle, 4, 4 ); + mnTopLine = ::extract_value< sal_uInt8 >( nLineStyle, 8, 4 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nLineStyle, 12, 4 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nLineColor, 0, 7 ); + mnRightColor = ::extract_value< sal_uInt16 >( nLineColor, 7, 7 ); + mnTopColor = ::extract_value< sal_uInt16 >( nLineColor, 16, 7 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nLineColor, 23, 7 ); + mbLeftUsed = !::get_flag( nFlags, EXC_CF_BORDER_LEFT ); + mbRightUsed = !::get_flag( nFlags, EXC_CF_BORDER_RIGHT ); + mbTopUsed = !::get_flag( nFlags, EXC_CF_BORDER_TOP ); + mbBottomUsed = !::get_flag( nFlags, EXC_CF_BORDER_BOTTOM ); + mbDiagUsed = false; +} + +bool XclImpCellBorder::HasAnyOuterBorder() const +{ + return + (mbLeftUsed && (mnLeftLine != EXC_LINE_NONE)) || + (mbRightUsed && (mnRightLine != EXC_LINE_NONE)) || + (mbTopUsed && (mnTopLine != EXC_LINE_NONE)) || + (mbBottomUsed && (mnBottomLine != EXC_LINE_NONE)); +} + +namespace { + +/** Converts the passed line style to a ::editeng::SvxBorderLine, or returns false, if style is "no line". */ +bool lclConvertBorderLine( ::editeng::SvxBorderLine& rLine, const XclImpPalette& rPalette, sal_uInt8 nXclLine, sal_uInt16 nXclColor ) +{ + static const sal_uInt16 ppnLineParam[][ 4 ] = + { + // outer width, type + { 0, table::BorderLineStyle::SOLID }, // 0 = none + { EXC_BORDER_THIN, table::BorderLineStyle::SOLID }, // 1 = thin + { EXC_BORDER_MEDIUM, table::BorderLineStyle::SOLID }, // 2 = medium + { EXC_BORDER_THIN, table::BorderLineStyle::FINE_DASHED }, // 3 = dashed + { EXC_BORDER_THIN, table::BorderLineStyle::DOTTED }, // 4 = dotted + { EXC_BORDER_THICK, table::BorderLineStyle::SOLID }, // 5 = thick + { EXC_BORDER_THICK, table::BorderLineStyle::DOUBLE_THIN }, // 6 = double + { EXC_BORDER_HAIR, table::BorderLineStyle::SOLID }, // 7 = hair + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASHED }, // 8 = med dash + { EXC_BORDER_THIN, table::BorderLineStyle::DASH_DOT }, // 9 = thin dashdot + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASH_DOT }, // A = med dashdot + { EXC_BORDER_THIN, table::BorderLineStyle::DASH_DOT_DOT }, // B = thin dashdotdot + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASH_DOT_DOT }, // C = med dashdotdot + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASH_DOT } // D = med slant dashdot + }; + + if( nXclLine == EXC_LINE_NONE ) + return false; + if( nXclLine >= SAL_N_ELEMENTS( ppnLineParam ) ) + nXclLine = EXC_LINE_THIN; + + rLine.SetColor( rPalette.GetColor( nXclColor ) ); + rLine.SetWidth( ppnLineParam[ nXclLine ][ 0 ] ); + rLine.SetBorderLineStyle( static_cast< SvxBorderLineStyle>( + ppnLineParam[ nXclLine ][ 1 ]) ); + return true; +} + +} // namespace + +void XclImpCellBorder::FillToItemSet( SfxItemSet& rItemSet, const XclImpPalette& rPalette, bool bSkipPoolDefs ) const +{ + if( mbLeftUsed || mbRightUsed || mbTopUsed || mbBottomUsed ) + { + SvxBoxItem aBoxItem( ATTR_BORDER ); + ::editeng::SvxBorderLine aLine; + if( mbLeftUsed && lclConvertBorderLine( aLine, rPalette, mnLeftLine, mnLeftColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::LEFT ); + if( mbRightUsed && lclConvertBorderLine( aLine, rPalette, mnRightLine, mnRightColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + if( mbTopUsed && lclConvertBorderLine( aLine, rPalette, mnTopLine, mnTopColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::TOP ); + if( mbBottomUsed && lclConvertBorderLine( aLine, rPalette, mnBottomLine, mnBottomColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + ScfTools::PutItem( rItemSet, aBoxItem, bSkipPoolDefs ); + } + if( !mbDiagUsed ) + return; + + SvxLineItem aTLBRItem( ATTR_BORDER_TLBR ); + SvxLineItem aBLTRItem( ATTR_BORDER_BLTR ); + ::editeng::SvxBorderLine aLine; + if( lclConvertBorderLine( aLine, rPalette, mnDiagLine, mnDiagColor ) ) + { + if( mbDiagTLtoBR ) + aTLBRItem.SetLine( &aLine ); + if( mbDiagBLtoTR ) + aBLTRItem.SetLine( &aLine ); + } + ScfTools::PutItem( rItemSet, aTLBRItem, bSkipPoolDefs ); + ScfTools::PutItem( rItemSet, aBLTRItem, bSkipPoolDefs ); +} + +XclImpCellArea::XclImpCellArea() +{ + SetUsedFlags( false ); +} + +void XclImpCellArea::SetUsedFlags( bool bUsed ) +{ + mbForeUsed = mbBackUsed = mbPattUsed = bUsed; +} + +void XclImpCellArea::FillFromXF2( sal_uInt8 nFlags ) +{ + mnPattern = ::get_flagvalue( nFlags, EXC_XF2_BACKGROUND, EXC_PATT_12_5_PERC, EXC_PATT_NONE ); + mnForeColor = EXC_COLOR_BIFF2_BLACK; + mnBackColor = EXC_COLOR_BIFF2_WHITE; + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromXF3( sal_uInt16 nArea ) +{ + mnPattern = ::extract_value< sal_uInt8 >( nArea, 0, 6 ); + mnForeColor = ::extract_value< sal_uInt16 >( nArea, 6, 5 ); + mnBackColor = ::extract_value< sal_uInt16 >( nArea, 11, 5 ); + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromXF5( sal_uInt32 nArea ) +{ + mnPattern = ::extract_value< sal_uInt8 >( nArea, 16, 6 ); + mnForeColor = ::extract_value< sal_uInt16 >( nArea, 0, 7 ); + mnBackColor = ::extract_value< sal_uInt16 >( nArea, 7, 7 ); + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromXF8( sal_uInt32 nBorder2, sal_uInt16 nArea ) +{ + mnPattern = ::extract_value< sal_uInt8 >( nBorder2, 26, 6 ); + mnForeColor = ::extract_value< sal_uInt16 >( nArea, 0, 7 ); + mnBackColor = ::extract_value< sal_uInt16 >( nArea, 7, 7 ); + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromCF8( sal_uInt16 nPattern, sal_uInt16 nColor, sal_uInt32 nFlags ) +{ + mnForeColor = ::extract_value< sal_uInt16 >( nColor, 0, 7 ); + mnBackColor = ::extract_value< sal_uInt16 >( nColor, 7, 7 ); + mnPattern = ::extract_value< sal_uInt8 >( nPattern, 10, 6 ); + mbForeUsed = !::get_flag( nFlags, EXC_CF_AREA_FGCOLOR ); + mbBackUsed = !::get_flag( nFlags, EXC_CF_AREA_BGCOLOR ); + mbPattUsed = !::get_flag( nFlags, EXC_CF_AREA_PATTERN ); + + if( mbBackUsed && (!mbPattUsed || (mnPattern == EXC_PATT_SOLID)) ) + { + mnForeColor = mnBackColor; + mnPattern = EXC_PATT_SOLID; + mbForeUsed = mbPattUsed = true; + } + else if( !mbBackUsed && mbPattUsed && (mnPattern == EXC_PATT_SOLID) ) + { + mbPattUsed = false; + } +} + +void XclImpCellArea::FillToItemSet( SfxItemSet& rItemSet, const XclImpPalette& rPalette, bool bSkipPoolDefs ) const +{ + if( !mbPattUsed ) // colors may be both unused in cond. formats + return; + + SvxBrushItem aBrushItem( ATTR_BACKGROUND ); + + // do not use IsTransparent() - old Calc filter writes transparency with different color indexes + if( mnPattern == EXC_PATT_NONE ) + { + aBrushItem.SetColor( COL_TRANSPARENT ); + } + else + { + Color aFore( rPalette.GetColor( mbForeUsed ? mnForeColor : EXC_COLOR_WINDOWTEXT ) ); + Color aBack( rPalette.GetColor( mbBackUsed ? mnBackColor : EXC_COLOR_WINDOWBACK ) ); + aBrushItem.SetColor( XclTools::GetPatternColor( aFore, aBack, mnPattern ) ); + } + + ScfTools::PutItem( rItemSet, aBrushItem, bSkipPoolDefs ); +} + +XclImpXF::XclImpXF( const XclImpRoot& rRoot ) : + XclXFBase( true ), // default is cell XF + XclImpRoot( rRoot ), + mpStyleSheet( nullptr ), + mnXclNumFmt( 0 ), + mnXclFont( 0 ) +{ +} + +XclImpXF::~XclImpXF() +{ +} + +void XclImpXF::ReadXF2( XclImpStream& rStrm ) +{ + sal_uInt8 nReadFont, nReadNumFmt, nFlags; + nReadFont = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + nReadNumFmt = rStrm.ReaduInt8(); + nFlags = rStrm.ReaduInt8(); + + // XF type always cell, no parent, used flags always true + SetAllUsedFlags( true ); + + // attributes + maProtection.FillFromXF2( nReadNumFmt ); + mnXclFont = nReadFont; + mnXclNumFmt = nReadNumFmt & EXC_XF2_VALFMT_MASK; + maAlignment.FillFromXF2( nFlags ); + maBorder.FillFromXF2( nFlags ); + maArea.FillFromXF2( nFlags ); +} + +void XclImpXF::ReadXF3( XclImpStream& rStrm ) +{ + sal_uInt32 nBorder; + sal_uInt16 nTypeProt, nAlign, nArea; + sal_uInt8 nReadFont, nReadNumFmt; + nReadFont = rStrm.ReaduInt8(); + nReadNumFmt = rStrm.ReaduInt8(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nArea = rStrm.ReaduInt16(); + nBorder = rStrm.ReaduInt32(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); // new in BIFF3 + mnParent = ::extract_value< sal_uInt16 >( nAlign, 4, 12 ); // new in BIFF3 + SetUsedFlags( ::extract_value< sal_uInt8 >( nTypeProt, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + mnXclFont = nReadFont; + mnXclNumFmt = nReadNumFmt; + maAlignment.FillFromXF3( nAlign ); + maBorder.FillFromXF3( nBorder ); + maArea.FillFromXF3( nArea ); // new in BIFF3 +} + +void XclImpXF::ReadXF4( XclImpStream& rStrm ) +{ + sal_uInt32 nBorder; + sal_uInt16 nTypeProt, nAlign, nArea; + sal_uInt8 nReadFont, nReadNumFmt; + nReadFont = rStrm.ReaduInt8(); + nReadNumFmt = rStrm.ReaduInt8(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nArea = rStrm.ReaduInt16(); + nBorder = rStrm.ReaduInt32(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); + mnParent = ::extract_value< sal_uInt16 >( nTypeProt, 4, 12 ); + SetUsedFlags( ::extract_value< sal_uInt8 >( nAlign, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + mnXclFont = nReadFont; + mnXclNumFmt = nReadNumFmt; + maAlignment.FillFromXF4( nAlign ); + maBorder.FillFromXF3( nBorder ); + maArea.FillFromXF3( nArea ); +} + +void XclImpXF::ReadXF5( XclImpStream& rStrm ) +{ + sal_uInt32 nArea, nBorder; + sal_uInt16 nTypeProt, nAlign; + mnXclFont = rStrm.ReaduInt16(); + mnXclNumFmt = rStrm.ReaduInt16(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nArea = rStrm.ReaduInt32(); + nBorder = rStrm.ReaduInt32(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); + mnParent = ::extract_value< sal_uInt16 >( nTypeProt, 4, 12 ); + SetUsedFlags( ::extract_value< sal_uInt8 >( nAlign, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + maAlignment.FillFromXF5( nAlign ); + maBorder.FillFromXF5( nBorder, nArea ); + maArea.FillFromXF5( nArea ); +} + +void XclImpXF::ReadXF8( XclImpStream& rStrm ) +{ + sal_uInt32 nBorder1, nBorder2; + sal_uInt16 nTypeProt, nAlign, nMiscAttrib, nArea; + mnXclFont = rStrm.ReaduInt16(); + mnXclNumFmt = rStrm.ReaduInt16(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nMiscAttrib = rStrm.ReaduInt16(); + nBorder1 = rStrm.ReaduInt32(); + nBorder2 = rStrm.ReaduInt32( ); + nArea = rStrm.ReaduInt16(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); + mnParent = ::extract_value< sal_uInt16 >( nTypeProt, 4, 12 ); + SetUsedFlags( ::extract_value< sal_uInt8 >( nMiscAttrib, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + maAlignment.FillFromXF8( nAlign, nMiscAttrib ); + maBorder.FillFromXF8( nBorder1, nBorder2 ); + maArea.FillFromXF8( nBorder2, nArea ); +} + +void XclImpXF::ReadXF( XclImpStream& rStrm ) +{ + switch( GetBiff() ) + { + case EXC_BIFF2: ReadXF2( rStrm ); break; + case EXC_BIFF3: ReadXF3( rStrm ); break; + case EXC_BIFF4: ReadXF4( rStrm ); break; + case EXC_BIFF5: ReadXF5( rStrm ); break; + case EXC_BIFF8: ReadXF8( rStrm ); break; + default: DBG_ERROR_BIFF(); + } +} + +const ScPatternAttr& XclImpXF::CreatePattern( bool bSkipPoolDefs ) +{ + if( mpPattern ) + return *mpPattern; + + // create new pattern attribute set + mpPattern.reset( new ScPatternAttr( GetDoc().GetPool() ) ); + SfxItemSet& rItemSet = mpPattern->GetItemSet(); + XclImpXF* pParentXF = IsCellXF() ? GetXFBuffer().GetXF( mnParent ) : nullptr; + + // parent cell style + if( IsCellXF() && !mpStyleSheet ) + { + mpStyleSheet = GetXFBuffer().CreateStyleSheet( mnParent ); + + /* Enables mb***Used flags, if the formatting attributes differ from + the passed XF record. In cell XFs Excel uses the cell attributes, + if they differ from the parent style XF. + ...or if the respective flag is not set in parent style XF. */ + if( pParentXF ) + { + if( !mbProtUsed ) + mbProtUsed = !pParentXF->mbProtUsed || !(maProtection == pParentXF->maProtection); + if( !mbFontUsed ) + mbFontUsed = !pParentXF->mbFontUsed || (mnXclFont != pParentXF->mnXclFont); + if( !mbFmtUsed ) + mbFmtUsed = !pParentXF->mbFmtUsed || (mnXclNumFmt != pParentXF->mnXclNumFmt); + if( !mbAlignUsed ) + mbAlignUsed = !pParentXF->mbAlignUsed || !(maAlignment == pParentXF->maAlignment); + if( !mbBorderUsed ) + mbBorderUsed = !pParentXF->mbBorderUsed || !(maBorder == pParentXF->maBorder); + if( !mbAreaUsed ) + mbAreaUsed = !pParentXF->mbAreaUsed || !(maArea == pParentXF->maArea); + } + } + + // cell protection + if( mbProtUsed ) + maProtection.FillToItemSet( rItemSet, bSkipPoolDefs ); + + // font + if( mbFontUsed ) + GetFontBuffer().FillToItemSet( rItemSet, XclFontItemType::Cell, mnXclFont, bSkipPoolDefs ); + + // value format + if( mbFmtUsed ) + { + GetNumFmtBuffer().FillToItemSet( rItemSet, mnXclNumFmt, bSkipPoolDefs ); + // Trace occurrences of Windows date formats + GetTracer().TraceDates( mnXclNumFmt ); + } + + // alignment + if( mbAlignUsed ) + maAlignment.FillToItemSet( rItemSet, GetFontBuffer().GetFont( mnXclFont ), bSkipPoolDefs ); + + // border + if( mbBorderUsed ) + { + maBorder.FillToItemSet( rItemSet, GetPalette(), bSkipPoolDefs ); + GetTracer().TraceBorderLineStyle(maBorder.mnLeftLine > EXC_LINE_HAIR || + maBorder.mnRightLine > EXC_LINE_HAIR || maBorder.mnTopLine > EXC_LINE_HAIR || + maBorder.mnBottomLine > EXC_LINE_HAIR ); + } + + // area + if( mbAreaUsed ) + { + maArea.FillToItemSet( rItemSet, GetPalette(), bSkipPoolDefs ); + GetTracer().TraceFillPattern(maArea.mnPattern != EXC_PATT_NONE && + maArea.mnPattern != EXC_PATT_SOLID); + } + + /* #i38709# Decide which rotation reference mode to use. If any outer + border line of the cell is set (either explicitly or via cell style), + and the cell contents are rotated, set rotation reference to bottom of + cell. This causes the borders to be painted rotated with the text. */ + if( mbAlignUsed || mbBorderUsed ) + { + SvxRotateMode eRotateMode = SVX_ROTATE_MODE_STANDARD; + const XclImpCellAlign* pAlign = mbAlignUsed ? &maAlignment : (pParentXF ? &pParentXF->maAlignment : nullptr); + const XclImpCellBorder* pBorder = mbBorderUsed ? &maBorder : (pParentXF ? &pParentXF->maBorder : nullptr); + if( pAlign && pBorder && (0 < pAlign->mnRotation) && (pAlign->mnRotation <= 180) && pBorder->HasAnyOuterBorder() ) + eRotateMode = SVX_ROTATE_MODE_BOTTOM; + ScfTools::PutItem( rItemSet, SvxRotateModeItem( eRotateMode, ATTR_ROTATE_MODE ), bSkipPoolDefs ); + } + + // Excel's cell margins are different from Calc's default margins. + SvxMarginItem aItem(40, 40, 40, 40, ATTR_MARGIN); + ScfTools::PutItem(rItemSet, aItem, bSkipPoolDefs); + + return *mpPattern; +} + +void XclImpXF::ApplyPatternToAttrVector( + std::vector<ScAttrEntry>& rAttrs, SCROW nRow1, SCROW nRow2, sal_uInt32 nForceScNumFmt) +{ + // force creation of cell style and hard formatting, do it here to have mpStyleSheet + CreatePattern(); + ScPatternAttr& rPat = *mpPattern; + + // insert into document + ScDocument& rDoc = GetDoc(); + + if (IsCellXF()) + { + if (mpStyleSheet) + { + // Apply style sheet. Don't clear the direct formats. + rPat.SetStyleSheet(mpStyleSheet, false); + } + else + { + // When the cell format is not associated with any style, use the + // 'Default' style. Some buggy XLS docs generated by apps other + // than Excel (such as 1C) may not have any built-in styles at + // all. + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + if (pStylePool) + { + ScStyleSheet* pStyleSheet = static_cast<ScStyleSheet*>( + pStylePool->Find( + ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para)); + + if (pStyleSheet) + rPat.SetStyleSheet(pStyleSheet, false); + } + + } + } + + if (nForceScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + ScPatternAttr aNumPat(rDoc.GetPool()); + GetNumFmtBuffer().FillScFmtToItemSet(aNumPat.GetItemSet(), nForceScNumFmt); + rPat.GetItemSet().Put(aNumPat.GetItemSet()); + } + + // Make sure we skip unnamed styles. + if (!rPat.GetStyleName()) + return; + + // Check for a gap between the last entry and this one. + bool bHasGap = false; + if (rAttrs.empty() && nRow1 > 0) + // First attribute range doesn't start at row 0. + bHasGap = true; + + if (!rAttrs.empty() && rAttrs.back().nEndRow + 1 < nRow1) + bHasGap = true; + + if (bHasGap) + { + // Fill this gap with the default pattern. + ScAttrEntry aEntry; + aEntry.nEndRow = nRow1 - 1; + aEntry.pPattern = rDoc.GetDefPattern(); + rAttrs.push_back(aEntry); + } + + ScAttrEntry aEntry; + aEntry.nEndRow = nRow2; + aEntry.pPattern = &rDoc.GetPool()->Put(rPat); + rAttrs.push_back(aEntry); +} + +void XclImpXF::ApplyPattern( + SCCOL nScCol1, SCROW nScRow1, SCCOL nScCol2, SCROW nScRow2, + SCTAB nScTab ) +{ + // force creation of cell style and hard formatting, do it here to have mpStyleSheet + const ScPatternAttr& rPattern = CreatePattern(); + + // insert into document + ScDocument& rDoc = GetDoc(); + if( IsCellXF() && mpStyleSheet ) + rDoc.ApplyStyleAreaTab( nScCol1, nScRow1, nScCol2, nScRow2, nScTab, *mpStyleSheet ); + if( HasUsedFlags() ) + rDoc.ApplyPatternAreaTab( nScCol1, nScRow1, nScCol2, nScRow2, nScTab, rPattern ); + +} + +/*static*/ void XclImpXF::ApplyPatternForBiff2CellFormat( const XclImpRoot& rRoot, + const ScAddress& rScPos, sal_uInt8 nFlags1, sal_uInt8 nFlags2, sal_uInt8 nFlags3 ) +{ + /* Create an XF object and let it do the work. We will have access to its + private members here. */ + XclImpXF aXF( rRoot ); + + // no used flags available in BIFF2 (always true) + aXF.SetAllUsedFlags( true ); + + // set the attributes + aXF.maProtection.FillFromXF2( nFlags1 ); + aXF.maAlignment.FillFromXF2( nFlags3 ); + aXF.maBorder.FillFromXF2( nFlags3 ); + aXF.maArea.FillFromXF2( nFlags3 ); + aXF.mnXclNumFmt = ::extract_value< sal_uInt16 >( nFlags2, 0, 6 ); + aXF.mnXclFont = ::extract_value< sal_uInt16 >( nFlags2, 6, 2 ); + + // write the attributes to the cell + aXF.ApplyPattern( rScPos.Col(), rScPos.Row(), rScPos.Col(), rScPos.Row(), rScPos.Tab() ); +} + +void XclImpXF::SetUsedFlags( sal_uInt8 nUsedFlags ) +{ + /* Notes about finding the mb***Used flags: + - In cell XFs a *set* bit means a used attribute. + - In style XFs a *cleared* bit means a used attribute. + The mb***Used members always store true, if the attribute is used. + The "mbCellXF == ::get_flag(...)" construct evaluates to true in + both mentioned cases: cell XF and set bit; or style XF and cleared bit. + */ + mbProtUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_PROT )); + mbFontUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_FONT )); + mbFmtUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_VALFMT )); + mbAlignUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_ALIGN )); + mbBorderUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_BORDER )); + mbAreaUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_AREA )); +} + +XclImpStyle::XclImpStyle( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnXfId( EXC_XF_NOTFOUND ), + mnBuiltinId( EXC_STYLE_USERDEF ), + mnLevel( EXC_STYLE_NOLEVEL ), + mbBuiltin( false ), + mbCustom( false ), + mbHidden( false ), + mpStyleSheet( nullptr ) +{ +} + +void XclImpStyle::ReadStyle( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF3 ); + + sal_uInt16 nXFIndex; + nXFIndex = rStrm.ReaduInt16(); + mnXfId = nXFIndex & EXC_STYLE_XFMASK; + mbBuiltin = ::get_flag( nXFIndex, EXC_STYLE_BUILTIN ); + + if( mbBuiltin ) + { + mnBuiltinId = rStrm.ReaduInt8(); + mnLevel = rStrm.ReaduInt8(); + } + else + { + maName = (GetBiff() <= EXC_BIFF5) ? rStrm.ReadByteString( false ) : rStrm.ReadUniString(); + // #i103281# check if this is a new built-in style introduced in XL2007 + if( (GetBiff() == EXC_BIFF8) && (rStrm.GetNextRecId() == EXC_ID_STYLEEXT) && rStrm.StartNextRecord() ) + { + sal_uInt8 nExtFlags; + rStrm.Ignore( 12 ); + nExtFlags = rStrm.ReaduInt8(); + mbBuiltin = ::get_flag( nExtFlags, EXC_STYLEEXT_BUILTIN ); + mbCustom = ::get_flag( nExtFlags, EXC_STYLEEXT_CUSTOM ); + mbHidden = ::get_flag( nExtFlags, EXC_STYLEEXT_HIDDEN ); + if( mbBuiltin ) + { + rStrm.Ignore( 1 ); // category + mnBuiltinId = rStrm.ReaduInt8(); + mnLevel = rStrm.ReaduInt8(); + } + } + } +} + +ScStyleSheet* XclImpStyle::CreateStyleSheet() +{ + // #i1624# #i1768# ignore unnamed user styles + if( !mpStyleSheet && (!maFinalName.isEmpty()) ) + { + bool bCreatePattern = false; + XclImpXF* pXF = GetXFBuffer().GetXF( mnXfId ); + + bool bDefStyle = mbBuiltin && (mnBuiltinId == EXC_STYLE_NORMAL); + if( bDefStyle ) + { + // set all flags to true to get all items in XclImpXF::CreatePattern() + if( pXF ) pXF->SetAllUsedFlags( true ); + // use existing "Default" style sheet + mpStyleSheet = static_cast< ScStyleSheet* >( GetStyleSheetPool().Find( + ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Para ) ); + OSL_ENSURE( mpStyleSheet, "XclImpStyle::CreateStyleSheet - Default style not found" ); + bCreatePattern = true; + } + else + { + /* #i103281# do not create another style sheet of the same name, + if it exists already. This is needed to prevent that styles + pasted from clipboard get duplicated over and over. */ + mpStyleSheet = static_cast< ScStyleSheet* >( GetStyleSheetPool().Find( maFinalName, SfxStyleFamily::Para ) ); + if( !mpStyleSheet ) + { + mpStyleSheet = &static_cast< ScStyleSheet& >( GetStyleSheetPool().Make( maFinalName, SfxStyleFamily::Para, SfxStyleSearchBits::UserDefined ) ); + bCreatePattern = true; + } + } + + // bDefStyle==true omits default pool items in CreatePattern() + if( bCreatePattern && mpStyleSheet && pXF ) + mpStyleSheet->GetItemSet().Put( pXF->CreatePattern( bDefStyle ).GetItemSet() ); + } + return mpStyleSheet; +} + +void XclImpStyle::CreateUserStyle( const OUString& rFinalName ) +{ + maFinalName = rFinalName; + if( !IsBuiltin() || mbCustom ) + CreateStyleSheet(); +} + +XclImpXFBuffer::XclImpXFBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpXFBuffer::Initialize() +{ + maXFList.clear(); + maBuiltinStyles.clear(); + maUserStyles.clear(); + maStylesByXf.clear(); +} + +void XclImpXFBuffer::ReadXF( XclImpStream& rStrm ) +{ + std::unique_ptr<XclImpXF> xXF = std::make_unique<XclImpXF>(GetRoot()); + xXF->ReadXF(rStrm); + maXFList.emplace_back(std::move(xXF)); +} + +void XclImpXFBuffer::ReadStyle( XclImpStream& rStrm ) +{ + std::unique_ptr<XclImpStyle> xStyle(std::make_unique<XclImpStyle>(GetRoot())); + xStyle->ReadStyle(rStrm); + XclImpStyleList& rStyleList = (xStyle->IsBuiltin() ? maBuiltinStyles : maUserStyles); + rStyleList.emplace_back(std::move(xStyle)); + XclImpStyle* pStyle = rStyleList.back().get(); + OSL_ENSURE( maStylesByXf.count( pStyle->GetXfId() ) == 0, "XclImpXFBuffer::ReadStyle - multiple styles with equal XF identifier" ); + maStylesByXf[ pStyle->GetXfId() ] = pStyle; +} + +sal_uInt16 XclImpXFBuffer::GetFontIndex( sal_uInt16 nXFIndex ) const +{ + const XclImpXF* pXF = GetXF( nXFIndex ); + return pXF ? pXF->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +const XclImpFont* XclImpXFBuffer::GetFont( sal_uInt16 nXFIndex ) const +{ + return GetFontBuffer().GetFont( GetFontIndex( nXFIndex ) ); +} + +namespace { + +/** Functor for case-insensitive string comparison, usable in maps etc. */ +struct IgnoreCaseCompare +{ + bool operator()( std::u16string_view rName1, std::u16string_view rName2 ) const + { return o3tl::compareToIgnoreAsciiCase( rName1, rName2 ) < 0; } +}; + +} // namespace + +void XclImpXFBuffer::CreateUserStyles() +{ + // calculate final names of all styles + std::map< OUString, XclImpStyle*, IgnoreCaseCompare > aCellStyles; + std::vector< XclImpStyle* > aConflictNameStyles; + + /* First, reserve style names that are built-in in Calc. This causes that + imported cell styles get different unused names and thus do not try to + overwrite these built-in styles. For BIFF4 workbooks (which contain a + separate list of cell styles per sheet), reserve all existing styles if + current sheet is not the first sheet (this styles buffer will be + initialized again for every new sheet). This will create unique names + for styles in different sheets with the same name. Assuming that the + BIFF4W import filter is never used to import from clipboard... */ + bool bReserveAll = (GetBiff() == EXC_BIFF4) && (GetCurrScTab() > 0); + SfxStyleSheetIterator aStyleIter( GetDoc().GetStyleSheetPool(), SfxStyleFamily::Para ); + OUString aStandardName = ScResId( STR_STYLENAME_STANDARD ); + for( SfxStyleSheetBase* pStyleSheet = aStyleIter.First(); pStyleSheet; pStyleSheet = aStyleIter.Next() ) + if( (pStyleSheet->GetName() != aStandardName) && (bReserveAll || !pStyleSheet->IsUserDefined()) ) + if( aCellStyles.count( pStyleSheet->GetName() ) == 0 ) + aCellStyles[ pStyleSheet->GetName() ] = nullptr; + + /* Calculate names of built-in styles. Store styles with reserved names + in the aConflictNameStyles list. */ + for( const auto& rxStyle : maBuiltinStyles ) + { + OUString aStyleName = XclTools::GetBuiltInStyleName( rxStyle->GetBuiltinId(), rxStyle->GetName(), rxStyle->GetLevel() ); + OSL_ENSURE( bReserveAll || (aCellStyles.count( aStyleName ) == 0), + "XclImpXFBuffer::CreateUserStyles - multiple styles with equal built-in identifier" ); + if( aCellStyles.count( aStyleName ) > 0 ) + aConflictNameStyles.push_back( rxStyle.get() ); + else + aCellStyles[ aStyleName ] = rxStyle.get(); + } + + /* Calculate names of user defined styles. Store styles with reserved + names in the aConflictNameStyles list. */ + for( const auto& rxStyle : maUserStyles ) + { + // #i1624# #i1768# ignore unnamed user styles + if( !rxStyle->GetName().isEmpty() ) + { + if( aCellStyles.count( rxStyle->GetName() ) > 0 ) + aConflictNameStyles.push_back( rxStyle.get() ); + else + aCellStyles[ rxStyle->GetName() ] = rxStyle.get(); + } + } + + // find unused names for all styles with conflicting names + for( XclImpStyle* pStyle : aConflictNameStyles ) + { + OUString aUnusedName; + sal_Int32 nIndex = 0; + do + { + aUnusedName = pStyle->GetName() + " " + OUString::number( ++nIndex ); + } + while( aCellStyles.count( aUnusedName ) > 0 ); + aCellStyles[ aUnusedName ] = pStyle; + } + + // set final names and create user-defined and modified built-in cell styles + for( auto& [rStyleName, rpStyle] : aCellStyles ) + if( rpStyle ) + rpStyle->CreateUserStyle( rStyleName ); +} + +ScStyleSheet* XclImpXFBuffer::CreateStyleSheet( sal_uInt16 nXFIndex ) +{ + XclImpStyleMap::iterator aIt = maStylesByXf.find( nXFIndex ); + return (aIt == maStylesByXf.end()) ? nullptr : aIt->second->CreateStyleSheet(); +} + +// Buffer for XF indexes in cells ============================================= + +bool XclImpXFRange::Expand( SCROW nScRow, const XclImpXFIndex& rXFIndex ) +{ + if( maXFIndex != rXFIndex ) + return false; + + if( mnScRow2 + 1 == nScRow ) + { + ++mnScRow2; + return true; + } + if( mnScRow1 > 0 && (mnScRow1 - 1 == nScRow) ) + { + --mnScRow1; + return true; + } + + return false; +} + +bool XclImpXFRange::Expand( const XclImpXFRange& rNextRange ) +{ + OSL_ENSURE( mnScRow2 < rNextRange.mnScRow1, "XclImpXFRange::Expand - rows out of order" ); + if( (maXFIndex == rNextRange.maXFIndex) && (mnScRow2 + 1 == rNextRange.mnScRow1) ) + { + mnScRow2 = rNextRange.mnScRow2; + return true; + } + return false; +} + +void XclImpXFRangeColumn::SetDefaultXF( const XclImpXFIndex& rXFIndex, const XclImpRoot& rRoot ) +{ + // List should be empty when inserting the default column format. + // Later explicit SetXF() calls will break up this range. + OSL_ENSURE( maIndexList.empty(), "XclImpXFRangeColumn::SetDefaultXF - Setting Default Column XF is not empty" ); + + // insert a complete row range with one insert. + maIndexList.push_back( std::make_unique<XclImpXFRange>( 0, rRoot.GetDoc().MaxRow(), rXFIndex ) ); +} + +void XclImpXFRangeColumn::SetXF( SCROW nScRow, const XclImpXFIndex& rXFIndex ) +{ + XclImpXFRange* pPrevRange; + XclImpXFRange* pNextRange; + sal_uLong nNextIndex; + + Find( pPrevRange, pNextRange, nNextIndex, nScRow ); + + // previous range: + // try to overwrite XF (if row is contained in) or try to expand range + if( pPrevRange ) + { + if( pPrevRange->Contains( nScRow ) ) // overwrite old XF + { + if( rXFIndex == pPrevRange->maXFIndex ) + return; + + SCROW nFirstScRow = pPrevRange->mnScRow1; + SCROW nLastScRow = pPrevRange->mnScRow2; + sal_uLong nIndex = nNextIndex - 1; + XclImpXFRange* pThisRange = pPrevRange; + pPrevRange = (nIndex > 0 && nIndex <= maIndexList.size()) ? maIndexList[ nIndex - 1 ].get() : nullptr; + + if( nFirstScRow == nLastScRow ) // replace solely XF + { + pThisRange->maXFIndex = rXFIndex; + TryConcatPrev( nNextIndex ); // try to concat. next with this + TryConcatPrev( nIndex ); // try to concat. this with previous + } + else if( nFirstScRow == nScRow ) // replace first XF + { + ++(pThisRange->mnScRow1); + // try to concatenate with previous of this + if( !pPrevRange || !pPrevRange->Expand( nScRow, rXFIndex ) ) + Insert( new XclImpXFRange( nScRow, rXFIndex ), nIndex ); + } + else if( nLastScRow == nScRow ) // replace last XF + { + --(pThisRange->mnScRow2); + if( !pNextRange || !pNextRange->Expand( nScRow, rXFIndex ) ) + Insert( new XclImpXFRange( nScRow, rXFIndex ), nNextIndex ); + } + else // insert in the middle of the range + { + pThisRange->mnScRow1 = nScRow + 1; + // List::Insert() moves entries towards end of list, so insert twice at nIndex + Insert( new XclImpXFRange( nScRow, rXFIndex ), nIndex ); + Insert( new XclImpXFRange( nFirstScRow, nScRow - 1, pThisRange->maXFIndex ), nIndex ); + } + return; + } + else if( pPrevRange->Expand( nScRow, rXFIndex ) ) // try to expand + { + TryConcatPrev( nNextIndex ); // try to concatenate next with expanded + return; + } + } + + // try to expand next range + if( pNextRange && pNextRange->Expand( nScRow, rXFIndex ) ) + return; + + // create new range + Insert( new XclImpXFRange( nScRow, rXFIndex ), nNextIndex ); +} + +void XclImpXFRangeColumn::Insert(XclImpXFRange* pXFRange, sal_uLong nIndex) +{ + maIndexList.insert( maIndexList.begin() + nIndex, std::unique_ptr<XclImpXFRange>(pXFRange) ); +} + +void XclImpXFRangeColumn::Find( + XclImpXFRange*& rpPrevRange, XclImpXFRange*& rpNextRange, + sal_uLong& rnNextIndex, SCROW nScRow ) +{ + + // test whether list is empty + if( maIndexList.empty() ) + { + rpPrevRange = rpNextRange = nullptr; + rnNextIndex = 0; + return; + } + + rpPrevRange = maIndexList.front().get(); + rpNextRange = maIndexList.back().get(); + + // test whether row is at end of list (contained in or behind last range) + // rpPrevRange will contain a possible existing row + if( rpNextRange->mnScRow1 <= nScRow ) + { + rpPrevRange = rpNextRange; + rpNextRange = nullptr; + rnNextIndex = maIndexList.size(); + return; + } + + // test whether row is at beginning of list (really before first range) + if( nScRow < rpPrevRange->mnScRow1 ) + { + rpNextRange = rpPrevRange; + rpPrevRange = nullptr; + rnNextIndex = 0; + return; + } + + // loop: find range entries before and after new row + // break the loop if there is no more range between first and last -or- + // if rpPrevRange contains nScRow (rpNextRange will never contain nScRow) + sal_uLong nPrevIndex = 0; + sal_uLong nMidIndex; + rnNextIndex = maIndexList.size() - 1; + XclImpXFRange* pMidRange; + while( ((rnNextIndex - nPrevIndex) > 1) && (rpPrevRange->mnScRow2 < nScRow) ) + { + nMidIndex = (nPrevIndex + rnNextIndex) / 2; + pMidRange = maIndexList[nMidIndex].get(); + OSL_ENSURE( pMidRange, "XclImpXFRangeColumn::Find - missing XF index range" ); + if( nScRow < pMidRange->mnScRow1 ) // row is really before pMidRange + { + rpNextRange = pMidRange; + rnNextIndex = nMidIndex; + } + else // row is in or after pMidRange + { + rpPrevRange = pMidRange; + nPrevIndex = nMidIndex; + } + } + + // find next rpNextRange if rpPrevRange contains nScRow + if( nScRow <= rpPrevRange->mnScRow2 ) + { + rnNextIndex = nPrevIndex + 1; + rpNextRange = maIndexList[rnNextIndex].get(); + } +} + +void XclImpXFRangeColumn::TryConcatPrev( sal_uLong nIndex ) +{ + if( !nIndex || nIndex >= maIndexList.size() ) + return; + + XclImpXFRange& prevRange = *maIndexList[ nIndex - 1 ]; + XclImpXFRange& nextRange = *maIndexList[ nIndex ]; + + if( prevRange.Expand( nextRange ) ) + maIndexList.erase( maIndexList.begin() + nIndex ); +} + +XclImpXFRangeBuffer::XclImpXFRangeBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +XclImpXFRangeBuffer::~XclImpXFRangeBuffer() +{ +} + +void XclImpXFRangeBuffer::Initialize() +{ + maColumns.clear(); + maHyperlinks.clear(); + maMergeList.RemoveAll(); +} + +void XclImpXFRangeBuffer::SetXF( const ScAddress& rScPos, sal_uInt16 nXFIndex, XclImpXFInsertMode eMode ) +{ + SCCOL nScCol = rScPos.Col(); + SCROW nScRow = rScPos.Row(); + + // set cell XF's + size_t nIndex = static_cast< size_t >( nScCol ); + if( maColumns.size() <= nIndex ) + maColumns.resize( nIndex + 1 ); + if( !maColumns[ nIndex ] ) + maColumns[ nIndex ] = std::make_shared<XclImpXFRangeColumn>(); + // remember all Boolean cells, they will get 'Standard' number format + maColumns[ nIndex ]->SetXF( nScRow, XclImpXFIndex( nXFIndex, eMode == xlXFModeBoolCell ) ); + + // set "center across selection" and "fill" attribute for all following empty cells + // ignore it on row default XFs + if( eMode == xlXFModeRow ) + return; + + const XclImpXF* pXF = GetXFBuffer().GetXF( nXFIndex ); + if( pXF && ((pXF->GetHorAlign() == EXC_XF_HOR_CENTER_AS) || (pXF->GetHorAlign() == EXC_XF_HOR_FILL)) ) + { + // expand last merged range if this attribute is set repeatedly + ScRange* pRange = maMergeList.empty() ? nullptr : &maMergeList.back(); + if (pRange && (pRange->aEnd.Row() == nScRow) && (pRange->aEnd.Col() + 1 == nScCol) && (eMode == xlXFModeBlank)) + pRange->aEnd.IncCol(); + else if( eMode != xlXFModeBlank ) // do not merge empty cells + maMergeList.push_back( ScRange( nScCol, nScRow, 0 ) ); + } +} + +void XclImpXFRangeBuffer::SetXF( const ScAddress& rScPos, sal_uInt16 nXFIndex ) +{ + SetXF( rScPos, nXFIndex, xlXFModeCell ); +} + +void XclImpXFRangeBuffer::SetBlankXF( const ScAddress& rScPos, sal_uInt16 nXFIndex ) +{ + SetXF( rScPos, nXFIndex, xlXFModeBlank ); +} + +void XclImpXFRangeBuffer::SetBoolXF( const ScAddress& rScPos, sal_uInt16 nXFIndex ) +{ + SetXF( rScPos, nXFIndex, xlXFModeBoolCell ); +} + +void XclImpXFRangeBuffer::SetRowDefXF( SCROW nScRow, sal_uInt16 nXFIndex ) +{ + for( SCCOL nScCol = 0; nScCol <= GetDoc().MaxCol(); ++nScCol ) + SetXF( ScAddress( nScCol, nScRow, 0 ), nXFIndex, xlXFModeRow ); +} + +void XclImpXFRangeBuffer::SetColumnDefXF( SCCOL nScCol, sal_uInt16 nXFIndex ) +{ + // our array should not have values when creating the default column format. + size_t nIndex = static_cast< size_t >( nScCol ); + if( maColumns.size() <= nIndex ) + maColumns.resize( nIndex + 1 ); + OSL_ENSURE( !maColumns[ nIndex ], "XclImpXFRangeBuffer::SetColumnDefXF - default column of XFs already has values" ); + maColumns[ nIndex ] = std::make_shared<XclImpXFRangeColumn>(); + maColumns[ nIndex ]->SetDefaultXF( XclImpXFIndex( nXFIndex ), GetRoot()); +} + +void XclImpXFRangeBuffer::SetBorderLine( 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(); + ScDocument& rDoc = GetDoc(); + + 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 XclImpXFRangeBuffer::SetHyperlink( const XclRange& rXclRange, const OUString& rUrl ) +{ + maHyperlinks.emplace_back( rXclRange, rUrl ); +} + +void XclImpXFRangeBuffer::SetMerge( SCCOL nScCol1, SCROW nScRow1, SCCOL nScCol2, SCROW nScRow2 ) +{ + if( (nScCol1 < nScCol2) || (nScRow1 < nScRow2) ) + maMergeList.push_back( ScRange( nScCol1, nScRow1, 0, nScCol2, nScRow2, 0 ) ); +} + +void XclImpXFRangeBuffer::Finalize() +{ + ScDocumentImport& rDocImport = GetDocImport(); + ScDocument& rDoc = rDocImport.getDoc(); + SCTAB nScTab = GetCurrScTab(); + + // apply patterns + XclImpXFBuffer& rXFBuffer = GetXFBuffer(); + ScDocumentImport::Attrs aPendingAttrParam; + SCCOL pendingColStart = -1; + SCCOL pendingColEnd = -1; + SCCOL nScCol = 0; + for( const auto& rxColumn : maColumns ) + { + // apply all cell styles of an existing column + if( rxColumn ) + { + XclImpXFRangeColumn& rColumn = *rxColumn; + std::vector<ScAttrEntry> aAttrs; + aAttrs.reserve(rColumn.end() - rColumn.begin()); + + for (const auto& rxStyle : rColumn) + { + XclImpXFRange& rStyle = *rxStyle; + const XclImpXFIndex& rXFIndex = rStyle.maXFIndex; + XclImpXF* pXF = rXFBuffer.GetXF( rXFIndex.GetXFIndex() ); + if (!pXF) + continue; + + sal_uInt32 nForceScNumFmt = rXFIndex.IsBoolCell() ? + GetNumFmtBuffer().GetStdScNumFmt() : NUMBERFORMAT_ENTRY_NOT_FOUND; + + pXF->ApplyPatternToAttrVector(aAttrs, rStyle.mnScRow1, rStyle.mnScRow2, nForceScNumFmt); + } + + if (aAttrs.empty() || aAttrs.back().nEndRow != rDoc.MaxRow()) + { + ScAttrEntry aEntry; + aEntry.nEndRow = rDoc.MaxRow(); + aEntry.pPattern = rDoc.GetDefPattern(); + aAttrs.push_back(aEntry); + } + + aAttrs.shrink_to_fit(); + assert(aAttrs.size() > 0); + ScDocumentImport::Attrs aAttrParam; + aAttrParam.mvData.swap(aAttrs); + aAttrParam.mbLatinNumFmtOnly = false; // when unsure, set it to false. + + // 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(nScTab, pendingColStart, pendingColEnd, std::move(aPendingAttrParam)); + pendingColStart = pendingColEnd = nScCol; + aPendingAttrParam = std::move( aAttrParam ); + } + } + ++nScCol; + } + if( pendingColStart != -1 ) + rDocImport.setAttrEntries(nScTab, pendingColStart, pendingColEnd, std::move(aPendingAttrParam)); + + // insert hyperlink cells + for( const auto& [rXclRange, rUrl] : maHyperlinks ) + XclImpHyperlink::InsertUrl( GetRoot(), rXclRange, rUrl ); + + // apply cell merging + for ( size_t i = 0, nRange = maMergeList.size(); i < nRange; ++i ) + { + const ScRange & rRange = maMergeList[ i ]; + const ScAddress& rStart = rRange.aStart; + const ScAddress& rEnd = rRange.aEnd; + bool bMultiCol = rStart.Col() != rEnd.Col(); + bool bMultiRow = rStart.Row() != rEnd.Row(); + // set correct right border + if( bMultiCol ) + SetBorderLine( rRange, nScTab, SvxBoxItemLine::RIGHT ); + // set correct lower border + if( bMultiRow ) + SetBorderLine( rRange, nScTab, SvxBoxItemLine::BOTTOM ); + // do merge + if( bMultiCol || bMultiRow ) + rDoc.DoMerge( rStart.Col(), rStart.Row(), rEnd.Col(), rEnd.Row(), nScTab ); + // #i93609# merged range in a single row: test if manual row height is needed + if( !bMultiRow ) + { + bool bTextWrap = rDoc.GetAttr( rStart, ATTR_LINEBREAK )->GetValue(); + if( !bTextWrap && (rDoc.GetCellType( rStart ) == CELLTYPE_EDIT) ) + if (const EditTextObject* pEditObj = rDoc.GetEditText(rStart)) + bTextWrap = pEditObj->GetParagraphCount() > 1; + if( bTextWrap ) + GetOldRoot().pColRowBuff->SetManualRowHeight( rStart.Row() ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiview.cxx b/sc/source/filter/excel/xiview.cxx new file mode 100644 index 000000000..49878de6e --- /dev/null +++ b/sc/source/filter/excel/xiview.cxx @@ -0,0 +1,300 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> + +#include <xiview.hxx> +#include <document.hxx> +#include <scextopt.hxx> +#include <viewopti.hxx> +#include <xistream.hxx> +#include <xihelper.hxx> +#include <xistyle.hxx> + +// Document view settings ===================================================== + +XclImpDocViewSettings::XclImpDocViewSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpDocViewSettings::ReadWindow1( XclImpStream& rStrm ) +{ + maData.mnWinX = rStrm.ReaduInt16(); + maData.mnWinY = rStrm.ReaduInt16(); + maData.mnWinWidth = rStrm.ReaduInt16(); + maData.mnWinHeight = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + if( GetBiff() >= EXC_BIFF5 ) + { + maData.mnDisplXclTab = rStrm.ReaduInt16(); + maData.mnFirstVisXclTab = rStrm.ReaduInt16(); + maData.mnXclSelectCnt = rStrm.ReaduInt16(); + maData.mnTabBarWidth = rStrm.ReaduInt16(); + } +} + +SCTAB XclImpDocViewSettings::GetDisplScTab() const +{ + /* Simply cast Excel index to Calc index. + TODO: This may fail if the document contains scenarios. */ + sal_uInt16 nMaxXclTab = static_cast< sal_uInt16 >( GetMaxPos().Tab() ); + return static_cast< SCTAB >( (maData.mnDisplXclTab <= nMaxXclTab) ? maData.mnDisplXclTab : 0 ); +} + +void XclImpDocViewSettings::Finalize() +{ + ScViewOptions aViewOpt( GetDoc().GetViewOptions() ); + aViewOpt.SetOption( VOPT_HSCROLL, ::get_flag( maData.mnFlags, EXC_WIN1_HOR_SCROLLBAR ) ); + aViewOpt.SetOption( VOPT_VSCROLL, ::get_flag( maData.mnFlags, EXC_WIN1_VER_SCROLLBAR ) ); + aViewOpt.SetOption( VOPT_TABCONTROLS, ::get_flag( maData.mnFlags, EXC_WIN1_TABBAR ) ); + GetDoc().SetViewOptions( aViewOpt ); + + // displayed sheet + GetExtDocOptions().GetDocSettings().mnDisplTab = GetDisplScTab(); + + // width of the tabbar with sheet names + if( maData.mnTabBarWidth <= 1000 ) + GetExtDocOptions().GetDocSettings().mfTabBarWidth = static_cast< double >( maData.mnTabBarWidth ) / 1000.0; +} + +// Sheet view settings ======================================================== + +namespace { + +tools::Long lclGetScZoom( sal_uInt16 nXclZoom, sal_uInt16 nDefZoom ) +{ + return static_cast< tools::Long >( nXclZoom ? nXclZoom : nDefZoom ); +} + +} // namespace + +XclImpTabViewSettings::XclImpTabViewSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ + Initialize(); +} + +void XclImpTabViewSettings::Initialize() +{ + maData.SetDefaults(); +} + +void XclImpTabViewSettings::ReadTabBgColor( XclImpStream& rStrm, const XclImpPalette& rPal ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF8 ); + if( GetBiff() < EXC_BIFF8 ) + return; + + sal_uInt8 ColorIndex; + + rStrm.Ignore( 16 ); + ColorIndex = rStrm.ReaduInt8() & EXC_SHEETEXT_TABCOLOR; //0x7F + if ( ColorIndex >= 8 && ColorIndex <= 63 ) //only accept valid index values + { + maData.maTabBgColor = rPal.GetColor( ColorIndex ); + } +} + +void XclImpTabViewSettings::ReadWindow2( XclImpStream& rStrm, bool bChart ) +{ + if( GetBiff() == EXC_BIFF2 ) + { + maData.mbShowFormulas = rStrm.ReaduInt8() != 0; + maData.mbShowGrid = rStrm.ReaduInt8() != 0; + maData.mbShowHeadings = rStrm.ReaduInt8() != 0; + maData.mbFrozenPanes = rStrm.ReaduInt8() != 0; + maData.mbShowZeros = rStrm.ReaduInt8() != 0; + rStrm >> maData.maFirstXclPos; + maData.mbDefGridColor = rStrm.ReaduInt8() != 0; + rStrm >> maData.maGridColor; + } + else + { + sal_uInt16 nFlags; + nFlags = rStrm.ReaduInt16(); + rStrm >> maData.maFirstXclPos; + + // #i59590# real life: Excel ignores some view settings in chart sheets + maData.mbSelected = ::get_flag( nFlags, EXC_WIN2_SELECTED ); + maData.mbDisplayed = ::get_flag( nFlags, EXC_WIN2_DISPLAYED ); + maData.mbMirrored = !bChart && ::get_flag( nFlags, EXC_WIN2_MIRRORED ); + maData.mbFrozenPanes = !bChart && ::get_flag( nFlags, EXC_WIN2_FROZEN ); + maData.mbPageMode = !bChart && ::get_flag( nFlags, EXC_WIN2_PAGEBREAKMODE ); + maData.mbDefGridColor = bChart || ::get_flag( nFlags, EXC_WIN2_DEFGRIDCOLOR ); + maData.mbShowFormulas = !bChart && ::get_flag( nFlags, EXC_WIN2_SHOWFORMULAS ); + maData.mbShowGrid = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWGRID ); + maData.mbShowHeadings = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWHEADINGS ); + maData.mbShowZeros = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWZEROS ); + maData.mbShowOutline = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWOUTLINE ); + + switch( GetBiff() ) + { + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + rStrm >> maData.maGridColor; + break; + case EXC_BIFF8: + { + sal_uInt16 nGridColorIdx; + nGridColorIdx = rStrm.ReaduInt16(); + // zoom data not included in chart sheets + if( rStrm.GetRecLeft() >= 6 ) + { + rStrm.Ignore( 2 ); + maData.mnPageZoom = rStrm.ReaduInt16(); + maData.mnNormalZoom = rStrm.ReaduInt16(); + } + + if( !maData.mbDefGridColor ) + maData.maGridColor = GetPalette().GetColor( nGridColorIdx ); + } + break; + default: DBG_ERROR_BIFF(); + } + } + + // do not scroll chart sheets + if( bChart ) + maData.maFirstXclPos.Set( 0, 0 ); +} + +void XclImpTabViewSettings::ReadScl( XclImpStream& rStrm ) +{ + sal_uInt16 nNum, nDenom; + nNum = rStrm.ReaduInt16(); + nDenom = rStrm.ReaduInt16(); + OSL_ENSURE( nDenom > 0, "XclImpPageSettings::ReadScl - invalid denominator" ); + if( nDenom > 0 ) + maData.mnCurrentZoom = limit_cast< sal_uInt16 >( (nNum * 100) / nDenom ); +} + +void XclImpTabViewSettings::ReadPane( XclImpStream& rStrm ) +{ + maData.mnSplitX = rStrm.ReaduInt16(); + maData.mnSplitY = rStrm.ReaduInt16(); + + rStrm >> maData.maSecondXclPos; + maData.mnActivePane = rStrm.ReaduInt8(); +} + +void XclImpTabViewSettings::ReadSelection( XclImpStream& rStrm ) +{ + // pane of this selection + sal_uInt8 nPane; + nPane = rStrm.ReaduInt8(); + XclSelectionData& rSelData = maData.CreateSelectionData( nPane ); + // cursor position and selection + rStrm >> rSelData.maXclCursor; + rSelData.mnCursorIdx = rStrm.ReaduInt16(); + rSelData.maXclSelection.Read( rStrm, false ); +} + +void XclImpTabViewSettings::Finalize() +{ + SCTAB nScTab = GetCurrScTab(); + ScDocument& rDoc = GetDoc(); + XclImpAddressConverter& rAddrConv = GetAddressConverter(); + ScExtTabSettings& rTabSett = GetExtDocOptions().GetOrCreateTabSettings( nScTab ); + bool bDisplayed = GetDocViewSettings().GetDisplScTab() == nScTab; + + // *** sheet options: cursor, selection, splits, zoom *** + + // sheet flags + if( maData.mbMirrored ) + // do not call this function with sal_False, it would mirror away all drawing objects + rDoc.SetLayoutRTL( nScTab, true ); + rTabSett.mbSelected = maData.mbSelected || bDisplayed; + + // first visible cell in top-left pane and in additional pane(s) + rTabSett.maFirstVis = rAddrConv.CreateValidAddress( maData.maFirstXclPos, nScTab, false ); + rTabSett.maSecondVis = rAddrConv.CreateValidAddress( maData.maSecondXclPos, nScTab, false ); + + // cursor position and selection + if( const XclSelectionData* pSelData = maData.GetSelectionData( maData.mnActivePane ) ) + { + rTabSett.maCursor = rAddrConv.CreateValidAddress( pSelData->maXclCursor, nScTab, false ); + rAddrConv.ConvertRangeList( rTabSett.maSelection, pSelData->maXclSelection, nScTab, false ); + } + + // active pane + switch( maData.mnActivePane ) + { + case EXC_PANE_TOPLEFT: rTabSett.meActivePane = SCEXT_PANE_TOPLEFT; break; + case EXC_PANE_TOPRIGHT: rTabSett.meActivePane = SCEXT_PANE_TOPRIGHT; break; + case EXC_PANE_BOTTOMLEFT: rTabSett.meActivePane = SCEXT_PANE_BOTTOMLEFT; break; + case EXC_PANE_BOTTOMRIGHT: rTabSett.meActivePane = SCEXT_PANE_BOTTOMRIGHT; break; + } + + // freeze/split position + rTabSett.mbFrozenPanes = maData.mbFrozenPanes; + if( maData.mbFrozenPanes ) + { + /* Frozen panes: handle split position as row/column positions. + #i35812# Excel uses number of visible rows/columns, Calc uses position of freeze. */ + if( (maData.mnSplitX > 0) && (maData.maFirstXclPos.mnCol + maData.mnSplitX <= GetScMaxPos().Col()) ) + rTabSett.maFreezePos.SetCol( static_cast< SCCOL >( maData.maFirstXclPos.mnCol + maData.mnSplitX ) ); + if( (maData.mnSplitY > 0) && (maData.maFirstXclPos.mnRow + maData.mnSplitY <= o3tl::make_unsigned(GetScMaxPos().Row())) ) + rTabSett.maFreezePos.SetRow( static_cast< SCROW >( maData.maFirstXclPos.mnRow + maData.mnSplitY ) ); + } + else + { + // split window: position is in twips + rTabSett.maSplitPos.setX( static_cast< tools::Long >( maData.mnSplitX ) ); + rTabSett.maSplitPos.setY( static_cast< tools::Long >( maData.mnSplitY ) ); + } + + // grid color + if( maData.mbDefGridColor ) + rTabSett.maGridColor = COL_AUTO; + else + rTabSett.maGridColor = maData.maGridColor; + + // show grid option + rTabSett.mbShowGrid = maData.mbShowGrid; + + // view mode and zoom + if( maData.mnCurrentZoom != 0 ) + (maData.mbPageMode ? maData.mnPageZoom : maData.mnNormalZoom) = maData.mnCurrentZoom; + rTabSett.mbPageMode = maData.mbPageMode; + rTabSett.mnNormalZoom = lclGetScZoom( maData.mnNormalZoom, EXC_WIN2_NORMALZOOM_DEF ); + rTabSett.mnPageZoom = lclGetScZoom( maData.mnPageZoom, EXC_WIN2_PAGEZOOM_DEF ); + + // *** additional handling for displayed sheet *** + + if( bDisplayed ) + { + // set Excel sheet settings globally at Calc document, take settings from displayed sheet + ScViewOptions aViewOpt( rDoc.GetViewOptions() ); + aViewOpt.SetOption( VOPT_FORMULAS, maData.mbShowFormulas ); + aViewOpt.SetOption( VOPT_HEADER, maData.mbShowHeadings ); + aViewOpt.SetOption( VOPT_NULLVALS, maData.mbShowZeros ); + aViewOpt.SetOption( VOPT_OUTLINER, maData.mbShowOutline ); + rDoc.SetViewOptions( aViewOpt ); + } + + // *** set tab bg color + if ( !maData.IsDefaultTabBgColor() ) + rDoc.SetTabBgColor(nScTab, maData.maTabBgColor); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xladdress.cxx b/sc/source/filter/excel/xladdress.cxx new file mode 100644 index 000000000..20ef4fa7f --- /dev/null +++ b/sc/source/filter/excel/xladdress.cxx @@ -0,0 +1,161 @@ +/* -*- 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 <xladdress.hxx> +#include <xestream.hxx> +#include <xltracer.hxx> +#include <xistream.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +void XclAddress::Read( XclImpStream& rStrm ) +{ + mnRow = rStrm.ReaduInt16(); + mnCol = rStrm.ReaduInt16(); +} + +void XclAddress::Write( XclExpStream& rStrm ) const +{ + rStrm << static_cast<sal_uInt16> (mnRow); + rStrm << mnCol; +} + +bool XclRange::Contains( const XclAddress& rPos ) const +{ + return (maFirst.mnCol <= rPos.mnCol) && (rPos.mnCol <= maLast.mnCol) && + (maFirst.mnRow <= rPos.mnRow) && (rPos.mnRow <= maLast.mnRow); +} + +void XclRange::Read( XclImpStream& rStrm, bool bCol16Bit ) +{ + maFirst.mnRow = rStrm.ReaduInt16(); + maLast.mnRow = rStrm.ReaduInt16(); + + if( bCol16Bit ) + { + maFirst.mnCol = rStrm.ReaduInt16(); + maLast.mnCol = rStrm.ReaduInt16(); + } + else + { + maFirst.mnCol = rStrm.ReaduInt8(); + maLast.mnCol = rStrm.ReaduInt8(); + } +} + +void XclRange::Write( XclExpStream& rStrm, bool bCol16Bit ) const +{ + rStrm << static_cast<sal_uInt16>(maFirst.mnRow) << static_cast<sal_uInt16>(maLast.mnRow); + if( bCol16Bit ) + rStrm << maFirst.mnCol << maLast.mnCol; + else + rStrm << static_cast< sal_uInt8 >( maFirst.mnCol ) << static_cast< sal_uInt8 >( maLast.mnCol ); +} + +XclRange XclRangeList::GetEnclosingRange() const +{ + XclRange aXclRange; + if( !mRanges.empty() ) + { + XclRangeVector::const_iterator aIt = mRanges.begin(), aEnd = mRanges.end(); + aXclRange = *aIt; + for( ++aIt; aIt != aEnd; ++aIt ) + { + aXclRange.maFirst.mnCol = ::std::min( aXclRange.maFirst.mnCol, aIt->maFirst.mnCol ); + aXclRange.maFirst.mnRow = ::std::min( aXclRange.maFirst.mnRow, aIt->maFirst.mnRow ); + aXclRange.maLast.mnCol = ::std::max( aXclRange.maLast.mnCol, aIt->maLast.mnCol ); + aXclRange.maLast.mnRow = ::std::max( aXclRange.maLast.mnRow, aIt->maLast.mnRow ); + } + } + return aXclRange; +} + +void XclRangeList::Read( XclImpStream& rStrm, bool bCol16Bit, sal_uInt16 nCountInStream ) +{ + sal_uInt16 nCount; + if (nCountInStream) + nCount = nCountInStream; + else + nCount = rStrm.ReaduInt16(); + + if (!nCount) + return; + + XclRange aRange; + while (true) + { + aRange.Read(rStrm, bCol16Bit); + if (!rStrm.IsValid()) + break; + mRanges.emplace_back(aRange); + --nCount; + if (!nCount) + break; + } +} + +void XclRangeList::Write( XclExpStream& rStrm, bool bCol16Bit, sal_uInt16 nCountInStream ) const +{ + WriteSubList( rStrm, 0, mRanges.size(), bCol16Bit, nCountInStream ); +} + +void XclRangeList::WriteSubList( XclExpStream& rStrm, size_t nBegin, size_t nCount, bool bCol16Bit, + sal_uInt16 nCountInStream ) const +{ + OSL_ENSURE( nBegin <= mRanges.size(), "XclRangeList::WriteSubList - invalid start position" ); + size_t nEnd = ::std::min< size_t >( nBegin + nCount, mRanges.size() ); + if (!nCountInStream) + { + sal_uInt16 nXclCount = ulimit_cast< sal_uInt16 >( nEnd - nBegin ); + rStrm << nXclCount; + } + rStrm.SetSliceSize( bCol16Bit ? 8 : 6 ); + std::for_each(mRanges.begin() + nBegin, mRanges.begin() + nEnd, + [&rStrm, &bCol16Bit](const XclRange& rRange) { rRange.Write(rStrm, bCol16Bit); }); +} + +XclAddressConverterBase::XclAddressConverterBase( XclTracer& rTracer, const ScAddress& rMaxPos ) : + mrTracer( rTracer ), + maMaxPos( rMaxPos ), + mnMaxCol( static_cast< sal_uInt16 >( rMaxPos.Col() ) ), + mnMaxRow( static_cast< sal_uInt16 >( rMaxPos.Row() ) ), + mbColTrunc( false ), + mbRowTrunc( false ), + mbTabTrunc( false ) +{ + OSL_ENSURE( o3tl::make_unsigned( rMaxPos.Col() ) <= SAL_MAX_UINT16, "XclAddressConverterBase::XclAddressConverterBase - invalid max column" ); + OSL_ENSURE( o3tl::make_unsigned( rMaxPos.Row() ) <= SAL_MAX_UINT32, "XclAddressConverterBase::XclAddressConverterBase - invalid max row" ); +} + +XclAddressConverterBase::~XclAddressConverterBase() +{ +} + +void XclAddressConverterBase::CheckScTab( SCTAB nScTab ) +{ + bool bValid = (0 <= nScTab) && (nScTab <= maMaxPos.Tab()); + if( !bValid ) + { + mbTabTrunc |= (nScTab > maMaxPos.Tab()); // do not warn for deleted refs + mrTracer.TraceInvalidTab( nScTab, maMaxPos.Tab() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlchart.cxx b/sc/source/filter/excel/xlchart.cxx new file mode 100644 index 000000000..3547dad16 --- /dev/null +++ b/sc/source/filter/excel/xlchart.cxx @@ -0,0 +1,1283 @@ +/* -*- 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 <xlchart.hxx> + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/awt/Gradient.hpp> +#include <com/sun/star/drawing/Hatch.hpp> +#include <com/sun/star/drawing/LineDash.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart/XAxisXSupplier.hpp> +#include <com/sun/star/chart/XAxisYSupplier.hpp> +#include <com/sun/star/chart/XAxisZSupplier.hpp> +#include <com/sun/star/chart/XChartDocument.hpp> +#include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp> +#include <com/sun/star/chart2/Symbol.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include <o3tl/string_view.hxx> +#include <sal/macros.h> +#include <sal/mathconf.h> +#include <svl/itemset.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xfltrit.hxx> +#include <svx/xflgrit.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/unomid.hxx> +#include <filter/msfilter/escherex.hxx> +#include <xlroot.hxx> +#include <xlstyle.hxx> +#include <xltools.hxx> + +using namespace css; + +// Common ===================================================================== + +XclChRectangle::XclChRectangle() : + mnX( 0 ), + mnY( 0 ), + mnWidth( 0 ), + mnHeight( 0 ) +{ +} + +XclChDataPointPos::XclChDataPointPos( sal_uInt16 nSeriesIdx, sal_uInt16 nPointIdx ) : + mnSeriesIdx( nSeriesIdx ), + mnPointIdx( nPointIdx ) +{ +} + +bool operator<( const XclChDataPointPos& rL, const XclChDataPointPos& rR ) +{ + return (rL.mnSeriesIdx < rR.mnSeriesIdx) || + ((rL.mnSeriesIdx == rR.mnSeriesIdx) && (rL.mnPointIdx < rR.mnPointIdx)); +} + +XclChFrBlock::XclChFrBlock( sal_uInt16 nType ) : + mnType( nType ), + mnContext( 0 ), + mnValue1( 0 ), + mnValue2( 0 ) +{ +} + +// Frame formatting =========================================================== + +XclChFramePos::XclChFramePos() : + mnTLMode( EXC_CHFRAMEPOS_PARENT ), + mnBRMode( EXC_CHFRAMEPOS_PARENT ) +{ +} + +XclChLineFormat::XclChLineFormat() : + maColor( COL_BLACK ), + mnPattern( EXC_CHLINEFORMAT_SOLID ), + mnWeight( EXC_CHLINEFORMAT_SINGLE ), + mnFlags( EXC_CHLINEFORMAT_AUTO ) +{ +} + +XclChAreaFormat::XclChAreaFormat() : + maPattColor( COL_WHITE ), + maBackColor( COL_BLACK ), + mnPattern( EXC_PATT_SOLID ), + mnFlags( EXC_CHAREAFORMAT_AUTO ) +{ +} + +XclChEscherFormat::XclChEscherFormat() +{ +} + +XclChEscherFormat::~XclChEscherFormat() +{ +} + +XclChPicFormat::XclChPicFormat() : + mnBmpMode( EXC_CHPICFORMAT_NONE ), + mnFlags( EXC_CHPICFORMAT_TOPBOTTOM | EXC_CHPICFORMAT_FRONTBACK | EXC_CHPICFORMAT_LEFTRIGHT ), + mfScale( 0.5 ) +{ +} + +XclChFrame::XclChFrame() : + mnFormat( EXC_CHFRAME_STANDARD ), + mnFlags( EXC_CHFRAME_AUTOSIZE | EXC_CHFRAME_AUTOPOS ) +{ +} + +// Source links =============================================================== + +XclChSourceLink::XclChSourceLink() : + mnDestType( EXC_CHSRCLINK_TITLE ), + mnLinkType( EXC_CHSRCLINK_DEFAULT ), + mnFlags( 0 ), + mnNumFmtIdx( 0 ) +{ +} + +// Text ======================================================================= + +XclChObjectLink::XclChObjectLink() : + mnTarget( EXC_CHOBJLINK_NONE ) +{ +} + +XclChFrLabelProps::XclChFrLabelProps() : + mnFlags( 0 ) +{ +} + +XclChText::XclChText() : + maTextColor( COL_BLACK ), + mnHAlign( EXC_CHTEXT_ALIGN_CENTER ), + mnVAlign( EXC_CHTEXT_ALIGN_CENTER ), + mnBackMode( EXC_CHTEXT_TRANSPARENT ), + mnFlags( EXC_CHTEXT_AUTOCOLOR | EXC_CHTEXT_AUTOFILL ), + mnFlags2( EXC_CHTEXT_POS_DEFAULT ), + mnRotation( EXC_ROT_NONE ) +{ +} + +// Data series ================================================================ + +XclChMarkerFormat::XclChMarkerFormat() : + maLineColor( COL_BLACK ), + maFillColor( COL_WHITE ), + mnMarkerSize( EXC_CHMARKERFORMAT_SINGLESIZE ), + mnMarkerType( EXC_CHMARKERFORMAT_NOSYMBOL ), + mnFlags( EXC_CHMARKERFORMAT_AUTO ) +{ +}; + +XclCh3dDataFormat::XclCh3dDataFormat() : + mnBase( EXC_CH3DDATAFORMAT_RECT ), + mnTop( EXC_CH3DDATAFORMAT_STRAIGHT ) +{ +} + +XclChDataFormat::XclChDataFormat() : + mnFormatIdx( EXC_CHDATAFORMAT_DEFAULT ), + mnFlags( 0 ) +{ +} + +XclChSerTrendLine::XclChSerTrendLine() : + mfForecastFor( 0.0 ), + mfForecastBack( 0.0 ), + mnLineType( EXC_CHSERTREND_POLYNOMIAL ), + mnOrder( 1 ), + mnShowEquation( 0 ), + mnShowRSquared( 0 ) +{ + /* Set all bits in mfIntercept to 1 (that is -1.#NAN) to indicate that + there is no interception point. Cannot use ::rtl::math::setNan() here + cause it misses the sign bit. */ + sal_math_Double* pDouble = reinterpret_cast< sal_math_Double* >( &mfIntercept ); + pDouble->w32_parts.msw = pDouble->w32_parts.lsw = 0xFFFFFFFF; +} + +XclChSerErrorBar::XclChSerErrorBar() : + mfValue( 0.0 ), + mnValueCount( 1 ), + mnBarType( EXC_CHSERERR_NONE ), + mnSourceType( EXC_CHSERERR_FIXED ), + mnLineEnd( EXC_CHSERERR_END_TSHAPE ) +{ +} + +XclChSeries::XclChSeries() : + mnCategType( EXC_CHSERIES_NUMERIC ), + mnValueType( EXC_CHSERIES_NUMERIC ), + mnBubbleType( EXC_CHSERIES_NUMERIC ), + mnCategCount( 0 ), + mnValueCount( 0 ), + mnBubbleCount( 0 ) +{ +} + +// Chart type groups ========================================================== + +XclChType::XclChType() : + mnOverlap( 0 ), + mnGap( 150 ), + mnRotation( 0 ), + mnPieHole( 0 ), + mnBubbleSize( 100 ), + mnBubbleType( EXC_CHSCATTER_AREA ), + mnFlags( 0 ) +{ +} + +XclChChart3d::XclChChart3d() : + mnRotation( 20 ), + mnElevation( 15 ), + mnEyeDist( 30 ), + mnRelHeight( 100 ), + mnRelDepth( 100 ), + mnDepthGap( 150 ), + mnFlags( EXC_CHCHART3D_AUTOHEIGHT ) +{ +} + +XclChLegend::XclChLegend() : + mnDockMode( EXC_CHLEGEND_RIGHT ), + mnSpacing( EXC_CHLEGEND_MEDIUM ), + mnFlags( EXC_CHLEGEND_DOCKED | EXC_CHLEGEND_AUTOSERIES | + EXC_CHLEGEND_AUTOPOSX | EXC_CHLEGEND_AUTOPOSY | EXC_CHLEGEND_STACKED ) +{ +} + +XclChTypeGroup::XclChTypeGroup() : + mnFlags( 0 ), + mnGroupIdx( EXC_CHSERGROUP_NONE ) +{ +} + +XclChProperties::XclChProperties() : + mnFlags( 0 ), + mnEmptyMode( EXC_CHPROPS_EMPTY_SKIP ) +{ +} + +// Axes ======================================================================= + +XclChLabelRange::XclChLabelRange() : + mnCross( 1 ), + mnLabelFreq( 1 ), + mnTickFreq( 1 ), + mnFlags( 0 ) +{ +} + +XclChDateRange::XclChDateRange() : + mnMinDate( 0 ), + mnMaxDate( 0 ), + mnMajorStep( 0 ), + mnMajorUnit( EXC_CHDATERANGE_DAYS ), + mnMinorStep( 0 ), + mnMinorUnit( EXC_CHDATERANGE_DAYS ), + mnBaseUnit( EXC_CHDATERANGE_DAYS ), + mnCross( 0 ), + mnFlags( EXC_CHDATERANGE_AUTOMIN | EXC_CHDATERANGE_AUTOMAX | + EXC_CHDATERANGE_AUTOMAJOR | EXC_CHDATERANGE_AUTOMINOR | + EXC_CHDATERANGE_AUTOBASE | EXC_CHDATERANGE_AUTOCROSS | + EXC_CHDATERANGE_AUTODATE ) +{ +} + +XclChValueRange::XclChValueRange() : + mfMin( 0.0 ), + mfMax( 0.0 ), + mfMajorStep( 0.0 ), + mfMinorStep( 0.0 ), + mfCross( 0.0 ), + mnFlags( EXC_CHVALUERANGE_AUTOMIN | EXC_CHVALUERANGE_AUTOMAX | + EXC_CHVALUERANGE_AUTOMAJOR | EXC_CHVALUERANGE_AUTOMINOR | + EXC_CHVALUERANGE_AUTOCROSS | EXC_CHVALUERANGE_BIT8 ) +{ +} + +XclChTick::XclChTick() : + maTextColor( COL_BLACK ), + mnMajor( EXC_CHTICK_INSIDE | EXC_CHTICK_OUTSIDE ), + mnMinor( 0 ), + mnLabelPos( EXC_CHTICK_NEXT ), + mnBackMode( EXC_CHTICK_TRANSPARENT ), + mnFlags( EXC_CHTICK_AUTOCOLOR | EXC_CHTICK_AUTOROT ), + mnRotation( EXC_ROT_NONE ) +{ +} + +XclChAxis::XclChAxis() : + mnType( EXC_CHAXIS_NONE ) +{ +} + +sal_Int32 XclChAxis::GetApiAxisDimension() const +{ + sal_Int32 nApiAxisDim = EXC_CHART_AXIS_NONE; + switch( mnType ) + { + case EXC_CHAXIS_X: nApiAxisDim = EXC_CHART_AXIS_X; break; + case EXC_CHAXIS_Y: nApiAxisDim = EXC_CHART_AXIS_Y; break; + case EXC_CHAXIS_Z: nApiAxisDim = EXC_CHART_AXIS_Z; break; + } + return nApiAxisDim; +} + +XclChAxesSet::XclChAxesSet() : + mnAxesSetId( EXC_CHAXESSET_PRIMARY ) +{ +} + +sal_Int32 XclChAxesSet::GetApiAxesSetIndex() const +{ + sal_Int32 nApiAxesSetIdx = EXC_CHART_AXESSET_NONE; + switch( mnAxesSetId ) + { + case EXC_CHAXESSET_PRIMARY: nApiAxesSetIdx = EXC_CHART_AXESSET_PRIMARY; break; + case EXC_CHAXESSET_SECONDARY: nApiAxesSetIdx = EXC_CHART_AXESSET_SECONDARY; break; + } + return nApiAxesSetIdx; +} + +// Static helper functions ==================================================== + +sal_uInt16 XclChartHelper::GetSeriesLineAutoColorIdx( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt16 spnLineColors[] = + { + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 63 + }; + return spnLineColors[ nFormatIdx % SAL_N_ELEMENTS( spnLineColors ) ]; +} + +sal_uInt16 XclChartHelper::GetSeriesFillAutoColorIdx( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt16 spnFillColors[] = + { + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23 + }; + return spnFillColors[ nFormatIdx % SAL_N_ELEMENTS( spnFillColors ) ]; +} + +sal_uInt8 XclChartHelper::GetSeriesFillAutoTransp( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt8 spnTrans[] = { 0x00, 0x40, 0x20, 0x60, 0x70 }; + return spnTrans[ (nFormatIdx / 56) % SAL_N_ELEMENTS( spnTrans ) ]; +} + +sal_uInt16 XclChartHelper::GetAutoMarkerType( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt16 spnSymbols[] = { + EXC_CHMARKERFORMAT_DIAMOND, EXC_CHMARKERFORMAT_SQUARE, EXC_CHMARKERFORMAT_TRIANGLE, + EXC_CHMARKERFORMAT_CROSS, EXC_CHMARKERFORMAT_STAR, EXC_CHMARKERFORMAT_CIRCLE, + EXC_CHMARKERFORMAT_PLUS, EXC_CHMARKERFORMAT_DOWJ, EXC_CHMARKERFORMAT_STDDEV }; + return spnSymbols[ nFormatIdx % SAL_N_ELEMENTS( spnSymbols ) ]; +} + +bool XclChartHelper::HasMarkerFillColor( sal_uInt16 nMarkerType ) +{ + static const bool spbFilled[] = { + false, true, true, true, false, false, false, false, true, false }; + return (nMarkerType < SAL_N_ELEMENTS( spbFilled )) && spbFilled[ nMarkerType ]; +} + +OUString XclChartHelper::GetErrorBarValuesRole( sal_uInt8 nBarType ) +{ + switch( nBarType ) + { + case EXC_CHSERERR_XPLUS: return EXC_CHPROP_ROLE_ERRORBARS_POSX; + case EXC_CHSERERR_XMINUS: return EXC_CHPROP_ROLE_ERRORBARS_NEGX; + case EXC_CHSERERR_YPLUS: return EXC_CHPROP_ROLE_ERRORBARS_POSY; + case EXC_CHSERERR_YMINUS: return EXC_CHPROP_ROLE_ERRORBARS_NEGY; + default: OSL_FAIL( "XclChartHelper::GetErrorBarValuesRole - unknown bar type" ); + } + return OUString(); +} + +// Chart formatting info provider ============================================= + +namespace { + +const XclChFormatInfo spFmtInfos[] = +{ + // object type property mode auto line color auto line weight auto pattern color missing frame type create delete isframe + { EXC_CHOBJTYPE_BACKGROUND, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, true, true, true }, + { EXC_CHOBJTYPE_PLOTFRAME, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, true, true, true }, + { EXC_CHOBJTYPE_WALL3D, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, true, false, true }, + { EXC_CHOBJTYPE_FLOOR3D, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, 23, EXC_CHFRAMETYPE_AUTO, true, false, true }, + { EXC_CHOBJTYPE_TEXT, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, true, true }, + { EXC_CHOBJTYPE_LEGEND, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, true, true, true }, + { EXC_CHOBJTYPE_LINEARSERIES, EXC_CHPROPMODE_LINEARSERIES, 0xFFFF, EXC_CHLINEFORMAT_SINGLE, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, false, false, false }, + { EXC_CHOBJTYPE_FILLEDSERIES, EXC_CHPROPMODE_FILLEDSERIES, EXC_COLOR_CHBORDERAUTO, EXC_CHLINEFORMAT_SINGLE, 0xFFFF, EXC_CHFRAMETYPE_AUTO, false, false, true }, + { EXC_CHOBJTYPE_AXISLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, false, false, false }, + { EXC_CHOBJTYPE_GRIDLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, true, false }, + { EXC_CHOBJTYPE_TRENDLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_DOUBLE, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_ERRORBAR, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_SINGLE, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_CONNECTLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_HILOLINE, EXC_CHPROPMODE_LINEARSERIES, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_WHITEDROPBAR, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, true }, + { EXC_CHOBJTYPE_BLACKDROPBAR, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWTEXT, EXC_CHFRAMETYPE_INVISIBLE, false, false, true } +}; + +} + +XclChFormatInfoProvider::XclChFormatInfoProvider() +{ + for(auto const &rIt : spFmtInfos) + maInfoMap[ rIt.meObjType ] = &rIt; +} + +const XclChFormatInfo& XclChFormatInfoProvider::GetFormatInfo( XclChObjectType eObjType ) const +{ + XclFmtInfoMap::const_iterator aIt = maInfoMap.find( eObjType ); + OSL_ENSURE( aIt != maInfoMap.end(), "XclChFormatInfoProvider::GetFormatInfo - unknown object type" ); + return (aIt == maInfoMap.end()) ? *spFmtInfos : *aIt->second; +} + +// Chart type info provider =================================================== + +namespace { + +// chart type service names +const char SERVICE_CHART2_AREA[] = "com.sun.star.chart2.AreaChartType"; +const char SERVICE_CHART2_CANDLE[] = "com.sun.star.chart2.CandleStickChartType"; +const char SERVICE_CHART2_COLUMN[] = "com.sun.star.chart2.ColumnChartType"; +const char SERVICE_CHART2_LINE[] = "com.sun.star.chart2.LineChartType"; +const char SERVICE_CHART2_NET[] = "com.sun.star.chart2.NetChartType"; +const char SERVICE_CHART2_FILLEDNET[] = "com.sun.star.chart2.FilledNetChartType"; +const char SERVICE_CHART2_PIE[] = "com.sun.star.chart2.PieChartType"; +const char SERVICE_CHART2_SCATTER[] = "com.sun.star.chart2.ScatterChartType"; +const char SERVICE_CHART2_BUBBLE[] = "com.sun.star.chart2.BubbleChartType"; +const char SERVICE_CHART2_SURFACE[] = "com.sun.star.chart2.ColumnChartType"; // Todo + +namespace csscd = css::chart::DataLabelPlacement; + +const XclChTypeInfo spTypeInfos[] = +{ + // chart type chart type category record id service varied point color def label pos comb2d 3d polar area2d area3d 1stvis xcateg swap stack revers betw + { EXC_CHTYPEID_BAR, EXC_CHTYPECATEG_BAR, EXC_ID_CHBAR, SERVICE_CHART2_COLUMN, EXC_CHVARPOINT_SINGLE, csscd::OUTSIDE, true, true, false, true, true, false, true, false, true, false, true }, + { EXC_CHTYPEID_HORBAR, EXC_CHTYPECATEG_BAR, EXC_ID_CHBAR, SERVICE_CHART2_COLUMN, EXC_CHVARPOINT_SINGLE, csscd::OUTSIDE, false, true, false, true, true, false, true, true, true, false, true }, + { EXC_CHTYPEID_LINE, EXC_CHTYPECATEG_LINE, EXC_ID_CHLINE, SERVICE_CHART2_LINE, EXC_CHVARPOINT_SINGLE, csscd::RIGHT, true, true, false, false, true, false, true, false, true, false, false }, + { EXC_CHTYPEID_AREA, EXC_CHTYPECATEG_LINE, EXC_ID_CHAREA, SERVICE_CHART2_AREA, EXC_CHVARPOINT_NONE, csscd::CENTER, true, true, false, true, true, false, true, false, true, true, false }, + { EXC_CHTYPEID_STOCK, EXC_CHTYPECATEG_LINE, EXC_ID_CHLINE, SERVICE_CHART2_CANDLE, EXC_CHVARPOINT_NONE, csscd::RIGHT, true, false, false, false, false, false, true, false, true, false, false }, + { EXC_CHTYPEID_RADARLINE, EXC_CHTYPECATEG_RADAR, EXC_ID_CHRADARLINE, SERVICE_CHART2_NET, EXC_CHVARPOINT_SINGLE, csscd::TOP, false, false, true, false, true, false, true, false, false, false, false }, + { EXC_CHTYPEID_RADARAREA, EXC_CHTYPECATEG_RADAR, EXC_ID_CHRADARAREA, SERVICE_CHART2_FILLEDNET, EXC_CHVARPOINT_NONE, csscd::TOP, false, false, true, true, true, false, true, false, false, true, false }, + { EXC_CHTYPEID_PIE, EXC_CHTYPECATEG_PIE, EXC_ID_CHPIE, SERVICE_CHART2_PIE, EXC_CHVARPOINT_MULTI, csscd::AVOID_OVERLAP, false, true, true, true, true, true, true, false, false, false, false }, + { EXC_CHTYPEID_DONUT, EXC_CHTYPECATEG_PIE, EXC_ID_CHPIE, SERVICE_CHART2_PIE, EXC_CHVARPOINT_MULTI, csscd::AVOID_OVERLAP, false, true, true, true, true, false, true, false, false, false, false }, + { EXC_CHTYPEID_PIEEXT, EXC_CHTYPECATEG_PIE, EXC_ID_CHPIEEXT, SERVICE_CHART2_PIE, EXC_CHVARPOINT_MULTI, csscd::AVOID_OVERLAP, false, false, true, true, true, true, true, false, false, false, false }, + { EXC_CHTYPEID_SCATTER, EXC_CHTYPECATEG_SCATTER, EXC_ID_CHSCATTER, SERVICE_CHART2_SCATTER, EXC_CHVARPOINT_SINGLE, csscd::RIGHT, true, false, false, false, true, false, false, false, false, false, false }, + { EXC_CHTYPEID_BUBBLES, EXC_CHTYPECATEG_SCATTER, EXC_ID_CHSCATTER, SERVICE_CHART2_BUBBLE, EXC_CHVARPOINT_SINGLE, csscd::RIGHT, false, false, false, true, true, false, false, false, false, false, false }, + { EXC_CHTYPEID_SURFACE, EXC_CHTYPECATEG_SURFACE, EXC_ID_CHSURFACE, SERVICE_CHART2_SURFACE, EXC_CHVARPOINT_NONE, csscd::RIGHT, false, true, false, true, true, false, true, false, false, false, false }, + { EXC_CHTYPEID_UNKNOWN, EXC_CHTYPECATEG_BAR, EXC_ID_CHBAR, SERVICE_CHART2_COLUMN, EXC_CHVARPOINT_SINGLE, csscd::OUTSIDE, true, true, false, true, true, false, true, false, true, false, true } +}; + +} // namespace + +XclChExtTypeInfo::XclChExtTypeInfo( const XclChTypeInfo& rTypeInfo ) : + XclChTypeInfo( rTypeInfo ), + mb3dChart( false ), + mbSpline( false ) +{ +} + +void XclChExtTypeInfo::Set( const XclChTypeInfo& rTypeInfo, bool b3dChart, bool bSpline ) +{ + static_cast< XclChTypeInfo& >( *this ) = rTypeInfo; + mb3dChart = mbSupports3d && b3dChart; + mbSpline = bSpline; +} + +XclChTypeInfoProvider::XclChTypeInfoProvider() +{ + for(const auto &rIt : spTypeInfos) + maInfoMap[ rIt.meTypeId ] = &rIt; +} + +const XclChTypeInfo& XclChTypeInfoProvider::GetTypeInfo( XclChTypeId eTypeId ) const +{ + XclChTypeInfoMap::const_iterator aIt = maInfoMap.find( eTypeId ); + OSL_ENSURE( aIt != maInfoMap.end(), "XclChTypeInfoProvider::GetTypeInfo - unknown chart type" ); + return (aIt == maInfoMap.end()) ? *maInfoMap.rbegin()->second : *aIt->second; +} + +const XclChTypeInfo& XclChTypeInfoProvider::GetTypeInfoFromRecId( sal_uInt16 nRecId ) const +{ + for(const auto &rIt : spTypeInfos) + { + if(rIt.mnRecId == nRecId) + return rIt; + } + OSL_FAIL( "XclChTypeInfoProvider::GetTypeInfoFromRecId - unknown record id" ); + return GetTypeInfo( EXC_CHTYPEID_UNKNOWN ); +} + +const XclChTypeInfo& XclChTypeInfoProvider::GetTypeInfoFromService( std::u16string_view rServiceName ) const +{ + for(auto const &rIt : spTypeInfos) + if( o3tl::equalsAscii( rServiceName, rIt.mpcServiceName ) ) + return rIt; + OSL_FAIL( "XclChTypeInfoProvider::GetTypeInfoFromService - unknown service name" ); + return GetTypeInfo( EXC_CHTYPEID_UNKNOWN ); +} + +// Property helpers =========================================================== + +XclChObjectTable::XclChObjectTable(uno::Reference<lang::XMultiServiceFactory> const & xFactory, + const OUString& rServiceName, const OUString& rObjNameBase ) : + mxFactory( xFactory ), + maServiceName( rServiceName ), + maObjNameBase( rObjNameBase ), + mnIndex( 0 ) +{ +} + +uno::Any XclChObjectTable::GetObject( const OUString& rObjName ) +{ + // get object table + if( !mxContainer.is() ) + mxContainer.set(ScfApiHelper::CreateInstance( mxFactory, maServiceName ), uno::UNO_QUERY); + OSL_ENSURE( mxContainer.is(), "XclChObjectTable::GetObject - container not found" ); + + uno::Any aObj; + if( mxContainer.is() ) + { + // get object from container + try + { + aObj = mxContainer->getByName( rObjName ); + } + catch (uno::Exception &) + { + OSL_FAIL( "XclChObjectTable::GetObject - object not found" ); + } + } + return aObj; +} + +OUString XclChObjectTable::InsertObject(const uno::Any& rObj) +{ + + // create object table + if( !mxContainer.is() ) + mxContainer.set(ScfApiHelper::CreateInstance( mxFactory, maServiceName ), uno::UNO_QUERY); + OSL_ENSURE( mxContainer.is(), "XclChObjectTable::InsertObject - container not found" ); + + OUString aObjName; + if( mxContainer.is() ) + { + // create new unused identifier + do + { + aObjName = maObjNameBase + OUString::number( ++mnIndex ); + } + while( mxContainer->hasByName( aObjName ) ); + + // insert object + try + { + mxContainer->insertByName( aObjName, rObj ); + } + catch (uno::Exception &) + { + OSL_FAIL( "XclChObjectTable::InsertObject - cannot insert object" ); + aObjName.clear(); + } + } + return aObjName; +} + +// Property names ------------------------------------------------------------- + +namespace { + +/** Property names for line style in common objects. */ +const char* const sppcLineNamesCommon[] = + { "LineStyle", "LineWidth", "LineColor", "LineTransparence", "LineDashName", nullptr }; +/** Property names for line style in linear series objects. */ +const char* const sppcLineNamesLinear[] = + { "LineStyle", "LineWidth", "Color", "Transparency", "LineDashName", nullptr }; +/** Property names for line style in filled series objects. */ +const char* const sppcLineNamesFilled[] = + { "BorderStyle", "BorderWidth", "BorderColor", "BorderTransparency", "BorderDashName", nullptr }; + +/** Property names for solid area style in common objects. */ +const char* const sppcAreaNamesCommon[] = { "FillStyle", "FillColor", "FillTransparence", nullptr }; +/** Property names for solid area style in filled series objects. */ +const char* const sppcAreaNamesFilled[] = { "FillStyle", "Color", "Transparency", nullptr }; +/** Property names for gradient area style in common objects. */ +const char* const sppcGradNamesCommon[] = { "FillStyle", "FillGradientName", nullptr }; +/** Property names for gradient area style in filled series objects. */ +const char* const sppcGradNamesFilled[] = { "FillStyle", "GradientName", nullptr }; +/** Property names for hatch area style in common objects. */ +const char* const sppcHatchNamesCommon[] = { "FillStyle", "FillHatchName", "FillColor", "FillBackground", nullptr }; +/** Property names for hatch area style in filled series objects. */ +const char* const sppcHatchNamesFilled[] = { "FillStyle", "HatchName", "Color", "FillBackground", nullptr }; +/** Property names for bitmap area style. */ +const char* const sppcBitmapNames[] = { "FillStyle", "FillBitmapName", "FillBitmapMode", nullptr }; + +} // namespace + +XclChPropSetHelper::XclChPropSetHelper() : + maLineHlpCommon( sppcLineNamesCommon ), + maLineHlpLinear( sppcLineNamesLinear ), + maLineHlpFilled( sppcLineNamesFilled ), + maAreaHlpCommon( sppcAreaNamesCommon ), + maAreaHlpFilled( sppcAreaNamesFilled ), + maGradHlpCommon( sppcGradNamesCommon ), + maGradHlpFilled( sppcGradNamesFilled ), + maHatchHlpCommon( sppcHatchNamesCommon ), + maHatchHlpFilled( sppcHatchNamesFilled ), + maBitmapHlp( sppcBitmapNames ) +{ +} + +// read properties ------------------------------------------------------------ + +void XclChPropSetHelper::ReadLineProperties( + XclChLineFormat& rLineFmt, XclChObjectTable& rDashTable, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) +{ + // read properties from property set + drawing::LineStyle eApiStyle = drawing::LineStyle_NONE; + sal_Int32 nApiWidth = 0; + sal_Int16 nApiTrans = 0; + uno::Any aDashNameAny; + + ScfPropSetHelper& rLineHlp = GetLineHelper( ePropMode ); + rLineHlp.ReadFromPropertySet( rPropSet ); + rLineHlp >> eApiStyle >> nApiWidth >> rLineFmt.maColor >> nApiTrans >> aDashNameAny; + + // clear automatic flag + ::set_flag( rLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, false ); + + // line width + if( nApiWidth <= 0 ) rLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; + else if( nApiWidth <= 35 ) rLineFmt.mnWeight = EXC_CHLINEFORMAT_SINGLE; + else if( nApiWidth <= 70 ) rLineFmt.mnWeight = EXC_CHLINEFORMAT_DOUBLE; + else rLineFmt.mnWeight = EXC_CHLINEFORMAT_TRIPLE; + + // line style + switch( eApiStyle ) + { + case drawing::LineStyle_NONE: + rLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + break; + case drawing::LineStyle_SOLID: + { + if( nApiTrans < 13 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + else if( nApiTrans < 38 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_DARKTRANS; + else if( nApiTrans < 63 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_MEDTRANS; + else if( nApiTrans < 100 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_LIGHTTRANS; + else rLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + } + break; + case drawing::LineStyle_DASH: + { + rLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + OUString aDashName; + drawing::LineDash aApiDash; + if( (aDashNameAny >>= aDashName) && (rDashTable.GetObject( aDashName ) >>= aApiDash) ) + { + // reorder dashes that are shorter than dots + if( (aApiDash.Dashes == 0) || (aApiDash.DashLen < aApiDash.DotLen) ) + { + ::std::swap( aApiDash.Dashes, aApiDash.Dots ); + ::std::swap( aApiDash.DashLen, aApiDash.DotLen ); + } + // ignore dots that are nearly equal to dashes + if( aApiDash.DotLen * 3 > aApiDash.DashLen * 2 ) + aApiDash.Dots = 0; + + // convert line dash to predefined Excel dash types + if( (aApiDash.Dashes == 1) && (aApiDash.Dots >= 1) ) + // one dash and one or more dots + rLineFmt.mnPattern = (aApiDash.Dots == 1) ? + EXC_CHLINEFORMAT_DASHDOT : EXC_CHLINEFORMAT_DASHDOTDOT; + else if( aApiDash.Dashes >= 1 ) + // one or more dashes and no dots (also: dash-dash-dot) + rLineFmt.mnPattern = (aApiDash.DashLen < 250) ? + EXC_CHLINEFORMAT_DOT : EXC_CHLINEFORMAT_DASH; + } + } + break; + default: + OSL_FAIL( "XclChPropSetHelper::ReadLineProperties - unknown line style" ); + rLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + } +} + +bool XclChPropSetHelper::ReadAreaProperties( XclChAreaFormat& rAreaFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) +{ + // read properties from property set + drawing::FillStyle eApiStyle = drawing::FillStyle_NONE; + sal_Int16 nTransparency = 0; + + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.ReadFromPropertySet( rPropSet ); + rAreaHlp >> eApiStyle >> rAreaFmt.maPattColor >> nTransparency; + + // clear automatic flag + ::set_flag( rAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, false ); + + // set fill style transparent or solid (set solid for anything but transparent) + rAreaFmt.mnPattern = (eApiStyle == drawing::FillStyle_NONE) ? EXC_PATT_NONE : EXC_PATT_SOLID; + + // return true to indicate complex fill (gradient, bitmap, solid transparency) + return (eApiStyle != drawing::FillStyle_NONE) && ((eApiStyle != drawing::FillStyle_SOLID) || (nTransparency > 0)); +} + +void XclChPropSetHelper::ReadEscherProperties( + XclChEscherFormat& rEscherFmt, XclChPicFormat& rPicFmt, + XclChObjectTable& rGradientTable, XclChObjectTable& rHatchTable, XclChObjectTable& rBitmapTable, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) +{ + // read style and transparency properties from property set + drawing::FillStyle eApiStyle = drawing::FillStyle_NONE; + Color aColor; + sal_Int16 nTransparency = 0; + + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.ReadFromPropertySet( rPropSet ); + rAreaHlp >> eApiStyle >> aColor >> nTransparency; + + switch( eApiStyle ) + { + case drawing::FillStyle_SOLID: + { + OSL_ENSURE( nTransparency > 0, "XclChPropSetHelper::ReadEscherProperties - unexpected solid area without transparency" ); + if( (0 < nTransparency) && (nTransparency <= 100) ) + { + // convert to Escher properties + sal_uInt32 nEscherColor = 0x02000000; + ::insert_value( nEscherColor, aColor.GetBlue(), 16, 8 ); + ::insert_value( nEscherColor, aColor.GetGreen(), 8, 8 ); + ::insert_value( nEscherColor, aColor.GetRed(), 0, 8 ); + sal_uInt32 nEscherOpacity = static_cast< sal_uInt32 >( (100 - nTransparency) * 655.36 ); + rEscherFmt.mxEscherSet = std::make_shared<EscherPropertyContainer>(); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillType, ESCHER_FillSolid ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillColor, nEscherColor ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillOpacity, nEscherOpacity ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillBackColor, 0x02FFFFFF ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillBackOpacity, 0x00010000 ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fNoFillHitTest, 0x001F001C ); + } + } + break; + case drawing::FillStyle_GRADIENT: + { + // extract gradient from global gradient table + OUString aGradientName; + ScfPropSetHelper& rGradHlp = GetGradientHelper( ePropMode ); + rGradHlp.ReadFromPropertySet( rPropSet ); + rGradHlp >> eApiStyle >> aGradientName; + awt::Gradient aGradient; + if( rGradientTable.GetObject( aGradientName ) >>= aGradient ) + { + // convert to Escher properties + rEscherFmt.mxEscherSet = std::make_shared<EscherPropertyContainer>(); + rEscherFmt.mxEscherSet->CreateGradientProperties( aGradient ); + } + } + break; + case drawing::FillStyle_HATCH: + { + // extract hatch from global hatch table + OUString aHatchName; + bool bFillBackground; + ScfPropSetHelper& rHatchHlp = GetHatchHelper( ePropMode ); + rHatchHlp.ReadFromPropertySet( rPropSet ); + rHatchHlp >> eApiStyle >> aHatchName >> aColor >> bFillBackground; + drawing::Hatch aHatch; + if( rHatchTable.GetObject( aHatchName ) >>= aHatch ) + { + // convert to Escher properties + rEscherFmt.mxEscherSet = std::make_shared<EscherPropertyContainer>(); + rEscherFmt.mxEscherSet->CreateEmbeddedHatchProperties( aHatch, aColor, bFillBackground ); + rPicFmt.mnBmpMode = EXC_CHPICFORMAT_STACK; + } + } + break; + case drawing::FillStyle_BITMAP: + { + // extract bitmap URL from global bitmap table + OUString aBitmapName; + drawing::BitmapMode eApiBmpMode; + maBitmapHlp.ReadFromPropertySet( rPropSet ); + maBitmapHlp >> eApiStyle >> aBitmapName >> eApiBmpMode; + uno::Reference<awt::XBitmap> xBitmap; + if (rBitmapTable.GetObject( aBitmapName ) >>= xBitmap) + { + // convert to Escher properties + rEscherFmt.mxEscherSet = std::make_shared<EscherPropertyContainer>(); + rEscherFmt.mxEscherSet->CreateEmbeddedBitmapProperties( xBitmap, eApiBmpMode ); + rPicFmt.mnBmpMode = (eApiBmpMode == drawing::BitmapMode_REPEAT) ? + EXC_CHPICFORMAT_STACK : EXC_CHPICFORMAT_STRETCH; + } + } + break; + default: + OSL_FAIL( "XclChPropSetHelper::ReadEscherProperties - unknown fill style" ); + } +} + +void XclChPropSetHelper::ReadMarkerProperties( + XclChMarkerFormat& rMarkerFmt, const ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) +{ + chart2::Symbol aApiSymbol; + if( !rPropSet.GetProperty( aApiSymbol, EXC_CHPROP_SYMBOL ) ) + return; + + // clear automatic flag + ::set_flag( rMarkerFmt.mnFlags, EXC_CHMARKERFORMAT_AUTO, false ); + + // symbol style + switch( aApiSymbol.Style ) + { + case chart2::SymbolStyle_NONE: + rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_NOSYMBOL; + break; + case chart2::SymbolStyle_STANDARD: + switch( aApiSymbol.StandardSymbol ) + { + case 0: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_SQUARE; break; // square + case 1: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_DIAMOND; break; // diamond + case 2: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STDDEV; break; // arrow down + case 3: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_TRIANGLE; break; // arrow up + case 4: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_DOWJ; break; // arrow right, same as import + case 5: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_PLUS; break; // arrow left + case 6: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_CROSS; break; // bow tie + case 7: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STAR; break; // sand glass + case 8: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_CIRCLE; break; // circle new in LibO3.5 + case 9: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_DIAMOND; break; // star new in LibO3.5 + case 10: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_CROSS; break; // X new in LibO3.5 + case 11: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_PLUS; break; // plus new in LibO3.5 + case 12: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STAR; break; // asterisk new in LibO3.5 + case 13: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STDDEV; break; // horizontal bar new in LibO3.5 + case 14: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STAR; break; // vertical bar new in LibO3.5 + default: rMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + } + break; + default: + rMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + } + bool bHasFillColor = XclChartHelper::HasMarkerFillColor( rMarkerFmt.mnMarkerType ); + ::set_flag( rMarkerFmt.mnFlags, EXC_CHMARKERFORMAT_NOFILL, !bHasFillColor ); + + // symbol size + sal_Int32 nApiSize = (aApiSymbol.Size.Width + aApiSymbol.Size.Height + 1) / 2; + rMarkerFmt.mnMarkerSize = XclTools::GetTwipsFromHmm( nApiSize ); + + // symbol colors + rMarkerFmt.maLineColor = Color( ColorTransparency, aApiSymbol.BorderColor ); + rMarkerFmt.maFillColor = Color( ColorTransparency, aApiSymbol.FillColor ); +} + +sal_uInt16 XclChPropSetHelper::ReadRotationProperties( const ScfPropertySet& rPropSet, bool bSupportsStacked ) +{ + // chart2 handles rotation as double in the range [0,360) + double fAngle = 0.0; + rPropSet.GetProperty( fAngle, EXC_CHPROP_TEXTROTATION ); + bool bStacked = bSupportsStacked && rPropSet.GetBoolProperty( EXC_CHPROP_STACKCHARACTERS ); + return bStacked ? EXC_ROT_STACKED : + XclTools::GetXclRotation( Degree100(static_cast< sal_Int32 >( fAngle * 100.0 + 0.5 )) ); +} + +// write properties ----------------------------------------------------------- + +void XclChPropSetHelper::WriteLineProperties( + ScfPropertySet& rPropSet, XclChObjectTable& rDashTable, + const XclChLineFormat& rLineFmt, XclChPropertyMode ePropMode ) +{ + // line width + sal_Int32 nApiWidth = 0; // 0 is the width of a hair line + switch( rLineFmt.mnWeight ) + { + case EXC_CHLINEFORMAT_SINGLE: nApiWidth = 35; break; + case EXC_CHLINEFORMAT_DOUBLE: nApiWidth = 70; break; + case EXC_CHLINEFORMAT_TRIPLE: nApiWidth = 105; break; + } + + // line style + drawing::LineStyle eApiStyle = drawing::LineStyle_NONE; + sal_Int16 nApiTrans = 0; + sal_Int32 nDotLen = ::std::min< sal_Int32 >( rLineFmt.mnWeight + 105, 210 ); + drawing::LineDash aApiDash( drawing::DashStyle_RECT, 0, nDotLen, 0, 4 * nDotLen, nDotLen ); + + switch( rLineFmt.mnPattern ) + { + case EXC_CHLINEFORMAT_SOLID: + eApiStyle = drawing::LineStyle_SOLID; + break; + case EXC_CHLINEFORMAT_DARKTRANS: + eApiStyle = drawing::LineStyle_SOLID; nApiTrans = 25; + break; + case EXC_CHLINEFORMAT_MEDTRANS: + eApiStyle = drawing::LineStyle_SOLID; nApiTrans = 50; + break; + case EXC_CHLINEFORMAT_LIGHTTRANS: + eApiStyle = drawing::LineStyle_SOLID; nApiTrans = 75; + break; + case EXC_CHLINEFORMAT_DASH: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dashes = 1; + break; + case EXC_CHLINEFORMAT_DOT: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dots = 1; + break; + case EXC_CHLINEFORMAT_DASHDOT: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dashes = aApiDash.Dots = 1; + break; + case EXC_CHLINEFORMAT_DASHDOTDOT: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dashes = 1; aApiDash.Dots = 2; + break; + } + + // line color + sal_Int32 nApiColor = sal_Int32( rLineFmt.maColor ); + + // try to insert the dash style and receive its name + uno::Any aDashNameAny; + if( eApiStyle == drawing::LineStyle_DASH ) + { + OUString aDashName = rDashTable.InsertObject( uno::Any( aApiDash ) ); + if( !aDashName.isEmpty() ) + aDashNameAny <<= aDashName; + } + + // write the properties + ScfPropSetHelper& rLineHlp = GetLineHelper( ePropMode ); + rLineHlp.InitializeWrite(); + rLineHlp << eApiStyle << nApiWidth << nApiColor << nApiTrans << aDashNameAny; + rLineHlp.WriteToPropertySet( rPropSet ); +} + +void XclChPropSetHelper::WriteAreaProperties( ScfPropertySet& rPropSet, + const XclChAreaFormat& rAreaFmt, XclChPropertyMode ePropMode ) +{ + drawing::FillStyle eFillStyle = drawing::FillStyle_NONE; + Color aColor; + + // fill color + if( rAreaFmt.mnPattern != EXC_PATT_NONE ) + { + eFillStyle = drawing::FillStyle_SOLID; + aColor = XclTools::GetPatternColor( rAreaFmt.maPattColor, rAreaFmt.maBackColor, rAreaFmt.mnPattern ); + } + + // write the properties + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.InitializeWrite(); + rAreaHlp << eFillStyle << aColor << sal_Int16(0)/*nTransparency*/; + rAreaHlp.WriteToPropertySet( rPropSet ); +} + +void XclChPropSetHelper::WriteEscherProperties( ScfPropertySet& rPropSet, + XclChObjectTable& rGradientTable, XclChObjectTable& rBitmapTable, + const XclChEscherFormat& rEscherFmt, const XclChPicFormat* pPicFmt, + sal_uInt32 nDffFillType, XclChPropertyMode ePropMode ) +{ + if( !rEscherFmt.mxItemSet ) + return; + + const XFillStyleItem* pStyleItem = rEscherFmt.mxItemSet->GetItem<XFillStyleItem>( XATTR_FILLSTYLE, false ); + if( !pStyleItem ) + return; + + switch( pStyleItem->GetValue() ) + { + case drawing::FillStyle_SOLID: + // #i84812# Excel 2007 writes Escher properties for solid fill + if( const XFillColorItem* pColorItem = rEscherFmt.mxItemSet->GetItem<XFillColorItem>( XATTR_FILLCOLOR, false ) ) + { + // get solid transparence too + const XFillTransparenceItem* pTranspItem = rEscherFmt.mxItemSet->GetItem<XFillTransparenceItem>( XATTR_FILLTRANSPARENCE, false ); + sal_uInt16 nTransp = pTranspItem ? pTranspItem->GetValue() : 0; + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.InitializeWrite(); + rAreaHlp << drawing::FillStyle_SOLID << pColorItem->GetColorValue() << static_cast< sal_Int16 >( nTransp ); + rAreaHlp.WriteToPropertySet( rPropSet ); + } + break; + case drawing::FillStyle_GRADIENT: + if( const XFillGradientItem* pGradItem = rEscherFmt.mxItemSet->GetItem<XFillGradientItem>( XATTR_FILLGRADIENT, false ) ) + { + uno::Any aGradientAny; + if( pGradItem->QueryValue( aGradientAny, MID_FILLGRADIENT ) ) + { + OUString aGradName = rGradientTable.InsertObject( aGradientAny ); + if( !aGradName.isEmpty() ) + { + ScfPropSetHelper& rGradHlp = GetGradientHelper( ePropMode ); + rGradHlp.InitializeWrite(); + rGradHlp << drawing::FillStyle_GRADIENT << aGradName; + rGradHlp.WriteToPropertySet( rPropSet ); + } + } + } + break; + case drawing::FillStyle_BITMAP: + if( const XFillBitmapItem* pBmpItem = rEscherFmt.mxItemSet->GetItem<XFillBitmapItem>( XATTR_FILLBITMAP, false ) ) + { + uno::Any aBitmapAny; + if (pBmpItem->QueryValue(aBitmapAny, MID_BITMAP)) + { + OUString aBmpName = rBitmapTable.InsertObject( aBitmapAny ); + if( !aBmpName.isEmpty() ) + { + /* #i71810# Caller decides whether to use a CHPICFORMAT record for bitmap mode. + If not passed, detect fill mode from the DFF property 'fill-type'. */ + bool bStretch = pPicFmt ? (pPicFmt->mnBmpMode == EXC_CHPICFORMAT_STRETCH) : (nDffFillType == mso_fillPicture); + drawing::BitmapMode eApiBmpMode = bStretch ? drawing::BitmapMode_STRETCH : drawing::BitmapMode_REPEAT; + maBitmapHlp.InitializeWrite(); + maBitmapHlp << drawing::FillStyle_BITMAP << aBmpName << eApiBmpMode; + maBitmapHlp.WriteToPropertySet( rPropSet ); + } + } + } + break; + default: + OSL_FAIL( "XclChPropSetHelper::WriteEscherProperties - unknown fill mode" ); + } +} + +void XclChPropSetHelper::WriteMarkerProperties( + ScfPropertySet& rPropSet, const XclChMarkerFormat& rMarkerFmt ) +{ + // symbol style + chart2::Symbol aApiSymbol; + aApiSymbol.Style = chart2::SymbolStyle_STANDARD; + switch( rMarkerFmt.mnMarkerType ) + { + case EXC_CHMARKERFORMAT_NOSYMBOL: aApiSymbol.Style = chart2::SymbolStyle_NONE; break; + case EXC_CHMARKERFORMAT_SQUARE: aApiSymbol.StandardSymbol = 0; break; // square + case EXC_CHMARKERFORMAT_DIAMOND: aApiSymbol.StandardSymbol = 1; break; // diamond + case EXC_CHMARKERFORMAT_TRIANGLE: aApiSymbol.StandardSymbol = 3; break; // arrow up + case EXC_CHMARKERFORMAT_CROSS: aApiSymbol.StandardSymbol = 10; break; // X, legacy bow tie + case EXC_CHMARKERFORMAT_STAR: aApiSymbol.StandardSymbol = 12; break; // asterisk, legacy sand glass + case EXC_CHMARKERFORMAT_DOWJ: aApiSymbol.StandardSymbol = 4; break; // arrow right, same as export + case EXC_CHMARKERFORMAT_STDDEV: aApiSymbol.StandardSymbol = 13; break; // horizontal bar, legacy arrow down + case EXC_CHMARKERFORMAT_CIRCLE: aApiSymbol.StandardSymbol = 8; break; // circle, legacy arrow right + case EXC_CHMARKERFORMAT_PLUS: aApiSymbol.StandardSymbol = 11; break; // plus, legacy arrow left + default: break; + } + + // symbol size + sal_Int32 nApiSize = XclTools::GetHmmFromTwips( rMarkerFmt.mnMarkerSize ); + aApiSymbol.Size = awt::Size( nApiSize, nApiSize ); + + // symbol colors + aApiSymbol.FillColor = sal_Int32( rMarkerFmt.maFillColor ); + aApiSymbol.BorderColor = ::get_flag( rMarkerFmt.mnFlags, EXC_CHMARKERFORMAT_NOLINE ) ? + aApiSymbol.FillColor : sal_Int32( rMarkerFmt.maLineColor ); + + // set the property + rPropSet.SetProperty( EXC_CHPROP_SYMBOL, aApiSymbol ); +} + +void XclChPropSetHelper::WriteRotationProperties( + ScfPropertySet& rPropSet, sal_uInt16 nRotation, bool bSupportsStacked ) +{ + if( nRotation != EXC_CHART_AUTOROTATION ) + { + // chart2 handles rotation as double in the range [0,360) + double fAngle = XclTools::GetScRotation( nRotation, 0_deg100 ).get() / 100.0; + rPropSet.SetProperty( EXC_CHPROP_TEXTROTATION, fAngle ); + if( bSupportsStacked ) + rPropSet.SetProperty( EXC_CHPROP_STACKCHARACTERS, nRotation == EXC_ROT_STACKED ); + } +} + +// private -------------------------------------------------------------------- + +ScfPropSetHelper& XclChPropSetHelper::GetLineHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maLineHlpCommon; + case EXC_CHPROPMODE_LINEARSERIES: return maLineHlpLinear; + case EXC_CHPROPMODE_FILLEDSERIES: return maLineHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetLineHelper - unknown property mode" ); + } + return maLineHlpCommon; +} + +ScfPropSetHelper& XclChPropSetHelper::GetAreaHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maAreaHlpCommon; + case EXC_CHPROPMODE_FILLEDSERIES: return maAreaHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetAreaHelper - unknown property mode" ); + } + return maAreaHlpCommon; +} + +ScfPropSetHelper& XclChPropSetHelper::GetGradientHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maGradHlpCommon; + case EXC_CHPROPMODE_FILLEDSERIES: return maGradHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetGradientHelper - unknown property mode" ); + } + return maGradHlpCommon; +} + +ScfPropSetHelper& XclChPropSetHelper::GetHatchHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maHatchHlpCommon; + case EXC_CHPROPMODE_FILLEDSERIES: return maHatchHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetHatchHelper - unknown property mode" ); + } + return maHatchHlpCommon; +} + +namespace { + +/* The following local functions implement getting the XShape interface of all + supported title objects (chart and axes). This needs some effort due to the + design of the old Chart1 API used to access these objects. */ + +/** Returns the drawing shape of the main title, if existing. */ +uno::Reference<drawing::XShape> lclGetMainTitleShape(const uno::Reference<chart::XChartDocument> & rxChart1Doc) +{ + ScfPropertySet aPropSet(rxChart1Doc); + if (rxChart1Doc.is() && aPropSet.GetBoolProperty("HasMainTitle")) + return rxChart1Doc->getTitle(); + return uno::Reference<drawing::XShape>(); +} + +uno::Reference<drawing::XShape> lclGetXAxisTitleShape(const uno::Reference<chart::XChartDocument> & rxChart1Doc) +{ + uno::Reference<chart::XAxisXSupplier> xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasXAxisTitle")) + return xAxisSupp->getXAxisTitle(); + return uno::Reference<drawing::XShape>(); +} + +uno::Reference<drawing::XShape> lclGetYAxisTitleShape(const uno::Reference<chart::XChartDocument> & rxChart1Doc ) +{ + uno::Reference<chart::XAxisYSupplier> xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasYAxisTitle")) + return xAxisSupp->getYAxisTitle(); + return uno::Reference<drawing::XShape>(); +} + +uno::Reference<drawing::XShape> lclGetZAxisTitleShape(const uno::Reference<chart::XChartDocument> & rxChart1Doc ) +{ + uno::Reference<chart::XAxisZSupplier> xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasZAxisTitle")) + return xAxisSupp->getZAxisTitle(); + return uno::Reference<drawing::XShape>(); +} + +uno::Reference<drawing::XShape> lclGetSecXAxisTitleShape(const uno::Reference<chart::XChartDocument> & rxChart1Doc) +{ + uno::Reference<chart::XSecondAxisTitleSupplier> xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasSecondaryXAxisTitle")) + return xAxisSupp->getSecondXAxisTitle(); + return uno::Reference<drawing::XShape>(); +} + +uno::Reference<drawing::XShape> lclGetSecYAxisTitleShape(const uno::Reference<chart::XChartDocument> & rxChart1Doc) +{ + uno::Reference<chart::XSecondAxisTitleSupplier> xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasSecondaryYAxisTitle")) + return xAxisSupp->getSecondYAxisTitle(); + return uno::Reference<drawing::XShape>(); +} + +} // namespace + +XclChRootData::XclChRootData() + : mxTypeInfoProv(std::make_shared<XclChTypeInfoProvider>()) + , mxFmtInfoProv(std::make_shared<XclChFormatInfoProvider>()) + , mnBorderGapX(0) + , mnBorderGapY(0) + , mfUnitSizeX(0.0) + , mfUnitSizeY(0.0) +{ + // remember some title shape getter functions + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_TITLE ) ] = lclGetMainTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_PRIMARY, EXC_CHAXIS_X ) ] = lclGetXAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_PRIMARY, EXC_CHAXIS_Y ) ] = lclGetYAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_PRIMARY, EXC_CHAXIS_Z ) ] = lclGetZAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_SECONDARY, EXC_CHAXIS_X ) ] = lclGetSecXAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_SECONDARY, EXC_CHAXIS_Y ) ] = lclGetSecYAxisTitleShape; +} + +XclChRootData::~XclChRootData() +{ +} + +void XclChRootData::InitConversion(const XclRoot& rRoot, const uno::Reference<chart2::XChartDocument> & rxChartDoc, const tools::Rectangle& rChartRect) +{ + // remember chart document reference and chart shape position/size + OSL_ENSURE( rxChartDoc.is(), "XclChRootData::InitConversion - missing chart document" ); + mxChartDoc = rxChartDoc; + maChartRect = rChartRect; + + // Excel excludes a border of 5 pixels in each direction from chart area + mnBorderGapX = rRoot.GetHmmFromPixelX( 5.0 ); + mnBorderGapY = rRoot.GetHmmFromPixelY( 5.0 ); + + // size of a chart unit in 1/100 mm + mfUnitSizeX = std::max<double>( maChartRect.GetWidth() - 2 * mnBorderGapX, mnBorderGapX ) / EXC_CHART_TOTALUNITS; + mfUnitSizeY = std::max<double>( maChartRect.GetHeight() - 2 * mnBorderGapY, mnBorderGapY ) / EXC_CHART_TOTALUNITS; + + // create object tables + uno::Reference<lang::XMultiServiceFactory> xFactory(mxChartDoc, uno::UNO_QUERY); + mxLineDashTable = std::make_shared<XclChObjectTable>(xFactory, SERVICE_DRAWING_DASHTABLE, "Excel line dash "); + mxGradientTable = std::make_shared<XclChObjectTable>(xFactory, SERVICE_DRAWING_GRADIENTTABLE, "Excel gradient "); + mxHatchTable = std::make_shared<XclChObjectTable>(xFactory, SERVICE_DRAWING_HATCHTABLE, "Excel hatch "); + mxBitmapTable = std::make_shared<XclChObjectTable>(xFactory, SERVICE_DRAWING_BITMAPTABLE, "Excel bitmap "); +} + +void XclChRootData::FinishConversion() +{ + // forget formatting object tables + mxBitmapTable.reset(); + mxHatchTable.reset(); + mxGradientTable.reset(); + mxLineDashTable.reset(); + // forget chart document reference + mxChartDoc.clear(); +} + +uno::Reference<drawing::XShape> XclChRootData::GetTitleShape(const XclChTextKey& rTitleKey) const +{ + XclChGetShapeFuncMap::const_iterator aIt = maGetShapeFuncs.find( rTitleKey ); + OSL_ENSURE( aIt != maGetShapeFuncs.end(), "XclChRootData::GetTitleShape - invalid title key" ); + uno::Reference<chart::XChartDocument> xChart1Doc( mxChartDoc, uno::UNO_QUERY ); + uno::Reference<drawing::XShape> xTitleShape; + if (xChart1Doc.is() && (aIt != maGetShapeFuncs.end())) + xTitleShape = (aIt->second)(xChart1Doc); + return xTitleShape; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlescher.cxx b/sc/source/filter/excel/xlescher.cxx new file mode 100644 index 000000000..8ae663cdd --- /dev/null +++ b/sc/source/filter/excel/xlescher.cxx @@ -0,0 +1,335 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include <xlescher.hxx> + +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/script/ScriptEventDescriptor.hpp> +#include <tools/UnitConversion.hxx> +#include <document.hxx> +#include <xistream.hxx> +#include <xlroot.hxx> +#include <xltools.hxx> + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XControlShape; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::script::ScriptEventDescriptor; + +namespace { + +/** Returns the scaling factor to calculate coordinates from twips. */ +double lclGetTwipsScale( MapUnit eMapUnit ) +{ + double fScale = 1.0; + if (const auto eTo = MapToO3tlLength(eMapUnit); eTo != o3tl::Length::invalid) + fScale = o3tl::convert(1.0, o3tl::Length::twip, eTo); + else + OSL_FAIL("lclGetTwipsScale - map unit not implemented"); + return fScale; +} + +/** Calculates a drawing layer X position (in twips) from an object column position. */ +tools::Long lclGetXFromCol( const ScDocument& rDoc, SCTAB nScTab, sal_uInt16 nXclCol, sal_uInt16 nOffset, double fScale ) +{ + SCCOL nScCol = static_cast< SCCOL >( nXclCol ); + return static_cast< tools::Long >( fScale * (rDoc.GetColOffset( nScCol, nScTab ) + + ::std::min( nOffset / 1024.0, 1.0 ) * rDoc.GetColWidth( nScCol, nScTab )) + 0.5 ); +} + +/** Calculates a drawing layer Y position (in twips) from an object row position. */ +tools::Long lclGetYFromRow( const ScDocument& rDoc, SCTAB nScTab, sal_uInt16 nXclRow, sal_uInt16 nOffset, double fScale ) +{ + SCROW nScRow = static_cast< SCROW >( nXclRow ); + return static_cast< tools::Long >( fScale * (rDoc.GetRowOffset( nScRow, nScTab ) + + ::std::min( nOffset / 256.0, 1.0 ) * rDoc.GetRowHeight( nScRow, nScTab )) + 0.5 ); +} + +/** Calculates an object column position from a drawing layer X position (in twips). */ +void lclGetColFromX( + const ScDocument& rDoc, SCTAB nScTab, sal_uInt16& rnXclCol, + sal_uInt16& rnOffset, sal_uInt16 nXclStartCol, sal_uInt16 nXclMaxCol, + tools::Long& rnStartW, tools::Long nX, double fScale ) +{ + // rnStartW in conjunction with nXclStartCol is used as buffer for previously calculated width + tools::Long nTwipsX = static_cast< tools::Long >( nX / fScale + 0.5 ); + tools::Long nColW = 0; + for( rnXclCol = nXclStartCol; rnXclCol <= nXclMaxCol; ++rnXclCol ) + { + nColW = rDoc.GetColWidth( static_cast< SCCOL >( rnXclCol ), nScTab ); + if( rnStartW + nColW > nTwipsX ) + break; + rnStartW += nColW; + } + rnOffset = nColW ? static_cast< sal_uInt16 >( (nTwipsX - rnStartW) * 1024.0 / nColW + 0.5 ) : 0; +} + +/** Calculates an object row position from a drawing layer Y position (in twips). */ +void lclGetRowFromY( + const ScDocument& rDoc, SCTAB nScTab, sal_uInt32& rnXclRow, + sal_uInt32& rnOffset, sal_uInt32 nXclStartRow, sal_uInt32 nXclMaxRow, + tools::Long& rnStartH, tools::Long nY, double fScale ) +{ + // rnStartH in conjunction with nXclStartRow is used as buffer for previously calculated height + tools::Long nTwipsY = static_cast< tools::Long >( nY / fScale + 0.5 ); + tools::Long nRowH = 0; + bool bFound = false; + for( sal_uInt32 nRow = nXclStartRow; nRow <= nXclMaxRow; ++nRow ) + { + nRowH = rDoc.GetRowHeight( nRow, nScTab ); + if( rnStartH + nRowH > nTwipsY ) + { + rnXclRow = nRow; + bFound = true; + break; + } + rnStartH += nRowH; + } + if( !bFound ) + rnXclRow = nXclMaxRow; + rnOffset = static_cast< sal_uInt32 >( nRowH ? std::max((nTwipsY - rnStartH) * 256.0 / nRowH + 0.5, 0.0) : 0 ); +} + +/** Mirrors a rectangle (from LTR to RTL layout or vice versa). */ +void lclMirrorRectangle( tools::Rectangle& rRect ) +{ + tools::Long nLeft = rRect.Left(); + rRect.SetLeft( -rRect.Right() ); + rRect.SetRight( -nLeft ); +} + +sal_uInt16 lclGetEmbeddedScale( tools::Long nPageSize, sal_Int32 nPageScale, tools::Long nPos, double fPosScale ) +{ + return static_cast< sal_uInt16 >( nPos * fPosScale / nPageSize * nPageScale + 0.5 ); +} + +} // namespace + +XclObjAnchor::XclObjAnchor() : + mnLX( 0 ), + mnTY( 0 ), + mnRX( 0 ), + mnBY( 0 ) +{ +} + +tools::Rectangle XclObjAnchor::GetRect( const XclRoot& rRoot, SCTAB nScTab, MapUnit eMapUnit ) const +{ + ScDocument& rDoc = rRoot.GetDoc(); + double fScale = lclGetTwipsScale( eMapUnit ); + tools::Rectangle aRect( + lclGetXFromCol(rDoc, nScTab, std::min<SCCOL>(maFirst.mnCol, rDoc.MaxCol()), mnLX, fScale), + lclGetYFromRow(rDoc, nScTab, std::min<SCROW>(maFirst.mnRow, rDoc.MaxRow()), mnTY, fScale), + lclGetXFromCol(rDoc, nScTab, std::min<SCCOL>(maLast.mnCol, rDoc.MaxCol()), mnRX + 1, fScale), + lclGetYFromRow(rDoc, nScTab, std::min<SCROW>(maLast.mnRow, rDoc.MaxRow()), mnBY, fScale)); + + // adjust coordinates in mirrored sheets + if( rDoc.IsLayoutRTL( nScTab ) ) + lclMirrorRectangle( aRect ); + return aRect; +} + +void XclObjAnchor::SetRect( const XclRoot& rRoot, SCTAB nScTab, const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + ScDocument& rDoc = rRoot.GetDoc(); + sal_uInt16 nXclMaxCol = rRoot.GetXclMaxPos().Col(); + sal_uInt16 nXclMaxRow = static_cast<sal_uInt16>( rRoot.GetXclMaxPos().Row()); + + // adjust coordinates in mirrored sheets + tools::Rectangle aRect( rRect ); + if( rDoc.IsLayoutRTL( nScTab ) ) + lclMirrorRectangle( aRect ); + + double fScale = lclGetTwipsScale( eMapUnit ); + tools::Long nDummy = 0; + lclGetColFromX( rDoc, nScTab, maFirst.mnCol, mnLX, 0, nXclMaxCol, nDummy, aRect.Left(), fScale ); + lclGetColFromX( rDoc, nScTab, maLast.mnCol, mnRX, maFirst.mnCol, nXclMaxCol, nDummy, aRect.Right(), fScale ); + nDummy = 0; + lclGetRowFromY( rDoc, nScTab, maFirst.mnRow, mnTY, 0, nXclMaxRow, nDummy, aRect.Top(), fScale ); + lclGetRowFromY( rDoc, nScTab, maLast.mnRow, mnBY, maFirst.mnRow, nXclMaxRow, nDummy, aRect.Bottom(), fScale ); +} + +void XclObjAnchor::SetRect( const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY, + const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + double fScale = 1.0; + if (const auto eFrom = MapToO3tlLength(eMapUnit); eFrom != o3tl::Length::invalid) + fScale = o3tl::convert(1.0, eFrom, o3tl::Length::mm100); + else + OSL_FAIL("XclObjAnchor::SetRect - map unit not implemented"); + + /* In objects with DFF client anchor, the position of the shape is stored + in the cell address components of the client anchor. In old BIFF3-BIFF5 + objects, the position is stored in the offset components of the anchor. */ + maFirst.mnCol = lclGetEmbeddedScale( rPageSize.Width(), nScaleX, rRect.Left(), fScale ); + maFirst.mnRow = lclGetEmbeddedScale( rPageSize.Height(), nScaleY, rRect.Top(), fScale ); + maLast.mnCol = lclGetEmbeddedScale( rPageSize.Width(), nScaleX, rRect.Right(), fScale ); + maLast.mnRow = lclGetEmbeddedScale( rPageSize.Height(), nScaleY, rRect.Bottom(), fScale ); + + // for safety, clear the other members + mnLX = mnTY = mnRX = mnBY = 0; +} + +XclObjLineData::XclObjLineData() : + mnColorIdx( EXC_OBJ_LINE_AUTOCOLOR ), + mnStyle( EXC_OBJ_LINE_SOLID ), + mnWidth( EXC_OBJ_LINE_HAIR ), + mnAuto( EXC_OBJ_LINE_AUTO ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclObjLineData& rLineData ) +{ + rLineData.mnColorIdx = rStrm.ReaduInt8(); + rLineData.mnStyle = rStrm.ReaduInt8(); + rLineData.mnWidth = rStrm.ReaduInt8(); + rLineData.mnAuto = rStrm.ReaduInt8(); + return rStrm; +} + +XclObjFillData::XclObjFillData() : + mnBackColorIdx( EXC_OBJ_LINE_AUTOCOLOR ), + mnPattColorIdx( EXC_OBJ_FILL_AUTOCOLOR ), + mnPattern( EXC_PATT_SOLID ), + mnAuto( EXC_OBJ_FILL_AUTO ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclObjFillData& rFillData ) +{ + rFillData.mnBackColorIdx = rStrm.ReaduInt8(); + rFillData.mnPattColorIdx = rStrm.ReaduInt8(); + rFillData.mnPattern = rStrm.ReaduInt8(); + rFillData.mnAuto = rStrm.ReaduInt8(); + return rStrm; +} + +XclObjTextData::XclObjTextData() : + mnTextLen( 0 ), + mnFormatSize( 0 ), + mnLinkSize( 0 ), + mnDefFontIdx( EXC_FONT_APP ), + mnFlags( 0 ), + mnOrient( EXC_OBJ_ORIENT_NONE ), + mnButtonFlags( 0 ), + mnShortcut( 0 ), + mnShortcutEA( 0 ) +{ +} + +void XclObjTextData::ReadObj3( XclImpStream& rStrm ) +{ + mnTextLen = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFormatSize = rStrm.ReaduInt16(); + mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFlags = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + rStrm.Ignore( 8 ); +} + +void XclObjTextData::ReadObj5( XclImpStream& rStrm ) +{ + mnTextLen = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFormatSize = rStrm.ReaduInt16(); + mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFlags = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnButtonFlags = rStrm.ReaduInt16(); + mnShortcut = rStrm.ReaduInt16(); + mnShortcutEA = rStrm.ReaduInt16(); +} + +void XclObjTextData::ReadTxo8( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + mnButtonFlags = rStrm.ReaduInt16(); + mnShortcut = rStrm.ReaduInt16(); + mnShortcutEA = rStrm.ReaduInt16(); + mnTextLen = rStrm.ReaduInt16(); + mnFormatSize = rStrm.ReaduInt16(); +} + +Reference< XControlModel > XclControlHelper::GetControlModel( Reference< XShape > const & xShape ) +{ + Reference< XControlModel > xCtrlModel; + Reference< XControlShape > xCtrlShape( xShape, UNO_QUERY ); + if( xCtrlShape.is() ) + xCtrlModel = xCtrlShape->getControl(); + return xCtrlModel; +} + +namespace { + +const struct +{ + const char* mpcListenerType; + const char* mpcEventMethod; +} +spTbxListenerData[] = +{ + // Attention: MUST be in order of the XclTbxEventType enum! + /*EXC_TBX_EVENT_ACTION*/ { "XActionListener", "actionPerformed" }, + /*EXC_TBX_EVENT_MOUSE*/ { "XMouseListener", "mouseReleased" }, + /*EXC_TBX_EVENT_TEXT*/ { "XTextListener", "textChanged" }, + /*EXC_TBX_EVENT_VALUE*/ { "XAdjustmentListener", "adjustmentValueChanged" }, + /*EXC_TBX_EVENT_CHANGE*/ { "XChangeListener", "changed" } +}; + +} // namespace + +bool XclControlHelper::FillMacroDescriptor( ScriptEventDescriptor& rDescriptor, + XclTbxEventType eEventType, const OUString& rXclMacroName, SfxObjectShell* pDocShell ) +{ + if( !rXclMacroName.isEmpty() ) + { + rDescriptor.ListenerType = OUString::createFromAscii( spTbxListenerData[ eEventType ].mpcListenerType ); + rDescriptor.EventMethod = OUString::createFromAscii( spTbxListenerData[ eEventType ].mpcEventMethod ); + rDescriptor.ScriptType = "Script"; + rDescriptor.ScriptCode = XclTools::GetSbMacroUrl( rXclMacroName, pDocShell ); + return true; + } + return false; +} + +OUString XclControlHelper::ExtractFromMacroDescriptor( + const ScriptEventDescriptor& rDescriptor, XclTbxEventType eEventType ) +{ + if( (!rDescriptor.ScriptCode.isEmpty()) && + rDescriptor.ScriptType.equalsIgnoreAsciiCase("Script") && + rDescriptor.ListenerType.equalsAscii( spTbxListenerData[ eEventType ].mpcListenerType ) && + rDescriptor.EventMethod.equalsAscii( spTbxListenerData[ eEventType ].mpcEventMethod ) ) + return XclTools::GetXclMacroName( rDescriptor.ScriptCode ); + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlformula.cxx b/sc/source/filter/excel/xlformula.cxx new file mode 100644 index 000000000..e2e082ac2 --- /dev/null +++ b/sc/source/filter/excel/xlformula.cxx @@ -0,0 +1,1022 @@ +/* -*- 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 <xlformula.hxx> + +#include <refdata.hxx> +#include <tokenarray.hxx> +#include <xestream.hxx> +#include <xistream.hxx> +#include <xlroot.hxx> + +#include <comphelper/string.hxx> +#include <svl/sharedstringpool.hxx> + +using namespace ::formula; + +// Function data ============================================================== + +OUString XclFunctionInfo::GetMacroFuncName() const +{ + if( IsMacroFunc() ) + return OUString( mpcMacroName, strlen(mpcMacroName), RTL_TEXTENCODING_UTF8 ); + return OUString(); +} + +OUString XclFunctionInfo::GetAddInEquivalentFuncName() const +{ + if( IsAddInEquivalent() ) + return OUString( mpcMacroName, strlen(mpcMacroName), RTL_TEXTENCODING_UTF8 ); + return OUString(); +} + +// abbreviations for function return token class +const sal_uInt8 R = EXC_TOKCLASS_REF; +const sal_uInt8 V = EXC_TOKCLASS_VAL; +const sal_uInt8 A = EXC_TOKCLASS_ARR; + +// abbreviations for parameter infos +#define RO { EXC_PARAM_REGULAR, EXC_PARAMCONV_ORG, false } +#define RA { EXC_PARAM_REGULAR, EXC_PARAMCONV_ARR, false } +#define RR { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPT, false } +#define RX { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPX, false } +#define VO { EXC_PARAM_REGULAR, EXC_PARAMCONV_ORG, true } +#define VV { EXC_PARAM_REGULAR, EXC_PARAMCONV_VAL, true } +#define VA { EXC_PARAM_REGULAR, EXC_PARAMCONV_ARR, true } +#define VR { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPT, true } +#define VX { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPX, true } +#define RO_E { EXC_PARAM_EXCELONLY, EXC_PARAMCONV_ORG, false } +#define VR_E { EXC_PARAM_EXCELONLY, EXC_PARAMCONV_RPT, true } +#define C { EXC_PARAM_CALCONLY, EXC_PARAMCONV_ORG, false } + +const sal_uInt16 NOID = SAL_MAX_UINT16; /// No BIFF/OOBIN function identifier available. +const sal_uInt8 MX = 30; /// Maximum parameter count. + +#define EXC_FUNCNAME( ascii ) "_xlfn." ascii +#define EXC_FUNCNAME_ODF( ascii ) "_xlfnodf." ascii +#define EXC_FUNCNAME_ADDIN( ascii ) "com.sun.star.sheet.addin." ascii + +/** Functions new in BIFF2. */ +const XclFunctionInfo saFuncTable_2[] = +{ + { ocCount, 0, 0, MX, V, { RX }, 0, nullptr }, + { ocIf, 1, 2, 3, R, { VO, RO }, 0, nullptr }, + { ocIsNA, 2, 1, 1, V, { VR }, 0, nullptr }, + { ocIsError, 3, 1, 1, V, { VR }, 0, nullptr }, + { ocSum, 4, 0, MX, V, { RX }, 0, nullptr }, + { ocAverage, 5, 1, MX, V, { RX }, 0, nullptr }, + { ocMin, 6, 1, MX, V, { RX }, 0, nullptr }, + { ocMax, 7, 1, MX, V, { RX }, 0, nullptr }, + { ocRow, 8, 0, 1, V, { RO }, 0, nullptr }, + { ocColumn, 9, 0, 1, V, { RO }, 0, nullptr }, + { ocNotAvail, 10, 0, 0, V, {}, 0, nullptr }, + { ocNPV, 11, 2, MX, V, { VR, RX }, 0, nullptr }, + { ocStDev, 12, 1, MX, V, { RX }, 0, nullptr }, + { ocCurrency, 13, 1, 2, V, { VR }, 0, nullptr }, + { ocFixed, 14, 1, 2, V, { VR, VR, C }, 0, nullptr }, + { ocSin, 15, 1, 1, V, { VR }, 0, nullptr }, + { ocCosecant, 15, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocCos, 16, 1, 1, V, { VR }, 0, nullptr }, + { ocSecant, 16, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocTan, 17, 1, 1, V, { VR }, 0, nullptr }, + { ocCot, 17, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocArcTan, 18, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCot, 18, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocPi, 19, 0, 0, V, {}, 0, nullptr }, + { ocSqrt, 20, 1, 1, V, { VR }, 0, nullptr }, + { ocExp, 21, 1, 1, V, { VR }, 0, nullptr }, + { ocLn, 22, 1, 1, V, { VR }, 0, nullptr }, + { ocLog10, 23, 1, 1, V, { VR }, 0, nullptr }, + { ocAbs, 24, 1, 1, V, { VR }, 0, nullptr }, + { ocInt, 25, 1, 1, V, { VR }, 0, nullptr }, + { ocPlusMinus, 26, 1, 1, V, { VR }, 0, nullptr }, + { ocRound, 27, 2, 2, V, { VR }, 0, nullptr }, + { ocLookup, 28, 2, 3, V, { VR, RA }, 0, nullptr }, + { ocIndex, 29, 2, 4, R, { RA, VV }, 0, nullptr }, + { ocRept, 30, 2, 2, V, { VR }, 0, nullptr }, + { ocMid, 31, 3, 3, V, { VR }, 0, nullptr }, + { ocLen, 32, 1, 1, V, { VR }, 0, nullptr }, + { ocValue, 33, 1, 1, V, { VR }, 0, nullptr }, + { ocTrue, 34, 0, 0, V, {}, 0, nullptr }, + { ocFalse, 35, 0, 0, V, {}, 0, nullptr }, + { ocAnd, 36, 1, MX, V, { RX }, 0, nullptr }, + { ocOr, 37, 1, MX, V, { RX }, 0, nullptr }, + { ocNot, 38, 1, 1, V, { VR }, 0, nullptr }, + { ocMod, 39, 2, 2, V, { VR }, 0, nullptr }, + { ocDBCount, 40, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBSum, 41, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBAverage, 42, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBMin, 43, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBMax, 44, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBStdDev, 45, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocVar, 46, 1, MX, V, { RX }, 0, nullptr }, + { ocDBVar, 47, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocText, 48, 2, 2, V, { VR }, 0, nullptr }, + { ocLinest, 49, 1, 2, A, { RA, RA, C, C }, 0, nullptr }, + { ocTrend, 50, 1, 3, A, { RA, RA, RA, C }, 0, nullptr }, + { ocLogest, 51, 1, 2, A, { RA, RA, C, C }, 0, nullptr }, + { ocGrowth, 52, 1, 3, A, { RA, RA, RA, C }, 0, nullptr }, + { ocPV, 56, 3, 5, V, { VR }, 0, nullptr }, + { ocFV, 57, 3, 5, V, { VR }, 0, nullptr }, + { ocNper, 58, 3, 5, V, { VR }, 0, nullptr }, + { ocPMT, 59, 3, 5, V, { VR }, 0, nullptr }, + { ocRate, 60, 3, 6, V, { VR }, 0, nullptr }, + { ocMIRR, 61, 3, 3, V, { RA, VR }, 0, nullptr }, + { ocIRR, 62, 1, 2, V, { RA, VR }, 0, nullptr }, + { ocRandom, 63, 0, 0, V, {}, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocMatch, 64, 2, 3, V, { VR, RX, RR }, 0, nullptr }, + { ocGetDate, 65, 3, 3, V, { VR }, 0, nullptr }, + { ocGetTime, 66, 3, 3, V, { VR }, 0, nullptr }, + { ocGetDay, 67, 1, 1, V, { VR }, 0, nullptr }, + { ocGetMonth, 68, 1, 1, V, { VR }, 0, nullptr }, + { ocGetYear, 69, 1, 1, V, { VR }, 0, nullptr }, + { ocGetDayOfWeek, 70, 1, 1, V, { VR, C }, 0, nullptr }, + { ocGetHour, 71, 1, 1, V, { VR }, 0, nullptr }, + { ocGetMin, 72, 1, 1, V, { VR }, 0, nullptr }, + { ocGetSec, 73, 1, 1, V, { VR }, 0, nullptr }, + { ocGetActTime, 74, 0, 0, V, {}, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocAreas, 75, 1, 1, V, { RO }, 0, nullptr }, + { ocRows, 76, 1, 1, V, { RO }, 0, nullptr }, + { ocColumns, 77, 1, 1, V, { RO }, 0, nullptr }, + { ocOffset, 78, 3, 5, R, { RO, VR }, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocSearch, 82, 2, 3, V, { VR }, 0, nullptr }, + { ocMatTrans, 83, 1, 1, A, { VO }, 0, nullptr }, + { ocType, 86, 1, 1, V, { VX }, 0, nullptr }, + { ocArcTan2, 97, 2, 2, V, { VR }, 0, nullptr }, + { ocArcSin, 98, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCos, 99, 1, 1, V, { VR }, 0, nullptr }, + { ocChoose, 100, 2, MX, R, { VO, RO }, 0, nullptr }, + { ocHLookup, 101, 3, 3, V, { VV, RO, RO, C }, 0, nullptr }, + { ocVLookup, 102, 3, 3, V, { VV, RO, RO, C }, 0, nullptr }, + { ocIsRef, 105, 1, 1, V, { RX }, 0, nullptr }, + { ocLog, 109, 1, 2, V, { VR }, 0, nullptr }, + { ocChar, 111, 1, 1, V, { VR }, 0, nullptr }, + { ocLower, 112, 1, 1, V, { VR }, 0, nullptr }, + { ocUpper, 113, 1, 1, V, { VR }, 0, nullptr }, + { ocProper, 114, 1, 1, V, { VR }, 0, nullptr }, + { ocLeft, 115, 1, 2, V, { VR }, 0, nullptr }, + { ocRight, 116, 1, 2, V, { VR }, 0, nullptr }, + { ocExact, 117, 2, 2, V, { VR }, 0, nullptr }, + { ocTrim, 118, 1, 1, V, { VR }, 0, nullptr }, + { ocReplace, 119, 4, 4, V, { VR }, 0, nullptr }, + { ocSubstitute, 120, 3, 4, V, { VR }, 0, nullptr }, + { ocCode, 121, 1, 1, V, { VR }, 0, nullptr }, + { ocFind, 124, 2, 3, V, { VR }, 0, nullptr }, + { ocCell, 125, 1, 2, V, { VV, RO }, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocIsErr, 126, 1, 1, V, { VR }, 0, nullptr }, + { ocIsString, 127, 1, 1, V, { VR }, 0, nullptr }, + { ocIsValue, 128, 1, 1, V, { VR }, 0, nullptr }, + { ocIsEmpty, 129, 1, 1, V, { VR }, 0, nullptr }, + { ocT, 130, 1, 1, V, { RO }, 0, nullptr }, + { ocN, 131, 1, 1, V, { RO }, 0, nullptr }, + { ocGetDateValue, 140, 1, 1, V, { VR }, 0, nullptr }, + { ocGetTimeValue, 141, 1, 1, V, { VR }, 0, nullptr }, + { ocSLN, 142, 3, 3, V, { VR }, 0, nullptr }, + { ocSYD, 143, 4, 4, V, { VR }, 0, nullptr }, + { ocDDB, 144, 4, 5, V, { VR }, 0, nullptr }, + { ocIndirect, 148, 1, 2, R, { VR }, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocClean, 162, 1, 1, V, { VR }, 0, nullptr }, + { ocMatDet, 163, 1, 1, V, { VA }, 0, nullptr }, + { ocMatInv, 164, 1, 1, A, { VA }, 0, nullptr }, + { ocMatMult, 165, 2, 2, A, { VA }, 0, nullptr }, + { ocIpmt, 167, 4, 6, V, { VR }, 0, nullptr }, + { ocPpmt, 168, 4, 6, V, { VR }, 0, nullptr }, + { ocCount2, 169, 0, MX, V, { RX }, 0, nullptr }, + { ocProduct, 183, 0, MX, V, { RX }, 0, nullptr }, + { ocFact, 184, 1, 1, V, { VR }, 0, nullptr }, + { ocDBProduct, 189, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocIsNonString, 190, 1, 1, V, { VR }, 0, nullptr }, + { ocStDevP, 193, 1, MX, V, { RX }, 0, nullptr }, + { ocVarP, 194, 1, MX, V, { RX }, 0, nullptr }, + { ocDBStdDevP, 195, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBVarP, 196, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocTrunc, 197, 1, 1, V, { VR, C }, 0, nullptr }, + { ocIsLogical, 198, 1, 1, V, { VR }, 0, nullptr }, + { ocDBCount2, 199, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocCurrency, 204, 1, 2, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, + { ocFindB, 205, 2, 3, V, { VR }, 0, nullptr }, + { ocSearchB, 206, 2, 3, V, { VR }, 0, nullptr }, + { ocReplaceB, 207, 4, 4, V, { VR }, 0, nullptr }, + { ocLeftB, 208, 1, 2, V, { VR }, 0, nullptr }, + { ocRightB, 209, 1, 2, V, { VR }, 0, nullptr }, + { ocMidB, 210, 3, 3, V, { VR }, 0, nullptr }, + { ocLenB, 211, 1, 1, V, { VR }, 0, nullptr }, + { ocRoundUp, 212, 2, 2, V, { VR }, 0, nullptr }, + { ocRoundDown, 213, 2, 2, V, { VR }, 0, nullptr }, + { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr } +}; + +/** Functions new in BIFF3. */ +const XclFunctionInfo saFuncTable_3[] = +{ + { ocLinest, 49, 1, 4, A, { RA, RA, VV }, 0, nullptr }, // BIFF2: 1-2, BIFF3: 1-4 + { ocTrend, 50, 1, 4, A, { RA, RA, RA, VV }, 0, nullptr }, // BIFF2: 1-3, BIFF3: 1-4 + { ocLogest, 51, 1, 4, A, { RA, RA, VV }, 0, nullptr }, // BIFF2: 1-2, BIFF3: 1-4 + { ocGrowth, 52, 1, 4, A, { RA, RA, RA, VV }, 0, nullptr }, // BIFF2: 1-3, BIFF3: 1-4 + { ocTrunc, 197, 1, 2, V, { VR }, 0, nullptr }, // BIFF2: 1, BIFF3: 1-2 + { ocAddress, 219, 2, 5, V, { VR }, 0, nullptr }, + { ocGetDiffDate360, 220, 2, 2, V, { VR, VR, C }, 0, nullptr }, + { ocGetActDate, 221, 0, 0, V, {}, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocVBD, 222, 5, 7, V, { VR }, 0, nullptr }, + { ocMedian, 227, 1, MX, V, { RX }, 0, nullptr }, + { ocSumProduct, 228, 1, MX, V, { VA }, 0, nullptr }, + { ocSinHyp, 229, 1, 1, V, { VR }, 0, nullptr }, + { ocCosecantHyp, 229, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocCosHyp, 230, 1, 1, V, { VR }, 0, nullptr }, + { ocSecantHyp, 230, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocTanHyp, 231, 1, 1, V, { VR }, 0, nullptr }, + { ocCotHyp, 231, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocArcSinHyp, 232, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCosHyp, 233, 1, 1, V, { VR }, 0, nullptr }, + { ocArcTanHyp, 234, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCotHyp, 234, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocDBGet, 235, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocInfo, 244, 1, 1, V, { VR }, EXC_FUNCFLAG_VOLATILE, nullptr } +}; + +/** Functions new in BIFF4. */ +const XclFunctionInfo saFuncTable_4[] = +{ + { ocFixed, 14, 1, 3, V, { VR }, 0, nullptr }, // BIFF2-3: 1-2, BIFF4: 1-3 + { ocAsc, 214, 1, 1, V, { VR }, 0, nullptr }, + { ocJis, 215, 1, 1, V, { VR }, 0, nullptr }, + { ocRank, 216, 2, 3, V, { VR, RO, VR }, 0, nullptr }, + { ocDB, 247, 4, 5, V, { VR }, 0, nullptr }, + { ocFrequency, 252, 2, 2, A, { RA }, 0, nullptr }, + { ocErrorType_ODF, 261, 1, 1, V, { VR }, 0, nullptr }, + { ocAveDev, 269, 1, MX, V, { RX }, 0, nullptr }, + { ocBetaDist, 270, 3, 5, V, { VR }, 0, nullptr }, + { ocGammaLn, 271, 1, 1, V, { VR }, 0, nullptr }, + { ocBetaInv, 272, 3, 5, V, { VR }, 0, nullptr }, + { ocBinomDist, 273, 4, 4, V, { VR }, 0, nullptr }, + { ocChiDist, 274, 2, 2, V, { VR }, 0, nullptr }, + { ocChiInv, 275, 2, 2, V, { VR }, 0, nullptr }, + { ocCombin, 276, 2, 2, V, { VR }, 0, nullptr }, + { ocConfidence, 277, 3, 3, V, { VR }, 0, nullptr }, + { ocCritBinom, 278, 3, 3, V, { VR }, 0, nullptr }, + { ocEven, 279, 1, 1, V, { VR }, 0, nullptr }, + { ocExpDist, 280, 3, 3, V, { VR }, 0, nullptr }, + { ocFDist, 281, 3, 3, V, { VR }, 0, nullptr }, + { ocFInv, 282, 3, 3, V, { VR }, 0, nullptr }, + { ocFisher, 283, 1, 1, V, { VR }, 0, nullptr }, + { ocFisherInv, 284, 1, 1, V, { VR }, 0, nullptr }, + { ocFloor_MS, 285, 2, 2, V, { VR }, 0, nullptr }, + { ocGammaDist, 286, 4, 4, V, { VR }, 0, nullptr }, + { ocGammaInv, 287, 3, 3, V, { VR }, 0, nullptr }, + { ocCeil_MS, 288, 2, 2, V, { VR }, 0, nullptr }, + { ocHypGeomDist, 289, 4, 4, V, { VR }, 0, nullptr }, + { ocLogNormDist, 290, 3, 3, V, { VR }, 0, nullptr }, + { ocLogInv, 291, 3, 3, V, { VR }, 0, nullptr }, + { ocNegBinomVert, 292, 3, 3, V, { VR }, 0, nullptr }, + { ocNormDist, 293, 4, 4, V, { VR }, 0, nullptr }, + { ocStdNormDist, 294, 1, 1, V, { VR }, 0, nullptr }, + { ocNormInv, 295, 3, 3, V, { VR }, 0, nullptr }, + { ocSNormInv, 296, 1, 1, V, { VR }, 0, nullptr }, + { ocStandard, 297, 3, 3, V, { VR }, 0, nullptr }, + { ocOdd, 298, 1, 1, V, { VR }, 0, nullptr }, + { ocPermut, 299, 2, 2, V, { VR }, 0, nullptr }, + { ocPoissonDist, 300, 3, 3, V, { VR }, 0, nullptr }, + { ocTDist, 301, 3, 3, V, { VR }, 0, nullptr }, + { ocWeibull, 302, 4, 4, V, { VR }, 0, nullptr }, + { ocSumXMY2, 303, 2, 2, V, { VA }, 0, nullptr }, + { ocSumX2MY2, 304, 2, 2, V, { VA }, 0, nullptr }, + { ocSumX2DY2, 305, 2, 2, V, { VA }, 0, nullptr }, + { ocChiTest, 306, 2, 2, V, { VA }, 0, nullptr }, + { ocCorrel, 307, 2, 2, V, { VA }, 0, nullptr }, + { ocCovar, 308, 2, 2, V, { VA }, 0, nullptr }, + { ocForecast, 309, 3, 3, V, { VR, VA }, 0, nullptr }, + { ocFTest, 310, 2, 2, V, { VA }, 0, nullptr }, + { ocIntercept, 311, 2, 2, V, { VA }, 0, nullptr }, + { ocPearson, 312, 2, 2, V, { VA }, 0, nullptr }, + { ocRSQ, 313, 2, 2, V, { VA }, 0, nullptr }, + { ocSTEYX, 314, 2, 2, V, { VA }, 0, nullptr }, + { ocSlope, 315, 2, 2, V, { VA }, 0, nullptr }, + { ocTTest, 316, 4, 4, V, { VA, VA, VR }, 0, nullptr }, + { ocProb, 317, 3, 4, V, { VA, VA, VR }, 0, nullptr }, + { ocDevSq, 318, 1, MX, V, { RX }, 0, nullptr }, + { ocGeoMean, 319, 1, MX, V, { RX }, 0, nullptr }, + { ocHarMean, 320, 1, MX, V, { RX }, 0, nullptr }, + { ocSumSQ, 321, 0, MX, V, { RX }, 0, nullptr }, + { ocKurt, 322, 1, MX, V, { RX }, 0, nullptr }, + { ocSkew, 323, 1, MX, V, { RX }, 0, nullptr }, + { ocZTest, 324, 2, 3, V, { RX, VR }, 0, nullptr }, + { ocLarge, 325, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocSmall, 326, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocQuartile, 327, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocPercentile, 328, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocPercentrank, 329, 2, 3, V, { RX, VR, VR_E }, 0, nullptr }, + { ocModalValue, 330, 1, MX, V, { VA }, 0, nullptr }, + { ocTrimMean, 331, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocTInv, 332, 2, 2, V, { VR }, 0, nullptr }, + // Functions equivalent to add-in functions, use same parameters as + // ocExternal but add programmatical function name (here without + // "com.sun.star.sheet.addin.") so it can be looked up and stored as + // add-in, as older Excel versions only know them as add-in. + { ocIsEven, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getIseven" ) }, + { ocIsOdd, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getIsodd" ) }, + { ocGCD, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getGcd" ) }, + { ocLCM, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getLcm" ) }, + { ocEffect, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getEffect" ) }, + { ocCumPrinc, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getCumprinc" ) }, + { ocCumIpmt, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getCumipmt" ) }, + { ocNominal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getNominal" ) }, + { ocNetWorkdays, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getNetworkdays" ) } +}; + +/** Functions new in BIFF5/BIFF7. Unsupported functions: DATESTRING, NUMBERSTRING. */ +const XclFunctionInfo saFuncTable_5[] = +{ + { ocGetDayOfWeek, 70, 1, 2, V, { VR }, 0, nullptr }, // BIFF2-4: 1, BIFF5: 1-2 + { ocHLookup, 101, 3, 4, V, { VV, RO, RO, VV }, 0, nullptr }, // BIFF2-4: 3, BIFF5: 3-4 + { ocVLookup, 102, 3, 4, V, { VV, RO, RO, VV }, 0, nullptr }, // BIFF2-4: 3, BIFF5: 3-4 + { ocGetDiffDate360, 220, 2, 3, V, { VR }, 0, nullptr }, // BIFF3-4: 2, BIFF5: 2-3 + { ocMacro, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocConcat, 336, 0, MX, V, { VR }, 0, nullptr }, + { ocPower, 337, 2, 2, V, { VR }, 0, nullptr }, + { ocRad, 342, 1, 1, V, { VR }, 0, nullptr }, + { ocDeg, 343, 1, 1, V, { VR }, 0, nullptr }, + { ocSubTotal, 344, 2, MX, V, { VR, RO }, 0, nullptr }, + { ocSumIf, 345, 2, 3, V, { RO, VR, RO }, 0, nullptr }, + { ocCountIf, 346, 2, 2, V, { RO, VR }, 0, nullptr }, + { ocCountEmptyCells, 347, 1, 1, V, { RO }, 0, nullptr }, + { ocISPMT, 350, 4, 4, V, { VR }, 0, nullptr }, + { ocGetDateDif, 351, 3, 3, V, { VR }, 0, nullptr }, + { ocNoName, 352, 1, 1, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, // DATESTRING + { ocNoName, 353, 2, 2, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, // NUMBERSTRING + { ocRoman, 354, 1, 2, V, { VR }, 0, nullptr } +}; + +/** Functions new in BIFF8. Unsupported functions: PHONETIC. */ +const XclFunctionInfo saFuncTable_8[] = +{ + { ocGetPivotData, 358, 2, MX, V, { RR, RR, VR }, 0, nullptr }, + { ocHyperLink, 359, 1, 2, V, { VV, VO }, 0, nullptr }, + { ocNoName, 360, 1, 1, V, { RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, // PHONETIC + { ocAverageA, 361, 1, MX, V, { RX }, 0, nullptr }, + { ocMaxA, 362, 1, MX, V, { RX }, 0, nullptr }, + { ocMinA, 363, 1, MX, V, { RX }, 0, nullptr }, + { ocStDevPA, 364, 1, MX, V, { RX }, 0, nullptr }, + { ocVarPA, 365, 1, MX, V, { RX }, 0, nullptr }, + { ocStDevA, 366, 1, MX, V, { RX }, 0, nullptr }, + { ocVarA, 367, 1, MX, V, { RX }, 0, nullptr }, + { ocBahtText, 368, 1, 1, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "BAHTTEXT" ) }, + { ocBahtText, 255, 2, 2, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "BAHTTEXT" ) }, + { ocEuroConvert, 255, 4, 6, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, "EUROCONVERT" } +}; + +#define EXC_FUNCENTRY_V_VR( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +/** Functions new in OOXML. */ +const XclFunctionInfo saFuncTable_Oox[] = +{ + { ocCountIfs, NOID, 2, MX, V, { RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "COUNTIFS" ) }, + { ocCountIfs, 255, 3, MX, V, { RO_E, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "COUNTIFS" ) }, + { ocSumIfs, NOID, 3, MX, V, { RO, RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "SUMIFS" ) }, + { ocSumIfs, 255, 4, MX, V, { RO_E, RO, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "SUMIFS" ) }, + { ocAverageIf, NOID, 2, 3, V, { RO, VR, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "AVERAGEIF" ) }, + { ocAverageIf, 255, 3, 4, V, { RO_E, RO, VR, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "AVERAGEIF" ) }, + { ocAverageIfs, NOID, 3, MX, V, { RO, RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) }, + { ocAverageIfs, 255, 4, MX, V, { RO_E, RO, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) }, + { ocIfError, NOID, 2, 2, V, { VO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "IFERROR" ) }, + { ocIfError, 255, 3, 3, V, { RO_E, VO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "IFERROR" ) }, + { ocNetWorkdays_MS, NOID, 2, 4, V, { VR, VR, RO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "NETWORKDAYS.INTL" ) }, + { ocNetWorkdays_MS, 255, 3, 5, V, { RO_E, VR, VR, RO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "NETWORKDAYS.INTL" ) }, + { ocWorkday_MS, NOID, 2, 4, V, { VR, VR, VR, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "WORKDAY.INTL" ) }, + { ocWorkday_MS, 255, 3, 5, V, { RO_E, VR, VR, VR, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "WORKDAY.INTL" ) }, + EXC_FUNCENTRY_V_VR( ocCeil_ISO, 1, 2, 0, "ISO.CEILING" ) +}; + +#define EXC_FUNCENTRY_V_VR_IMPORT( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_V_RO_EXPORT( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_A_VR( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, A, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, A, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_V_RO( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { RO }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +// implicit maxparam=MX +#define EXC_FUNCENTRY_V_RX( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, MX, V, { RX }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, MX, V, { RO_E, RX }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_V_VA( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VA }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +/** Functions new in Excel 2010. + + See http://office.microsoft.com/en-us/excel-help/what-s-new-changes-made-to-excel-functions-HA010355760.aspx + A lot of statistical functions have been renamed (the 'old' function names still exist). + + @See sc/source/filter/oox/formulabase.cxx saFuncTable2010 for V,VR,RO,... + */ +const XclFunctionInfo saFuncTable_2010[] = +{ + EXC_FUNCENTRY_V_VA( ocCovarianceP, 2, 2, 0, "COVARIANCE.P" ), + EXC_FUNCENTRY_V_VA( ocCovarianceS, 2, 2, 0, "COVARIANCE.S" ), + EXC_FUNCENTRY_V_RX( ocStDevP_MS, 1, MX, 0, "STDEV.P" ), + EXC_FUNCENTRY_V_RX( ocStDevS, 1, MX, 0, "STDEV.S" ), + EXC_FUNCENTRY_V_RX( ocVarP_MS, 1, MX, 0, "VAR.P" ), + EXC_FUNCENTRY_V_RX( ocVarS, 1, MX, 0, "VAR.S" ), + EXC_FUNCENTRY_V_VR( ocBetaDist_MS, 4, 6, 0, "BETA.DIST" ), + EXC_FUNCENTRY_V_VR( ocBetaInv_MS, 3, 5, 0, "BETA.INV" ), + EXC_FUNCENTRY_V_VR( ocBinomDist_MS, 4, 4, 0, "BINOM.DIST" ), + EXC_FUNCENTRY_V_VR( ocBinomInv, 3, 3, 0, "BINOM.INV" ), + EXC_FUNCENTRY_V_VR( ocChiSqDist_MS, 3, 3, 0, "CHISQ.DIST" ), + EXC_FUNCENTRY_V_VR( ocChiSqInv_MS, 2, 2, 0, "CHISQ.INV" ), + EXC_FUNCENTRY_V_VR( ocChiDist_MS, 2, 2, 0, "CHISQ.DIST.RT" ), + EXC_FUNCENTRY_V_VR( ocChiInv_MS, 2, 2, 0, "CHISQ.INV.RT" ), + EXC_FUNCENTRY_V_VR( ocChiTest_MS, 2, 2, 0, "CHISQ.TEST" ), + EXC_FUNCENTRY_V_VR( ocConfidence_N, 3, 3, 0, "CONFIDENCE.NORM" ), + EXC_FUNCENTRY_V_VR( ocConfidence_T, 3, 3, 0, "CONFIDENCE.T" ), + EXC_FUNCENTRY_V_VR( ocFDist_LT, 4, 4, 0, "F.DIST" ), + EXC_FUNCENTRY_V_VR( ocFDist_RT, 3, 3, 0, "F.DIST.RT" ), + EXC_FUNCENTRY_V_VR( ocFInv_LT, 3, 3, 0, "F.INV" ), + EXC_FUNCENTRY_V_VR( ocFInv_RT, 3, 3, 0, "F.INV.RT" ), + EXC_FUNCENTRY_V_VR( ocFTest_MS, 2, 2, 0, "F.TEST" ), + EXC_FUNCENTRY_V_VR( ocExpDist_MS, 3, 3, 0, "EXPON.DIST" ), + EXC_FUNCENTRY_V_VR( ocHypGeomDist_MS, 5, 5, 0, "HYPGEOM.DIST" ), + EXC_FUNCENTRY_V_VR( ocPoissonDist_MS, 3, 3, 0, "POISSON.DIST" ), + EXC_FUNCENTRY_V_VR( ocWeibull_MS, 4, 4, 0, "WEIBULL.DIST" ), + EXC_FUNCENTRY_V_VR( ocGammaDist_MS, 4, 4, 0, "GAMMA.DIST" ), + EXC_FUNCENTRY_V_VR( ocGammaInv_MS, 3, 3, 0, "GAMMA.INV" ), + EXC_FUNCENTRY_V_VR( ocGammaLn_MS, 1, 1, 0, "GAMMALN.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocLogNormDist_MS, 4, 4, 0, "LOGNORM.DIST" ), + EXC_FUNCENTRY_V_VR( ocLogInv_MS, 3, 3, 0, "LOGNORM.INV" ), + EXC_FUNCENTRY_V_VR( ocNormDist_MS, 4, 4, 0, "NORM.DIST" ), + EXC_FUNCENTRY_V_VR( ocNormInv_MS, 3, 3, 0, "NORM.INV" ), + EXC_FUNCENTRY_V_VR( ocStdNormDist_MS, 2, 2, 0, "NORM.S.DIST" ), + EXC_FUNCENTRY_V_VR( ocSNormInv_MS, 1, 1, 0, "NORM.S.INV" ), + EXC_FUNCENTRY_V_VR( ocTDist_2T, 2, 2, 0, "T.DIST.2T" ), + EXC_FUNCENTRY_V_VR( ocTDist_MS, 3, 3, 0, "T.DIST" ), + EXC_FUNCENTRY_V_VR( ocTDist_RT, 2, 2, 0, "T.DIST.RT" ), + EXC_FUNCENTRY_V_VR( ocTInv_2T, 2, 2, 0, "T.INV.2T" ), + EXC_FUNCENTRY_V_VR( ocTInv_MS, 2, 2, 0, "T.INV" ), + EXC_FUNCENTRY_V_VR( ocTTest_MS, 4, 4, 0, "T.TEST" ), + EXC_FUNCENTRY_V_VR( ocPercentile_Inc, 2, 2, 0, "PERCENTILE.INC" ), + EXC_FUNCENTRY_V_VR( ocPercentrank_Inc, 2, 3, 0, "PERCENTRANK.INC" ), + EXC_FUNCENTRY_V_VR( ocQuartile_Inc, 2, 2, 0, "QUARTILE.INC" ), + EXC_FUNCENTRY_V_VR( ocRank_Eq, 2, 3, 0, "RANK.EQ" ), + EXC_FUNCENTRY_V_VR( ocPercentile_Exc, 2, 2, 0, "PERCENTILE.EXC" ), + EXC_FUNCENTRY_V_VR( ocPercentrank_Exc, 2, 3, 0, "PERCENTRANK.EXC" ), + EXC_FUNCENTRY_V_VR( ocQuartile_Exc, 2, 2, 0, "QUARTILE.EXC" ), + EXC_FUNCENTRY_V_VR( ocRank_Avg, 2, 3, 0, "RANK.AVG" ), + EXC_FUNCENTRY_V_RX( ocModalValue_MS, 1, MX, 0, "MODE.SNGL" ), + EXC_FUNCENTRY_V_RX( ocModalValue_Multi, 1, MX, 0, "MODE.MULT" ), + EXC_FUNCENTRY_V_VR( ocNegBinomDist_MS, 4, 4, 0, "NEGBINOM.DIST" ), + EXC_FUNCENTRY_V_VR( ocZTest_MS, 2, 3, 0, "Z.TEST" ), + EXC_FUNCENTRY_V_VR( ocCeil_Precise, 1, 2, 0, "CEILING.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocFloor_Precise, 1, 2, 0, "FLOOR.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocErf_MS, 1, 1, 0, "ERF.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocErfc_MS, 1, 1, 0, "ERFC.PRECISE" ), + EXC_FUNCENTRY_V_RX( ocAggregate, 3, MX, 0, "AGGREGATE" ), +}; + +/** Functions new in Excel 2013. + + See http://office.microsoft.com/en-us/excel-help/new-functions-in-excel-2013-HA103980604.aspx + Most functions apparently were added for ODF1.2 ODFF / OpenFormula + compatibility. + + Functions with EXC_FUNCENTRY_V_VR_IMPORT are rewritten in + sc/source/filter/excel/xeformula.cxx during export for BIFF, OOXML export + uses a different mapping but still uses this mapping here to determine the + feature set. + + FIXME: either have the exporter determine the feature set from the active + mapping, preferred, or enhance this mapping here such that for OOXML the + rewrite can be overridden. + + @See sc/source/filter/oox/formulabase.cxx saFuncTable2013 for V,VR,RO,... + */ +const XclFunctionInfo saFuncTable_2013[] = +{ + EXC_FUNCENTRY_V_VR_IMPORT( ocArcCot, 1, 1, 0, "ACOT" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocArcCotHyp, 1, 1, 0, "ACOTH" ), + EXC_FUNCENTRY_V_VR( ocArabic, 1, 1, 0, "ARABIC" ), + EXC_FUNCENTRY_V_VR( ocBase, 2, 3, 0, "BASE" ), + EXC_FUNCENTRY_V_VR( ocB, 3, 4, 0, "BINOM.DIST.RANGE" ), + EXC_FUNCENTRY_V_VR( ocBitAnd, 2, 2, 0, "BITAND" ), + EXC_FUNCENTRY_V_VR( ocBitLshift, 2, 2, 0, "BITLSHIFT" ), + EXC_FUNCENTRY_V_VR( ocBitOr, 2, 2, 0, "BITOR" ), + EXC_FUNCENTRY_V_VR( ocBitRshift, 2, 2, 0, "BITRSHIFT" ), + EXC_FUNCENTRY_V_VR( ocBitXor, 2, 2, 0, "BITXOR" ), + EXC_FUNCENTRY_V_VR( ocCeil_Math, 1, 3, 0, "CEILING.MATH" ), + EXC_FUNCENTRY_V_RO_EXPORT( ocCeil, 1, 3, 0, "CEILING.MATH" ), + EXC_FUNCENTRY_V_VR( ocCombinA, 2, 2, 0, "COMBINA" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCot, 1, 1, 0, "COT" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCotHyp, 1, 1, 0, "COTH" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCosecant, 1, 1, 0, "CSC" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCosecantHyp, 1, 1, 0, "CSCH" ), + EXC_FUNCENTRY_V_VR( ocGetDiffDate, 2, 2, 0, "DAYS" ), + EXC_FUNCENTRY_V_VR( ocDecimal, 2, 2, 0, "DECIMAL" ), + EXC_FUNCENTRY_V_VR( ocEncodeURL, 1, 1, 0, "ENCODEURL" ), + // NOTE: this FDIST is not our LEGACY.FDIST + EXC_FUNCENTRY_V_VR( ocNoName, 3, 4, 0, "FDIST" ), + // NOTE: this FINV is not our LEGACY.FINV + EXC_FUNCENTRY_V_VR( ocNoName, 3, 3, 0, "FINV" ), + EXC_FUNCENTRY_V_VR( ocFilterXML, 2, 2, 0, "FILTERXML" ), + EXC_FUNCENTRY_V_VR( ocFloor_Math, 1, 3, 0, "FLOOR.MATH" ), + EXC_FUNCENTRY_V_RO_EXPORT( ocFloor, 1, 3, 0, "FLOOR.MATH" ), + EXC_FUNCENTRY_V_RO( ocFormula, 1, 1, 0, "FORMULATEXT" ), + EXC_FUNCENTRY_V_VR( ocGamma, 1, 1, 0, "GAMMA" ), + EXC_FUNCENTRY_V_VR( ocGauss, 1, 1, 0, "GAUSS" ), + { ocIfNA, NOID, 2, 2, V, { VO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "IFNA" ) }, + { ocIfNA, 255, 3, 3, V, { RO_E, VO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "IFNA" ) }, + // IMCOSH, IMCOT, IMCSC, IMCSCH, IMSEC, IMSECH, IMSINH and IMTAN are + // implemented in the Analysis Add-In. + EXC_FUNCENTRY_V_RO( ocIsFormula, 1, 1, 0, "ISFORMULA" ), + EXC_FUNCENTRY_V_VR( ocWeek, 1, 2, 0, "WEEKNUM" ), + EXC_FUNCENTRY_V_VR( ocIsoWeeknum, 1, 1, 0, "ISOWEEKNUM" ), + EXC_FUNCENTRY_A_VR( ocMatrixUnit, 1, 1, 0, "MUNIT" ), + EXC_FUNCENTRY_V_VR( ocNumberValue, 1, 3, 0, "NUMBERVALUE" ), + EXC_FUNCENTRY_V_VR( ocPDuration, 3, 3, 0, "PDURATION" ), + EXC_FUNCENTRY_V_VR( ocPermutationA, 2, 2, 0, "PERMUTATIONA" ), + EXC_FUNCENTRY_V_VR( ocPhi, 1, 1, 0, "PHI" ), + EXC_FUNCENTRY_V_VR( ocRRI, 3, 3, 0, "RRI" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocSecant, 1, 1, 0, "SEC" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocSecantHyp, 1, 1, 0, "SECH" ), + EXC_FUNCENTRY_V_RO( ocSheet, 0, 1, 0, "SHEET" ), + EXC_FUNCENTRY_V_RO( ocSheets, 0, 1, 0, "SHEETS" ), + EXC_FUNCENTRY_V_RX( ocSkewp, 1, MX, 0, "SKEW.P" ), + EXC_FUNCENTRY_V_VR( ocUnichar, 1, 1, 0, "UNICHAR" ), + EXC_FUNCENTRY_V_VR( ocUnicode, 1, 1, 0, "UNICODE" ), + EXC_FUNCENTRY_V_VR( ocWebservice, 1, 1, 0, "WEBSERVICE" ), + EXC_FUNCENTRY_V_RX( ocXor, 1, MX, 0, "XOR" ), + EXC_FUNCENTRY_V_VR( ocErrorType_ODF, 1, 1, 0, "ERROR.TYPE" ) +}; + +/** Functions new in Excel 2016. + + See https://support.office.com/en-us/article/Forecasting-functions-897a2fe9-6595-4680-a0b0-93e0308d5f6e?ui=en-US&rs=en-US&ad=US#_forecast.ets + and https://support.office.com/en-us/article/What-s-New-and-Improved-in-Office-2016-for-Office-365-95c8d81d-08ba-42c1-914f-bca4603e1426?ui=en-US&rs=en-US&ad=US + + @See sc/source/filter/oox/formulabase.cxx saFuncTable2016 for V,VR,RO,... + */ +const XclFunctionInfo saFuncTable_2016[] = +{ + EXC_FUNCENTRY_V_VR( ocForecast_ETS_ADD, 3, 6, 0, "FORECAST.ETS" ), + EXC_FUNCENTRY_V_VR( ocForecast_ETS_PIA, 3, 7, 0, "FORECAST.ETS.CONFINT" ), + EXC_FUNCENTRY_V_VR( ocForecast_ETS_SEA, 2, 4, 0, "FORECAST.ETS.SEASONALITY" ), + EXC_FUNCENTRY_V_VR( ocForecast_ETS_STA, 3, 6, 0, "FORECAST.ETS.STAT" ), + EXC_FUNCENTRY_V_VR( ocForecast_LIN, 3, 3, 0, "FORECAST.LINEAR" ), + EXC_FUNCENTRY_V_VR( ocConcat_MS, 1, MX, 0, "CONCAT" ), + EXC_FUNCENTRY_V_VR( ocTextJoin_MS, 3, MX, 0, "TEXTJOIN" ), + EXC_FUNCENTRY_V_VR( ocIfs_MS, 2, MX, 0, "IFS" ), + EXC_FUNCENTRY_V_VR( ocSwitch_MS, 3, MX, 0, "SWITCH" ), + EXC_FUNCENTRY_V_VR( ocMinIfs_MS, 3, MX, 0, "MINIFS" ), + EXC_FUNCENTRY_V_VR( ocMaxIfs_MS, 3, MX, 0, "MAXIFS" ) +}; + +#define EXC_FUNCENTRY_ODF( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME_ODF( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME_ODF( asciiname ) } + +/** Functions defined by OpenFormula, but not supported by Calc (ocNoName) or by Excel (defined op-code). */ +const XclFunctionInfo saFuncTable_Odf[] = +{ + EXC_FUNCENTRY_ODF( ocChiSqDist, 2, 3, 0, "CHISQDIST" ), + EXC_FUNCENTRY_ODF( ocChiSqInv, 2, 2, 0, "CHISQINV" ) +}; + +#undef EXC_FUNCENTRY_ODF + +#define EXC_FUNCENTRY_OOO( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +// Import Broken Raw ... even without leading _xlfn. +#define EXC_FUNCENTRY_OOO_IBR( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), asciiname } + +/** Functions defined by Calc, but not in OpenFormula nor supported by Excel. */ +const XclFunctionInfo saFuncTable_OOoLO[] = +{ + EXC_FUNCENTRY_OOO( ocErrorType, 1, 1, 0, "ORG.OPENOFFICE.ERRORTYPE" ), + EXC_FUNCENTRY_OOO_IBR( ocErrorType, 1, 1, 0, "ERRORTYPE" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocMultiArea, 1, MX, 0, "ORG.OPENOFFICE.MULTIRANGE" ), + EXC_FUNCENTRY_OOO_IBR( ocMultiArea, 1, MX, 0, "MULTIRANGE" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocBackSolver, 3, 3, 0, "ORG.OPENOFFICE.GOALSEEK" ), + EXC_FUNCENTRY_OOO_IBR( ocBackSolver,3, 3, 0, "GOALSEEK" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocEasterSunday, 1, 1, 0, "ORG.OPENOFFICE.EASTERSUNDAY" ), + EXC_FUNCENTRY_OOO_IBR( ocEasterSunday,1,1, 0, "EASTERSUNDAY" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocCurrent, 0, 0, 0, "ORG.OPENOFFICE.CURRENT" ), + EXC_FUNCENTRY_OOO_IBR( ocCurrent, 0, 0, 0, "CURRENT" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocStyle, 1, 3, 0, "ORG.OPENOFFICE.STYLE" ), + EXC_FUNCENTRY_OOO_IBR( ocStyle, 1, 3, 0, "STYLE" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocConvertOOo, 3, 3, 0, "ORG.OPENOFFICE.CONVERT" ), + EXC_FUNCENTRY_OOO( ocColor, 3, 4, 0, "ORG.LIBREOFFICE.COLOR" ), + EXC_FUNCENTRY_OOO( ocRawSubtract, 2, MX, 0, "ORG.LIBREOFFICE.RAWSUBTRACT" ), + EXC_FUNCENTRY_OOO( ocWeeknumOOo, 2, 2, 0, "ORG.LIBREOFFICE.WEEKNUM_OOO" ), + EXC_FUNCENTRY_OOO( ocForecast_ETS_MUL, 3, 6, 0, "ORG.LIBREOFFICE.FORECAST.ETS.MULT" ), + EXC_FUNCENTRY_OOO( ocForecast_ETS_PIM, 3, 7, 0, "ORG.LIBREOFFICE.FORECAST.ETS.PI.MULT" ), + EXC_FUNCENTRY_OOO( ocForecast_ETS_STM, 3, 6, 0, "ORG.LIBREOFFICE.FORECAST.ETS.STAT.MULT" ), + EXC_FUNCENTRY_OOO( ocRoundSig, 2, 2, 0, "ORG.LIBREOFFICE.ROUNDSIG" ), + EXC_FUNCENTRY_OOO( ocRegex, 2, 4, 0, "ORG.LIBREOFFICE.REGEX" ), + EXC_FUNCENTRY_OOO( ocFourier, 2, 5, 0, "ORG.LIBREOFFICE.FOURIER" ), + EXC_FUNCENTRY_OOO( ocRandomNV, 0, 0, 0, "ORG.LIBREOFFICE.RAND.NV" ), + EXC_FUNCENTRY_OOO( ocRandbetweenNV, 2, 2, 0, "ORG.LIBREOFFICE.RANDBETWEEN.NV" ) +}; + +#undef EXC_FUNCENTRY_OOO_IBR +#undef EXC_FUNCENTRY_OOO + +XclFunctionProvider::XclFunctionProvider( const XclRoot& rRoot ) +{ + void (XclFunctionProvider::*pFillFunc)( const XclFunctionInfo*, const XclFunctionInfo* ) = + rRoot.IsImport() ? &XclFunctionProvider::FillXclFuncMap : &XclFunctionProvider::FillScFuncMap; + + /* Only read/write functions supported in the current BIFF version. + Function tables from later BIFF versions may overwrite single functions + from earlier tables. */ + XclBiff eBiff = rRoot.GetBiff(); + if( eBiff >= EXC_BIFF2 ) + (this->*pFillFunc)(saFuncTable_2, saFuncTable_2 + SAL_N_ELEMENTS(saFuncTable_2)); + if( eBiff >= EXC_BIFF3 ) + (this->*pFillFunc)(saFuncTable_3, saFuncTable_3 + SAL_N_ELEMENTS(saFuncTable_3)); + if( eBiff >= EXC_BIFF4 ) + (this->*pFillFunc)(saFuncTable_4, saFuncTable_4 + SAL_N_ELEMENTS(saFuncTable_4)); + if( eBiff >= EXC_BIFF5 ) + (this->*pFillFunc)(saFuncTable_5, saFuncTable_5 + SAL_N_ELEMENTS(saFuncTable_5)); + if( eBiff >= EXC_BIFF8 ) + (this->*pFillFunc)(saFuncTable_8, saFuncTable_8 + SAL_N_ELEMENTS(saFuncTable_8)); + (this->*pFillFunc)(saFuncTable_Oox, saFuncTable_Oox + SAL_N_ELEMENTS(saFuncTable_Oox)); + (this->*pFillFunc)(saFuncTable_2010, saFuncTable_2010 + SAL_N_ELEMENTS(saFuncTable_2010)); + (this->*pFillFunc)(saFuncTable_2013, saFuncTable_2013 + SAL_N_ELEMENTS(saFuncTable_2013)); + (this->*pFillFunc)(saFuncTable_2016, saFuncTable_2016 + SAL_N_ELEMENTS(saFuncTable_2016)); + (this->*pFillFunc)(saFuncTable_Odf, saFuncTable_Odf + SAL_N_ELEMENTS(saFuncTable_Odf)); + (this->*pFillFunc)(saFuncTable_OOoLO, saFuncTable_OOoLO + SAL_N_ELEMENTS(saFuncTable_OOoLO)); +} + +const XclFunctionInfo* XclFunctionProvider::GetFuncInfoFromXclFunc( sal_uInt16 nXclFunc ) const +{ + // only in import filter allowed + OSL_ENSURE( !maXclFuncMap.empty(), "XclFunctionProvider::GetFuncInfoFromXclFunc - wrong filter" ); + XclFuncMap::const_iterator aIt = maXclFuncMap.find( nXclFunc ); + return (aIt == maXclFuncMap.end()) ? nullptr : aIt->second; +} + +const XclFunctionInfo* XclFunctionProvider::GetFuncInfoFromXclMacroName( const OUString& rXclMacroName ) const +{ + // only in import filter allowed, but do not test maXclMacroNameMap, it may be empty for old BIFF versions + OSL_ENSURE( !maXclFuncMap.empty(), "XclFunctionProvider::GetFuncInfoFromXclMacroName - wrong filter" ); + XclMacroNameMap::const_iterator aIt = maXclMacroNameMap.find( rXclMacroName ); + return (aIt == maXclMacroNameMap.end()) ? nullptr : aIt->second; +} + +const XclFunctionInfo* XclFunctionProvider::GetFuncInfoFromOpCode( OpCode eOpCode ) const +{ + // only in export filter allowed + OSL_ENSURE( !maScFuncMap.empty(), "XclFunctionProvider::GetFuncInfoFromOpCode - wrong filter" ); + ScFuncMap::const_iterator aIt = maScFuncMap.find( eOpCode ); + return (aIt == maScFuncMap.end()) ? nullptr : aIt->second; +} + +void XclFunctionProvider::FillXclFuncMap( const XclFunctionInfo* pBeg, const XclFunctionInfo* pEnd ) +{ + for( const XclFunctionInfo* pIt = pBeg; pIt != pEnd; ++pIt ) + { + if( !::get_flag( pIt->mnFlags, EXC_FUNCFLAG_EXPORTONLY ) ) + { + if( pIt->mnXclFunc != NOID ) + maXclFuncMap[ pIt->mnXclFunc ] = pIt; + if( pIt->IsMacroFunc() ) + maXclMacroNameMap[ pIt->GetMacroFuncName() ] = pIt; + } + } +} + +void XclFunctionProvider::FillScFuncMap( const XclFunctionInfo* pBeg, const XclFunctionInfo* pEnd ) +{ + for( const XclFunctionInfo* pIt = pBeg; pIt != pEnd; ++pIt ) + if( !::get_flag( pIt->mnFlags, EXC_FUNCFLAG_IMPORTONLY ) ) + maScFuncMap[ pIt->meOpCode ] = pIt; +} + +// Token array ================================================================ + +XclTokenArray::XclTokenArray( bool bVolatile ) : + mbVolatile( bVolatile ) +{ +} + +XclTokenArray::XclTokenArray( ScfUInt8Vec& rTokVec, ScfUInt8Vec& rExtDataVec, bool bVolatile ) : + mbVolatile( bVolatile ) +{ + maTokVec.swap( rTokVec ); + maExtDataVec.swap( rExtDataVec ); +} + +sal_uInt16 XclTokenArray::GetSize() const +{ + OSL_ENSURE( maTokVec.size() <= 0xFFFF, "XclTokenArray::GetSize - array too long" ); + return limit_cast< sal_uInt16 >( maTokVec.size() ); +} + +sal_uInt16 XclTokenArray::ReadSize(XclImpStream& rStrm) +{ + return rStrm.ReaduInt16(); +} + +void XclTokenArray::ReadArray(sal_uInt16 nSize, XclImpStream& rStrm) +{ + maTokVec.resize(0); + + const std::size_t nMaxBuffer = 4096; + std::size_t nBytesLeft = nSize; + std::size_t nTotalRead = 0; + + while (true) + { + if (!nBytesLeft) + break; + std::size_t nReadRequest = o3tl::sanitizing_min(nBytesLeft, nMaxBuffer); + maTokVec.resize(maTokVec.size() + nReadRequest); + auto nRead = rStrm.Read(maTokVec.data() + nTotalRead, nReadRequest); + nTotalRead += nRead; + if (nRead != nReadRequest) + { + maTokVec.resize(nTotalRead); + break; + } + nBytesLeft -= nRead; + } +} + +void XclTokenArray::Read( XclImpStream& rStrm ) +{ + ReadArray(ReadSize(rStrm), rStrm); +} + +void XclTokenArray::WriteSize( XclExpStream& rStrm ) const +{ + rStrm << GetSize(); +} + +void XclTokenArray::WriteArray( XclExpStream& rStrm ) const +{ + if( !maTokVec.empty() ) + rStrm.Write(maTokVec.data(), GetSize()); + if( !maExtDataVec.empty() ) + rStrm.Write(maExtDataVec.data(), maExtDataVec.size()); +} + +void XclTokenArray::Write( XclExpStream& rStrm ) const +{ + WriteSize( rStrm ); + WriteArray( rStrm ); +} + +bool XclTokenArray::operator==( const XclTokenArray& rTokArr ) const +{ + return (mbVolatile == rTokArr.mbVolatile) && (maTokVec == rTokArr.maTokVec) && (maExtDataVec == rTokArr.maExtDataVec); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclTokenArray& rTokArr ) +{ + rTokArr.Read( rStrm ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclTokenArray& rTokArr ) +{ + rTokArr.Write( rStrm ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclTokenArrayRef& rxTokArr ) +{ + if( rxTokArr ) + rxTokArr->Write( rStrm ); + else + rStrm << sal_uInt16( 0 ); + return rStrm; +} + +XclTokenArrayIterator::XclTokenArrayIterator() : + mppScTokenBeg( nullptr ), + mppScTokenEnd( nullptr ), + mppScToken( nullptr ), + mbSkipSpaces( false ) +{ +} + +XclTokenArrayIterator::XclTokenArrayIterator( const ScTokenArray& rScTokArr, bool bSkipSpaces ) +{ + Init( rScTokArr, bSkipSpaces ); +} + +XclTokenArrayIterator::XclTokenArrayIterator( const XclTokenArrayIterator& rTokArrIt, bool bSkipSpaces ) : + mppScTokenBeg( rTokArrIt.mppScTokenBeg ), + mppScTokenEnd( rTokArrIt.mppScTokenEnd ), + mppScToken( rTokArrIt.mppScToken ), + mbSkipSpaces( bSkipSpaces ) +{ + SkipSpaces(); +} + +void XclTokenArrayIterator::Init( const ScTokenArray& rScTokArr, bool bSkipSpaces ) +{ + sal_uInt16 nTokArrLen = rScTokArr.GetLen(); + mppScTokenBeg = static_cast< const FormulaToken* const* >( nTokArrLen ? rScTokArr.GetArray() : nullptr ); + mppScTokenEnd = mppScTokenBeg ? (mppScTokenBeg + nTokArrLen) : nullptr; + mppScToken = (mppScTokenBeg != mppScTokenEnd) ? mppScTokenBeg : nullptr; + mbSkipSpaces = bSkipSpaces; + SkipSpaces(); +} + +XclTokenArrayIterator& XclTokenArrayIterator::operator++() +{ + NextRawToken(); + SkipSpaces(); + return *this; +} + +void XclTokenArrayIterator::NextRawToken() +{ + if( mppScToken ) + if( (++mppScToken == mppScTokenEnd) || !*mppScToken ) + mppScToken = nullptr; +} + +void XclTokenArrayIterator::SkipSpaces() +{ + if( mbSkipSpaces ) + { + OpCode eOp; + while( Is() && (((eOp = (*this)->GetOpCode()) == ocSpaces) || eOp == ocWhitespace) ) + NextRawToken(); + } +} + +// strings and string lists --------------------------------------------------- + +bool XclTokenArrayHelper::GetTokenString( OUString& rString, const FormulaToken& rScToken ) +{ + bool bIsStr = (rScToken.GetType() == svString) && (rScToken.GetOpCode() == ocPush); + if( bIsStr ) rString = rScToken.GetString().getString(); + return bIsStr; +} + +bool XclTokenArrayHelper::GetString( OUString& rString, const ScTokenArray& rScTokArr ) +{ + XclTokenArrayIterator aIt( rScTokArr, true ); + // something is following the string token -> error + return aIt.Is() && GetTokenString( rString, *aIt ) && !++aIt; +} + +bool XclTokenArrayHelper::GetStringList( OUString& rStringList, const ScTokenArray& rScTokArr, sal_Unicode cSep ) +{ + bool bRet = true; + XclTokenArrayIterator aIt( rScTokArr, true ); + enum { STATE_START, STATE_STR, STATE_SEP, STATE_END } eState = STATE_START; + while( eState != STATE_END ) switch( eState ) + { + case STATE_START: + eState = aIt.Is() ? STATE_STR : STATE_END; + break; + case STATE_STR: + { + OUString aString; + bRet = GetTokenString( aString, *aIt ); + if( bRet ) rStringList += aString ; + eState = (bRet && (++aIt).Is()) ? STATE_SEP : STATE_END; + break; + } + case STATE_SEP: + bRet = aIt->GetOpCode() == ocSep; + if( bRet ) rStringList += OUStringChar(cSep); + eState = (bRet && (++aIt).Is()) ? STATE_STR : STATE_END; + break; + default:; + } + return bRet; +} + +void XclTokenArrayHelper::ConvertStringToList( + ScTokenArray& rScTokArr, svl::SharedStringPool& rSPool, sal_Unicode cStringSep ) +{ + OUString aString; + if( !GetString( aString, rScTokArr ) ) + return; + + rScTokArr.Clear(); + if (aString.isEmpty()) + return; + sal_Int32 nStringIx = 0; + for (;;) + { + OUString aToken( aString.getToken( 0, cStringSep, nStringIx ) ); + rScTokArr.AddString(rSPool.intern(comphelper::string::stripStart(aToken, ' '))); + if (nStringIx<0) + break; + rScTokArr.AddOpCode( ocSep ); + } +} + +// multiple operations -------------------------------------------------------- + +namespace { + +bool lclGetAddress( const ScDocument& rDoc, ScAddress& rAddress, const FormulaToken& rToken, const ScAddress& rPos ) +{ + OpCode eOpCode = rToken.GetOpCode(); + bool bIsSingleRef = (eOpCode == ocPush) && (rToken.GetType() == svSingleRef); + if( bIsSingleRef ) + { + const ScSingleRefData& rRef = *rToken.GetSingleRef(); + rAddress = rRef.toAbs(rDoc, rPos); + bIsSingleRef = !rRef.IsDeleted(); + } + return bIsSingleRef; +} + +} // namespace + +bool XclTokenArrayHelper::GetMultipleOpRefs( + const ScDocument& rDoc, + XclMultipleOpRefs& rRefs, const ScTokenArray& rScTokArr, const ScAddress& rScPos ) +{ + rRefs.mbDblRefMode = false; + enum + { + stBegin, stTableOp, stOpen, stFormula, stFormulaSep, + stColFirst, stColFirstSep, stColRel, stColRelSep, + stRowFirst, stRowFirstSep, stRowRel, stClose, stError + } eState = stBegin; // last read token + for( XclTokenArrayIterator aIt( rScTokArr, true ); aIt.Is() && (eState != stError); ++aIt ) + { + OpCode eOpCode = aIt->GetOpCode(); + bool bIsSep = eOpCode == ocSep; + switch( eState ) + { + case stBegin: + eState = (eOpCode == ocTableOp) ? stTableOp : stError; + break; + case stTableOp: + eState = (eOpCode == ocOpen) ? stOpen : stError; + break; + case stOpen: + eState = lclGetAddress(rDoc, rRefs.maFmlaScPos, *aIt, rScPos) ? stFormula : stError; + break; + case stFormula: + eState = bIsSep ? stFormulaSep : stError; + break; + case stFormulaSep: + eState = lclGetAddress(rDoc, rRefs.maColFirstScPos, *aIt, rScPos) ? stColFirst : stError; + break; + case stColFirst: + eState = bIsSep ? stColFirstSep : stError; + break; + case stColFirstSep: + eState = lclGetAddress(rDoc, rRefs.maColRelScPos, *aIt, rScPos) ? stColRel : stError; + break; + case stColRel: + eState = bIsSep ? stColRelSep : ((eOpCode == ocClose) ? stClose : stError); + break; + case stColRelSep: + eState = lclGetAddress(rDoc, rRefs.maRowFirstScPos, *aIt, rScPos) ? stRowFirst : stError; + rRefs.mbDblRefMode = true; + break; + case stRowFirst: + eState = bIsSep ? stRowFirstSep : stError; + break; + case stRowFirstSep: + eState = lclGetAddress(rDoc, rRefs.maRowRelScPos, *aIt, rScPos) ? stRowRel : stError; + break; + case stRowRel: + eState = (eOpCode == ocClose) ? stClose : stError; + break; + default: + eState = stError; + } + } + return eState == stClose; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlpage.cxx b/sc/source/filter/excel/xlpage.cxx new file mode 100644 index 000000000..937aa9427 --- /dev/null +++ b/sc/source/filter/excel/xlpage.cxx @@ -0,0 +1,262 @@ +/* -*- 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 <xlpage.hxx> +#include <xltools.hxx> +#include <editeng/paperinf.hxx> +#include <o3tl/unit_conversion.hxx> +#include <sal/macros.h> +#include <editeng/brushitem.hxx> + +namespace{ + +struct XclPaperSize +{ + Paper mePaper; /// SVX paper size identifier. + tools::Long mnWidth; /// Paper width in twips. + tools::Long mnHeight; /// Paper height in twips. +}; + +constexpr tools::Long in2twips(double n_inch) +{ + return o3tl::convert(n_inch, o3tl::Length::in, o3tl::Length::twip) + 0.5; +} +constexpr tools::Long mm2twips(double n_mm) +{ + return o3tl::convert(n_mm, o3tl::Length::mm, o3tl::Length::twip) + 0.5; +} +constexpr tools::Long twips2mm(tools::Long n_twips) +{ + return o3tl::convert(n_twips, o3tl::Length::twip, o3tl::Length::mm); +} + +// see ApiPaperSize spPaperSizeTable in filter and aDinTab in i18nutil +constexpr XclPaperSize pPaperSizeTable[] = +{ +/* 0*/ { PAPER_USER, 0, 0 }, // undefined + { PAPER_LETTER, in2twips( 8.5 ), in2twips( 11 ) }, // Letter + { PAPER_USER, in2twips( 8.5 ), in2twips( 11 ) }, // Letter Small + { PAPER_TABLOID, in2twips( 11 ), in2twips( 17 ) }, // Tabloid + { PAPER_LEDGER, in2twips( 17 ), in2twips( 11 ) }, // Ledger +/* 5*/ { PAPER_LEGAL, in2twips( 8.5 ), in2twips( 14 ) }, // Legal + { PAPER_STATEMENT, in2twips( 5.5 ), in2twips( 8.5 ) }, // Statement + { PAPER_EXECUTIVE, in2twips( 7.25 ), in2twips( 10.5 ) }, // Executive + { PAPER_A3, mm2twips( 297 ), mm2twips( 420 ) }, // A3 + { PAPER_A4, mm2twips( 210 ), mm2twips( 297 ) }, // A4 +/* 10*/ { PAPER_USER, mm2twips( 210 ), mm2twips( 297 ) }, // A4 Small + { PAPER_A5, mm2twips( 148 ), mm2twips( 210 ) }, // A5 + /* for JIS vs ISO B confusion see: + https://docs.microsoft.com/en-us/windows/win32/intl/paper-sizes + http://wiki.openoffice.org/wiki/DefaultPaperSize comments + http://partners.adobe.com/public/developer/en/ps/5003.PPD_Spec_v4.3.pdf */ + { PAPER_B4_JIS, mm2twips( 257 ), mm2twips( 364 ) }, // B4 (JIS) + { PAPER_B5_JIS, mm2twips( 182 ), mm2twips( 257 ) }, // B5 (JIS) + { PAPER_USER, in2twips( 8.5 ), in2twips( 13 ) }, // Folio +/* 15*/ { PAPER_QUARTO, mm2twips( 215 ), mm2twips( 275 ) }, // Quarto + { PAPER_10x14, in2twips( 10 ), in2twips( 14 ) }, // 10x14 + { PAPER_USER, in2twips( 11 ), in2twips( 17 ) }, // 11x17 + { PAPER_USER, in2twips( 8.5 ), in2twips( 11 ) }, // Note + { PAPER_ENV_9, in2twips( 3.875 ), in2twips( 8.875 ) }, // Envelope #9 +/* 20*/ { PAPER_ENV_10, in2twips( 4.125 ), in2twips( 9.5 ) }, // Envelope #10 + { PAPER_ENV_11, in2twips( 4.5 ), in2twips( 10.375 ) }, // Envelope #11 + { PAPER_ENV_12, in2twips( 4.75 ), in2twips( 11 ) }, // Envelope #12 + { PAPER_ENV_14, in2twips( 5 ), in2twips( 11.5 ) }, // Envelope #14 + { PAPER_C, in2twips( 17 ), in2twips( 22 ) }, // ANSI-C +/* 25*/ { PAPER_D, in2twips( 22 ), in2twips( 34 ) }, // ANSI-D + { PAPER_E, in2twips( 34 ), in2twips( 44 ) }, // ANSI-E + { PAPER_ENV_DL, mm2twips( 110 ), mm2twips( 220 ) }, // Envelope DL + { PAPER_ENV_C5, mm2twips( 162 ), mm2twips( 229 ) }, // Envelope C5 + { PAPER_ENV_C3, mm2twips( 324 ), mm2twips( 458 ) }, // Envelope C3 +/* 30*/ { PAPER_ENV_C4, mm2twips( 229 ), mm2twips( 324 ) }, // Envelope C4 + { PAPER_ENV_C6, mm2twips( 114 ), mm2twips( 162 ) }, // Envelope C6 + { PAPER_ENV_C65, mm2twips( 114 ), mm2twips( 229 ) }, // Envelope C65 + { PAPER_B4_ISO, mm2twips( 250 ), mm2twips( 353 ) }, // B4 (ISO) + { PAPER_B5_ISO, mm2twips( 176 ), mm2twips( 250 ) }, // B5 (ISO) +/* 35*/ { PAPER_B6_ISO, mm2twips( 125 ), mm2twips( 176 ) }, // B6 (ISO) + { PAPER_ENV_ITALY, mm2twips( 110 ), mm2twips( 230 ) }, // Envelope Italy + { PAPER_ENV_MONARCH, in2twips( 3.875 ), in2twips( 7.5 ) }, // Envelope Monarch + { PAPER_ENV_PERSONAL, in2twips( 3.625 ), in2twips( 6.5 ) }, // 6 3/4 Envelope + { PAPER_FANFOLD_US, in2twips( 14.875 ), in2twips( 11 ) }, // US Std Fanfold +/* 40*/ { PAPER_FANFOLD_DE, in2twips( 8.5 ), in2twips( 12 ) }, // German Std Fanfold + { PAPER_FANFOLD_LEGAL_DE, in2twips( 8.5 ), in2twips( 13 ) }, // German Legal Fanfold + { PAPER_B4_ISO, mm2twips( 250 ), mm2twips( 353 ) }, // B4 (ISO) + { PAPER_POSTCARD_JP,mm2twips( 100 ), mm2twips( 148 ) }, // Japanese Postcard + { PAPER_9x11, in2twips( 9 ), in2twips( 11 ) }, // 9x11 +/* 45*/ { PAPER_10x11, in2twips( 10 ), in2twips( 11 ) }, // 10x11 + { PAPER_15x11, in2twips( 15 ), in2twips( 11 ) }, // 15x11 + { PAPER_ENV_INVITE, mm2twips( 220 ), mm2twips( 220 ) }, // Envelope Invite + { PAPER_USER, 0, 0 }, // undefined + { PAPER_USER, 0, 0 }, // undefined + /* See: https://docs.microsoft.com/en-us/windows/win32/intl/paper-sizes */ +/* 50*/ { PAPER_USER, in2twips( 9.5 ), in2twips( 12 ) }, // Letter Extra + { PAPER_USER, in2twips( 9.5 ), in2twips( 15 ) }, // Legal Extra + { PAPER_USER, in2twips( 11.69 ), in2twips( 18 ) }, // Tabloid Extra + { PAPER_USER, mm2twips( 235 ), mm2twips( 322 ) }, // A4 Extra + { PAPER_USER, in2twips( 8.5 ), in2twips( 11 ) }, // Letter Transverse +/* 55*/ { PAPER_USER, mm2twips( 210 ), mm2twips( 297 ) }, // A4 Transverse + { PAPER_USER, in2twips( 9.5 ), in2twips( 12 ) }, // Letter Extra Transverse + { PAPER_A_PLUS, mm2twips( 227 ), mm2twips( 356 ) }, // Super A/A4 + { PAPER_B_PLUS, mm2twips( 305 ), mm2twips( 487 ) }, // Super B/A3 + { PAPER_LETTER_PLUS,in2twips( 8.5 ), in2twips( 12.69 ) }, // Letter Plus +/* 60*/ { PAPER_A4_PLUS, mm2twips( 210 ), mm2twips( 330 ) }, // A4 Plus + { PAPER_USER, mm2twips( 148 ), mm2twips( 210 ) }, // A5 Transverse + { PAPER_USER, mm2twips( 182 ), mm2twips( 257 ) }, // B5 (JIS) Transverse + { PAPER_USER, mm2twips( 322 ), mm2twips( 445 ) }, // A3 Extra + { PAPER_USER, mm2twips( 174 ), mm2twips( 235 ) }, // A5 Extra +/* 65*/ { PAPER_USER, mm2twips( 201 ), mm2twips( 276 ) }, // B5 (ISO) Extra + { PAPER_A2, mm2twips( 420 ), mm2twips( 594 ) }, // A2 + { PAPER_USER, mm2twips( 297 ), mm2twips( 420 ) }, // A3 Transverse + { PAPER_USER, mm2twips( 322 ), mm2twips( 445 ) }, // A3 Extra Transverse + { PAPER_DOUBLEPOSTCARD_JP, mm2twips( 200 ), mm2twips( 148 ) }, // Double Japanese Postcard +/* 70*/ { PAPER_A6, mm2twips( 105 ), mm2twips( 148 ) }, // A6 + { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #2 + { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #3 + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #3 + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #4 +/* 75*/ { PAPER_USER, in2twips( 11 ), in2twips( 8.5 ) }, // Letter Rotated + { PAPER_USER, mm2twips( 420 ), mm2twips( 297 ) }, // A3 Rotated + { PAPER_USER, mm2twips( 297 ), mm2twips( 210 ) }, // A4 Rotated + { PAPER_USER, mm2twips( 210 ), mm2twips( 148 ) }, // A5 Rotated + { PAPER_USER, mm2twips( 364 ), mm2twips( 257 ) }, // B4 (JIS) Rotated +/* 80*/ { PAPER_USER, mm2twips( 257 ), mm2twips( 182 ) }, // B5 (JIS) Rotated + { PAPER_USER, mm2twips( 148 ), mm2twips( 100 ) }, // Japanese Postcard Rotated + { PAPER_USER, mm2twips( 148 ), mm2twips( 200 ) }, // Double Japanese Postcard Rotated + { PAPER_USER, mm2twips( 148 ), mm2twips( 105 ) }, // A6 Rotated + { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #2 Rotated +/* 85*/ { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #3 Rotated + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #3 Rotated + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #4 Rotated + { PAPER_B6_JIS, mm2twips( 128 ), mm2twips( 182 ) }, // B6 (JIS) + { PAPER_USER, mm2twips( 182 ), mm2twips( 128 ) }, // B6 (JIS) Rotated +/* 90*/ { PAPER_12x11, in2twips( 12 ), in2twips( 11 ) } // 12x11 +}; + +} //namespace + +// Page settings ============================================================== + +XclPageData::XclPageData() +{ + SetDefaults(); +} + +XclPageData::~XclPageData() +{ + // SvxBrushItem incomplete in header +} + +void XclPageData::SetDefaults() +{ + maHorPageBreaks.clear(); + maVerPageBreaks.clear(); + mxBrushItem.reset(); + maHeader.clear(); + maFooter.clear(); + maHeaderEven.clear(); + maFooterEven.clear(); + mfLeftMargin = mfRightMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_LR ); + mfTopMargin = mfBottomMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_TB ); + mfHeaderMargin = mfFooterMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_HF ); + mfHdrLeftMargin = mfHdrRightMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_HLR ); + mfFtrLeftMargin = mfFtrRightMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_FLR ); + mnStrictPaperSize = mnPaperSize = EXC_PAPERSIZE_DEFAULT; + mnPaperWidth = 0; + mnPaperHeight = 0; + mnCopies = 1; + mnStartPage = 0; + mnScaling = 100; + mnFitToWidth = mnFitToHeight = 1; + mnHorPrintRes = mnVerPrintRes = 300; + mbUseEvenHF = mbUseFirstHF = false; + mbValid = false; + mbPortrait = true; + mbPrintInRows = mbBlackWhite = mbDraftQuality = mbPrintNotes = mbManualStart = mbFitToPages = false; + mbHorCenter = mbVerCenter = mbPrintHeadings = mbPrintGrid = false; +} + +Size XclPageData::GetScPaperSize() const +{ + const XclPaperSize* pEntry = pPaperSizeTable; + if( mnPaperSize < SAL_N_ELEMENTS( pPaperSizeTable ) ) + pEntry += mnPaperSize; + + Size aSize; + if( pEntry->mePaper == PAPER_USER ) + aSize = Size( pEntry->mnWidth, pEntry->mnHeight ); + else + aSize = SvxPaperInfo::GetPaperSize( pEntry->mePaper ); + + // invalid size -> back to default + if( !aSize.Width() || !aSize.Height() ) + aSize = SvxPaperInfo::GetDefaultPaperSize(); + + if( !mbPortrait ) + { + // swap width and height + tools::Long n = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(n); + } + + return aSize; +} + +void XclPageData::SetScPaperSize( const Size& rSize, bool bPortrait, bool bStrictSize ) +{ + mbPortrait = bPortrait; + mnPaperSize = 0; + tools::Long nWidth = bPortrait ? rSize.Width() : rSize.Height(); + tools::Long nHeight = bPortrait ? rSize.Height() : rSize.Width(); + tools::Long nMaxWDiff = 80; + tools::Long nMaxHDiff = 50; + + mnPaperWidth = twips2mm( nWidth ); + mnPaperHeight = twips2mm( nHeight ); + if( bStrictSize ) + { + nMaxWDiff = 5; + nMaxHDiff = 5; + mnStrictPaperSize = EXC_PAPERSIZE_USER; + } + else + { + mnPaperSize = EXC_PAPERSIZE_DEFAULT; + } + + for( const auto &rEntry : pPaperSizeTable) + { + tools::Long nWDiff = std::abs( rEntry.mnWidth - nWidth ); + tools::Long nHDiff = std::abs( rEntry.mnHeight - nHeight ); + if( ((nWDiff <= nMaxWDiff) && (nHDiff < nMaxHDiff)) || + ((nWDiff < nMaxWDiff) && (nHDiff <= nMaxHDiff)) ) + { + sal_uInt16 nIndex = static_cast< sal_uInt16 >( &rEntry - pPaperSizeTable ); + mnPaperSize = nIndex; + if( bStrictSize ) + mnStrictPaperSize = nIndex; + + nMaxWDiff = nWDiff; + nMaxHDiff = nHDiff; + } + } + if( !bStrictSize ) + SetScPaperSize( rSize, bPortrait, true ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlpivot.cxx b/sc/source/filter/excel/xlpivot.cxx new file mode 100644 index 000000000..d18ab7416 --- /dev/null +++ b/sc/source/filter/excel/xlpivot.cxx @@ -0,0 +1,1043 @@ +/* -*- 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 <dpsave.hxx> +#include <xestream.hxx> +#include <xistream.hxx> +#include <xestring.hxx> +#include <xlpivot.hxx> +#include <generalfunction.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp> + +using ::com::sun::star::sheet::DataPilotFieldOrientation; + +namespace ScDPSortMode = ::com::sun::star::sheet::DataPilotFieldSortMode; +namespace ScDPShowItemsMode = ::com::sun::star::sheet::DataPilotFieldShowItemsMode; +namespace ScDPLayoutMode = ::com::sun::star::sheet::DataPilotFieldLayoutMode; +namespace ScDPRefItemType = ::com::sun::star::sheet::DataPilotFieldReferenceItemType; +namespace ScDPGroupBy = ::com::sun::star::sheet::DataPilotFieldGroupBy; + +// Pivot cache + +XclPCItem::XclPCItem() : + meType( EXC_PCITEM_INVALID ), + maDateTime( DateTime::EMPTY ) +{ +} + +XclPCItem::~XclPCItem() +{ +} + +void XclPCItem::SetEmpty() +{ + meType = EXC_PCITEM_EMPTY; + maText.clear(); +} + +void XclPCItem::SetText( const OUString& rText ) +{ + meType = EXC_PCITEM_TEXT; + maText = rText; +} + +void XclPCItem::SetDouble( double fValue, const OUString& rText ) +{ + meType = EXC_PCITEM_DOUBLE; + maText = rText; + mfValue = fValue; +} + +void XclPCItem::SetDateTime( const DateTime& rDateTime, const OUString& rText ) +{ + meType = EXC_PCITEM_DATETIME; + maText = rText; + maDateTime = rDateTime; +} + +void XclPCItem::SetInteger( sal_Int16 nValue ) +{ + meType = EXC_PCITEM_INTEGER; + maText = OUString::number(nValue); + mnValue = nValue; +} + +void XclPCItem::SetError( sal_uInt16 nError ) +{ + meType = EXC_PCITEM_ERROR; + maText.clear(); + mnError = nError; + switch( nError ) + { + case 0x00: maText = "#nullptr!"; break; + case 0x07: maText = "#DIV/0!"; break; + case 0x0F: maText = "#VALUE!"; break; + case 0x17: maText = "#REF!"; break; + case 0x1D: maText = "#NAME?"; break; + case 0x24: maText = "#NUM!"; break; + case 0x2A: maText = "#N/A"; break; + default: break; + } +} + +void XclPCItem::SetBool( bool bValue, const OUString& rText ) +{ + meType = EXC_PCITEM_BOOL; + maText = rText; + mbValue = bValue; +} + +bool XclPCItem::IsEqual( const XclPCItem& rItem ) const +{ + if( meType == rItem.meType ) switch( meType ) + { + case EXC_PCITEM_INVALID: return true; + case EXC_PCITEM_EMPTY: return true; + case EXC_PCITEM_TEXT: return maText == rItem.maText; + case EXC_PCITEM_DOUBLE: return mfValue == rItem.mfValue; + case EXC_PCITEM_DATETIME: return maDateTime == rItem.maDateTime; + case EXC_PCITEM_INTEGER: return mnValue == rItem.mnValue; + case EXC_PCITEM_BOOL: return mbValue == rItem.mbValue; + case EXC_PCITEM_ERROR: return mnError == rItem.mnError; + default: OSL_FAIL( "XclPCItem::IsEqual - unknown pivot cache item type" ); + } + return false; +} + +bool XclPCItem::IsEmpty() const +{ + return meType == EXC_PCITEM_EMPTY; +} + +const OUString* XclPCItem::GetText() const +{ + return (meType == EXC_PCITEM_TEXT || meType == EXC_PCITEM_ERROR) ? &maText : nullptr; +} + +const double* XclPCItem::GetDouble() const +{ + return (meType == EXC_PCITEM_DOUBLE) ? &mfValue : nullptr; +} + +const DateTime* XclPCItem::GetDateTime() const +{ + return (meType == EXC_PCITEM_DATETIME) ? &maDateTime : nullptr; +} + +const sal_Int16* XclPCItem::GetInteger() const +{ + return (meType == EXC_PCITEM_INTEGER) ? &mnValue : nullptr; +} + +const sal_uInt16* XclPCItem::GetError() const +{ + return (meType == EXC_PCITEM_ERROR) ? &mnError : nullptr; +} + +const bool* XclPCItem::GetBool() const +{ + return (meType == EXC_PCITEM_BOOL) ? &mbValue : nullptr; +} + +XclPCItemType XclPCItem::GetType() const +{ + return meType; +} + +// Field settings ============================================================= + +XclPCFieldInfo::XclPCFieldInfo() : + mnFlags( 0 ), + mnGroupChild( 0 ), + mnGroupBase( 0 ), + mnVisItems( 0 ), + mnGroupItems( 0 ), + mnBaseItems( 0 ), + mnOrigItems( 0 ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPCFieldInfo& rInfo ) +{ + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnGroupChild = rStrm.ReaduInt16(); + rInfo.mnGroupBase = rStrm.ReaduInt16(); + rInfo.mnVisItems = rStrm.ReaduInt16(); + rInfo.mnGroupItems = rStrm.ReaduInt16(); + rInfo.mnBaseItems = rStrm.ReaduInt16(); + rInfo.mnOrigItems = rStrm.ReaduInt16(); + if( rStrm.GetRecLeft() >= 3 ) + rInfo.maName = rStrm.ReadUniString(); + else + rInfo.maName.clear(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPCFieldInfo& rInfo ) +{ + return rStrm + << rInfo.mnFlags + << rInfo.mnGroupChild + << rInfo.mnGroupBase + << rInfo.mnVisItems + << rInfo.mnGroupItems + << rInfo.mnBaseItems + << rInfo.mnOrigItems + << XclExpString( rInfo.maName ); +} + +// Numeric grouping field settings ============================================ + +XclPCNumGroupInfo::XclPCNumGroupInfo() : + mnFlags( EXC_SXNUMGROUP_AUTOMIN | EXC_SXNUMGROUP_AUTOMAX ) +{ + SetNumType(); +} + +void XclPCNumGroupInfo::SetNumType() +{ + SetXclDataType( EXC_SXNUMGROUP_TYPE_NUM ); +} + +sal_Int32 XclPCNumGroupInfo::GetScDateType() const +{ + sal_Int32 nScType = 0; + switch( GetXclDataType() ) + { + case EXC_SXNUMGROUP_TYPE_SEC: nScType = ScDPGroupBy::SECONDS; break; + case EXC_SXNUMGROUP_TYPE_MIN: nScType = ScDPGroupBy::MINUTES; break; + case EXC_SXNUMGROUP_TYPE_HOUR: nScType = ScDPGroupBy::HOURS; break; + case EXC_SXNUMGROUP_TYPE_DAY: nScType = ScDPGroupBy::DAYS; break; + case EXC_SXNUMGROUP_TYPE_MONTH: nScType = ScDPGroupBy::MONTHS; break; + case EXC_SXNUMGROUP_TYPE_QUART: nScType = ScDPGroupBy::QUARTERS; break; + case EXC_SXNUMGROUP_TYPE_YEAR: nScType = ScDPGroupBy::YEARS; break; + default: SAL_WARN("sc.filter", "XclPCNumGroupInfo::GetScDateType - unexpected date type " << GetXclDataType() ); + } + return nScType; +} + +void XclPCNumGroupInfo::SetScDateType( sal_Int32 nScType ) +{ + sal_uInt16 nXclType = EXC_SXNUMGROUP_TYPE_NUM; + switch( nScType ) + { + case ScDPGroupBy::SECONDS: nXclType = EXC_SXNUMGROUP_TYPE_SEC; break; + case ScDPGroupBy::MINUTES: nXclType = EXC_SXNUMGROUP_TYPE_MIN; break; + case ScDPGroupBy::HOURS: nXclType = EXC_SXNUMGROUP_TYPE_HOUR; break; + case ScDPGroupBy::DAYS: nXclType = EXC_SXNUMGROUP_TYPE_DAY; break; + case ScDPGroupBy::MONTHS: nXclType = EXC_SXNUMGROUP_TYPE_MONTH; break; + case ScDPGroupBy::QUARTERS: nXclType = EXC_SXNUMGROUP_TYPE_QUART; break; + case ScDPGroupBy::YEARS: nXclType = EXC_SXNUMGROUP_TYPE_YEAR; break; + default: + SAL_INFO("sc.filter", "unexpected date type " << nScType); + } + SetXclDataType( nXclType ); +} + +sal_uInt16 XclPCNumGroupInfo::GetXclDataType() const +{ + return ::extract_value< sal_uInt16 >( mnFlags, 2, 4 ); +} + +void XclPCNumGroupInfo::SetXclDataType( sal_uInt16 nXclType ) +{ + ::insert_value( mnFlags, nXclType, 2, 4 ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPCNumGroupInfo& rInfo ) +{ + rInfo.mnFlags = rStrm.ReaduInt16(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPCNumGroupInfo& rInfo ) +{ + return rStrm << rInfo.mnFlags; +} + +// Base class for pivot cache fields ========================================== + +XclPCField::XclPCField( XclPCFieldType eFieldType, sal_uInt16 nFieldIdx ) : + meFieldType( eFieldType ), + mnFieldIdx( nFieldIdx ) +{ +} + +XclPCField::~XclPCField() +{ +} + +bool XclPCField::IsSupportedField() const +{ + return (meFieldType != EXC_PCFIELD_CALCED) && (meFieldType != EXC_PCFIELD_UNKNOWN); +} + +bool XclPCField::IsStandardField() const +{ + return meFieldType == EXC_PCFIELD_STANDARD; +} + +bool XclPCField::IsStdGroupField() const +{ + return meFieldType == EXC_PCFIELD_STDGROUP; +} + +bool XclPCField::IsNumGroupField() const +{ + return meFieldType == EXC_PCFIELD_NUMGROUP; +} + +bool XclPCField::IsDateGroupField() const +{ + return (meFieldType == EXC_PCFIELD_DATEGROUP) || (meFieldType == EXC_PCFIELD_DATECHILD); +} + +bool XclPCField::IsGroupField() const +{ + return IsStdGroupField() || IsNumGroupField() || IsDateGroupField(); +} + +bool XclPCField::IsGroupBaseField() const +{ + return ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ); +} + +bool XclPCField::IsGroupChildField() const +{ + return (meFieldType == EXC_PCFIELD_STDGROUP) || (meFieldType == EXC_PCFIELD_DATECHILD); +} + +bool XclPCField::HasOrigItems() const +{ + return IsSupportedField() && ((maFieldInfo.mnOrigItems > 0) || HasPostponedItems()); +} + +bool XclPCField::HasInlineItems() const +{ + return (IsStandardField() || IsGroupField()) && ((maFieldInfo.mnGroupItems > 0) || (maFieldInfo.mnOrigItems > 0)); +} + +bool XclPCField::HasPostponedItems() const +{ + return IsStandardField() && ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_POSTPONE ); +} + +bool XclPCField::Has16BitIndexes() const +{ + return IsStandardField() && ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_16BIT ); +} + +// Pivot cache settings ======================================================= + +/** Contains data for a pivot cache (SXDB record). */ +XclPCInfo::XclPCInfo() : + mnSrcRecs( 0 ), + mnStrmId( 0xFFFF ), + mnFlags( EXC_SXDB_DEFAULTFLAGS ), + mnBlockRecs( EXC_SXDB_BLOCKRECS ), + mnStdFields( 0 ), + mnTotalFields( 0 ), + mnSrcType( EXC_SXDB_SRC_SHEET ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPCInfo& rInfo ) +{ + rInfo.mnSrcRecs = rStrm.ReaduInt32(); + rInfo.mnStrmId = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnBlockRecs = rStrm.ReaduInt16(); + rInfo.mnStdFields = rStrm.ReaduInt16(); + rInfo.mnTotalFields = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + rInfo.mnSrcType = rStrm.ReaduInt16(); + rInfo.maUserName = rStrm.ReadUniString(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPCInfo& rInfo ) +{ + return rStrm + << rInfo.mnSrcRecs + << rInfo.mnStrmId + << rInfo.mnFlags + << rInfo.mnBlockRecs + << rInfo.mnStdFields + << rInfo.mnTotalFields + << sal_uInt16( 0 ) + << rInfo.mnSrcType + << XclExpString( rInfo.maUserName ); +} + +// Pivot table + +// cached name ================================================================ + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTCachedName& rCachedName ) +{ + sal_uInt16 nStrLen; + nStrLen = rStrm.ReaduInt16(); + rCachedName.mbUseCache = nStrLen == EXC_PT_NOSTRING; + if( rCachedName.mbUseCache ) + rCachedName.maName.clear(); + else + rCachedName.maName = rStrm.ReadUniString( nStrLen ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTCachedName& rCachedName ) +{ + if( rCachedName.mbUseCache ) + rStrm << EXC_PT_NOSTRING; + else + rStrm << XclExpString( rCachedName.maName, XclStrFlags::NONE, EXC_PT_MAXSTRLEN ); + return rStrm; +} + +const OUString* XclPTVisNameInfo::GetVisName() const +{ + return HasVisName() ? &maVisName.maName : nullptr; +} + +void XclPTVisNameInfo::SetVisName( const OUString& rName ) +{ + maVisName.maName = rName; + maVisName.mbUseCache = rName.isEmpty(); +} + +// Field item settings ======================================================== + +XclPTItemInfo::XclPTItemInfo() : + mnType( EXC_SXVI_TYPE_DATA ), + mnFlags( EXC_SXVI_DEFAULTFLAGS ), + mnCacheIdx( EXC_SXVI_DEFAULT_CACHE ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTItemInfo& rInfo ) +{ + rInfo.mnType = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnCacheIdx = rStrm.ReaduInt16(); + rStrm >> rInfo.maVisName; + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTItemInfo& rInfo ) +{ + return rStrm + << rInfo.mnType + << rInfo.mnFlags + << rInfo.mnCacheIdx + << rInfo.maVisName; +} + +// General field settings ===================================================== + +XclPTFieldInfo::XclPTFieldInfo() : + mnAxes( EXC_SXVD_AXIS_NONE ), + mnSubtCount( 1 ), + mnSubtotals( EXC_SXVD_SUBT_DEFAULT ), + mnItemCount( 0 ), + mnCacheIdx( EXC_SXVD_DEFAULT_CACHE ) +{ +} + +DataPilotFieldOrientation XclPTFieldInfo::GetApiOrient( sal_uInt16 nMask ) const +{ + using namespace ::com::sun::star::sheet; + DataPilotFieldOrientation eOrient = DataPilotFieldOrientation_HIDDEN; + sal_uInt16 nUsedAxes = mnAxes & nMask; + if( nUsedAxes & EXC_SXVD_AXIS_ROW ) + eOrient = DataPilotFieldOrientation_ROW; + else if( nUsedAxes & EXC_SXVD_AXIS_COL ) + eOrient = DataPilotFieldOrientation_COLUMN; + else if( nUsedAxes & EXC_SXVD_AXIS_PAGE ) + eOrient = DataPilotFieldOrientation_PAGE; + else if( nUsedAxes & EXC_SXVD_AXIS_DATA ) + eOrient = DataPilotFieldOrientation_DATA; + return eOrient; +} + +void XclPTFieldInfo::AddApiOrient( DataPilotFieldOrientation eOrient ) +{ + using namespace ::com::sun::star::sheet; + switch( eOrient ) + { + case DataPilotFieldOrientation_ROW: mnAxes |= EXC_SXVD_AXIS_ROW; break; + case DataPilotFieldOrientation_COLUMN: mnAxes |= EXC_SXVD_AXIS_COL; break; + case DataPilotFieldOrientation_PAGE: mnAxes |= EXC_SXVD_AXIS_PAGE; break; + case DataPilotFieldOrientation_DATA: mnAxes |= EXC_SXVD_AXIS_DATA; break; + default:; + } +} + +//TODO: should be a Sequence<GeneralFunction> in ScDPSaveData +void XclPTFieldInfo::GetSubtotals( XclPTSubtotalVec& rSubtotals ) const +{ + rSubtotals.clear(); + rSubtotals.reserve( 16 ); + + if( mnSubtotals & EXC_SXVD_SUBT_DEFAULT ) rSubtotals.push_back( ScGeneralFunction::AUTO ); + if( mnSubtotals & EXC_SXVD_SUBT_SUM ) rSubtotals.push_back( ScGeneralFunction::SUM ); + if( mnSubtotals & EXC_SXVD_SUBT_COUNT ) rSubtotals.push_back( ScGeneralFunction::COUNT ); + if( mnSubtotals & EXC_SXVD_SUBT_AVERAGE ) rSubtotals.push_back( ScGeneralFunction::AVERAGE ); + if( mnSubtotals & EXC_SXVD_SUBT_MAX ) rSubtotals.push_back( ScGeneralFunction::MAX ); + if( mnSubtotals & EXC_SXVD_SUBT_MIN ) rSubtotals.push_back( ScGeneralFunction::MIN ); + if( mnSubtotals & EXC_SXVD_SUBT_PROD ) rSubtotals.push_back( ScGeneralFunction::PRODUCT ); + if( mnSubtotals & EXC_SXVD_SUBT_COUNTNUM ) rSubtotals.push_back( ScGeneralFunction::COUNTNUMS ); + if( mnSubtotals & EXC_SXVD_SUBT_STDDEV ) rSubtotals.push_back( ScGeneralFunction::STDEV ); + if( mnSubtotals & EXC_SXVD_SUBT_STDDEVP ) rSubtotals.push_back( ScGeneralFunction::STDEVP ); + if( mnSubtotals & EXC_SXVD_SUBT_VAR ) rSubtotals.push_back( ScGeneralFunction::VAR ); + if( mnSubtotals & EXC_SXVD_SUBT_VARP ) rSubtotals.push_back( ScGeneralFunction::VARP ); +} + +void XclPTFieldInfo::SetSubtotals( const XclPTSubtotalVec& rSubtotals ) +{ + mnSubtotals = EXC_SXVD_SUBT_NONE; + for( const auto& rSubtotal : rSubtotals ) + { + switch( rSubtotal ) + { + case ScGeneralFunction::AUTO: mnSubtotals |= EXC_SXVD_SUBT_DEFAULT; break; + case ScGeneralFunction::SUM: mnSubtotals |= EXC_SXVD_SUBT_SUM; break; + case ScGeneralFunction::COUNT: mnSubtotals |= EXC_SXVD_SUBT_COUNT; break; + case ScGeneralFunction::AVERAGE: mnSubtotals |= EXC_SXVD_SUBT_AVERAGE; break; + case ScGeneralFunction::MAX: mnSubtotals |= EXC_SXVD_SUBT_MAX; break; + case ScGeneralFunction::MIN: mnSubtotals |= EXC_SXVD_SUBT_MIN; break; + case ScGeneralFunction::PRODUCT: mnSubtotals |= EXC_SXVD_SUBT_PROD; break; + case ScGeneralFunction::COUNTNUMS: mnSubtotals |= EXC_SXVD_SUBT_COUNTNUM; break; + case ScGeneralFunction::STDEV: mnSubtotals |= EXC_SXVD_SUBT_STDDEV; break; + case ScGeneralFunction::STDEVP: mnSubtotals |= EXC_SXVD_SUBT_STDDEVP; break; + case ScGeneralFunction::VAR: mnSubtotals |= EXC_SXVD_SUBT_VAR; break; + case ScGeneralFunction::VARP: mnSubtotals |= EXC_SXVD_SUBT_VARP; break; + default: break; + } + } + + mnSubtCount = 0; + for( sal_uInt16 nMask = 0x8000; nMask; nMask >>= 1 ) + if( mnSubtotals & nMask ) + ++mnSubtCount; +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTFieldInfo& rInfo ) +{ + // rInfo.mnCacheIdx is not part of the SXVD record + rInfo.mnAxes = rStrm.ReaduInt16(); + rInfo.mnSubtCount = rStrm.ReaduInt16(); + rInfo.mnSubtotals = rStrm.ReaduInt16(); + rInfo.mnItemCount = rStrm.ReaduInt16(); + rStrm >> rInfo.maVisName; + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTFieldInfo& rInfo ) +{ + // rInfo.mnCacheIdx is not part of the SXVD record + return rStrm + << rInfo.mnAxes + << rInfo.mnSubtCount + << rInfo.mnSubtotals + << rInfo.mnItemCount + << rInfo.maVisName; +} + +// Extended field settings ==================================================== + +XclPTFieldExtInfo::XclPTFieldExtInfo() : + mnFlags( EXC_SXVDEX_DEFAULTFLAGS ), + mnSortField( EXC_SXVDEX_SORT_OWN ), + mnShowField( EXC_SXVDEX_SHOW_NONE ), + mnNumFmt(0) +{ +} + +sal_Int32 XclPTFieldExtInfo::GetApiSortMode() const +{ + sal_Int32 nSortMode = ScDPSortMode::MANUAL; + if( ::get_flag( mnFlags, EXC_SXVDEX_SORT ) ) + nSortMode = (mnSortField == EXC_SXVDEX_SORT_OWN) ? ScDPSortMode::NAME : ScDPSortMode::DATA; + return nSortMode; +} + +void XclPTFieldExtInfo::SetApiSortMode( sal_Int32 nSortMode ) +{ + bool bSort = (nSortMode == ScDPSortMode::NAME) || (nSortMode == ScDPSortMode::DATA); + ::set_flag( mnFlags, EXC_SXVDEX_SORT, bSort ); + if( nSortMode == ScDPSortMode::NAME ) + mnSortField = EXC_SXVDEX_SORT_OWN; // otherwise sort field has to be set by caller +} + +sal_Int32 XclPTFieldExtInfo::GetApiAutoShowMode() const +{ + return ::get_flagvalue( mnFlags, EXC_SXVDEX_AUTOSHOW_ASC, + ScDPShowItemsMode::FROM_TOP, ScDPShowItemsMode::FROM_BOTTOM ); +} + +void XclPTFieldExtInfo::SetApiAutoShowMode( sal_Int32 nShowMode ) +{ + ::set_flag( mnFlags, EXC_SXVDEX_AUTOSHOW_ASC, nShowMode == ScDPShowItemsMode::FROM_TOP ); +} + +sal_Int32 XclPTFieldExtInfo::GetApiAutoShowCount() const +{ + return ::extract_value< sal_Int32 >( mnFlags, 24, 8 ); +} + +void XclPTFieldExtInfo::SetApiAutoShowCount( sal_Int32 nShowCount ) +{ + ::insert_value( mnFlags, limit_cast< sal_uInt8 >( nShowCount ), 24, 8 ); +} + +sal_Int32 XclPTFieldExtInfo::GetApiLayoutMode() const +{ + sal_Int32 nLayoutMode = ScDPLayoutMode::TABULAR_LAYOUT; + if( ::get_flag( mnFlags, EXC_SXVDEX_LAYOUT_REPORT ) ) + nLayoutMode = ::get_flag( mnFlags, EXC_SXVDEX_LAYOUT_TOP ) ? + ScDPLayoutMode::OUTLINE_SUBTOTALS_TOP : ScDPLayoutMode::OUTLINE_SUBTOTALS_BOTTOM; + return nLayoutMode; +} + +void XclPTFieldExtInfo::SetApiLayoutMode( sal_Int32 nLayoutMode ) +{ + ::set_flag( mnFlags, EXC_SXVDEX_LAYOUT_REPORT, nLayoutMode != ScDPLayoutMode::TABULAR_LAYOUT ); + ::set_flag( mnFlags, EXC_SXVDEX_LAYOUT_TOP, nLayoutMode == ScDPLayoutMode::OUTLINE_SUBTOTALS_TOP ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTFieldExtInfo& rInfo ) +{ + sal_uInt8 nNameLen = 0; + rInfo.mnFlags = rStrm.ReaduInt32(); + rInfo.mnSortField = rStrm.ReaduInt16(); + rInfo.mnShowField = rStrm.ReaduInt16(); + rInfo.mnNumFmt = rStrm.ReaduInt16(); + nNameLen = rStrm.ReaduInt8(); + + rStrm.Ignore(10); + if (nNameLen != 0xFF) + // Custom field total name is used. Pick it up. + rInfo.mpFieldTotalName = rStrm.ReadUniString(nNameLen, 0); + + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTFieldExtInfo& rInfo ) +{ + rStrm << rInfo.mnFlags + << rInfo.mnSortField + << rInfo.mnShowField + << EXC_SXVDEX_FORMAT_NONE; + + if (rInfo.mpFieldTotalName && !rInfo.mpFieldTotalName->isEmpty()) + { + OUString aFinalName = *rInfo.mpFieldTotalName; + if (aFinalName.getLength() >= 254) + aFinalName = aFinalName.copy(0, 254); + sal_uInt8 nNameLen = static_cast<sal_uInt8>(aFinalName.getLength()); + rStrm << nNameLen; + rStrm.WriteZeroBytes(10); + rStrm << XclExpString(aFinalName, XclStrFlags::NoHeader); + } + else + { + rStrm << sal_uInt16(0xFFFF); + rStrm.WriteZeroBytes(8); + } + return rStrm; +} + +// Page field settings ======================================================== + +XclPTPageFieldInfo::XclPTPageFieldInfo() : + mnField( 0 ), + mnSelItem( EXC_SXPI_ALLITEMS ), + mnObjId( 0xFFFF ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTPageFieldInfo& rInfo ) +{ + rInfo.mnField = rStrm.ReaduInt16(); + rInfo.mnSelItem = rStrm.ReaduInt16(); + rInfo.mnObjId = rStrm.ReaduInt16(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTPageFieldInfo& rInfo ) +{ + return rStrm + << rInfo.mnField + << rInfo.mnSelItem + << rInfo.mnObjId; +} + +// Data field settings ======================================================== + +XclPTDataFieldInfo::XclPTDataFieldInfo() : + mnField( 0 ), + mnAggFunc( EXC_SXDI_FUNC_SUM ), + mnRefType( EXC_SXDI_REF_NORMAL ), + mnRefField( 0 ), + mnRefItem( 0 ), + mnNumFmt( 0 ) +{ +} + +ScGeneralFunction XclPTDataFieldInfo::GetApiAggFunc() const +{ + ScGeneralFunction eAggFunc; + switch( mnAggFunc ) + { + case EXC_SXDI_FUNC_SUM: eAggFunc = ScGeneralFunction::SUM; break; + case EXC_SXDI_FUNC_COUNT: eAggFunc = ScGeneralFunction::COUNT; break; + case EXC_SXDI_FUNC_AVERAGE: eAggFunc = ScGeneralFunction::AVERAGE; break; + case EXC_SXDI_FUNC_MAX: eAggFunc = ScGeneralFunction::MAX; break; + case EXC_SXDI_FUNC_MIN: eAggFunc = ScGeneralFunction::MIN; break; + case EXC_SXDI_FUNC_PRODUCT: eAggFunc = ScGeneralFunction::PRODUCT; break; + case EXC_SXDI_FUNC_COUNTNUM: eAggFunc = ScGeneralFunction::COUNTNUMS; break; + case EXC_SXDI_FUNC_STDDEV: eAggFunc = ScGeneralFunction::STDEV; break; + case EXC_SXDI_FUNC_STDDEVP: eAggFunc = ScGeneralFunction::STDEVP; break; + case EXC_SXDI_FUNC_VAR: eAggFunc = ScGeneralFunction::VAR; break; + case EXC_SXDI_FUNC_VARP: eAggFunc = ScGeneralFunction::VARP; break; + default: eAggFunc = ScGeneralFunction::SUM; + } + return eAggFunc; +} + +void XclPTDataFieldInfo::SetApiAggFunc( ScGeneralFunction eAggFunc ) +{ + switch( eAggFunc ) + { + case ScGeneralFunction::SUM: mnAggFunc = EXC_SXDI_FUNC_SUM; break; + case ScGeneralFunction::COUNT: mnAggFunc = EXC_SXDI_FUNC_COUNT; break; + case ScGeneralFunction::AVERAGE: mnAggFunc = EXC_SXDI_FUNC_AVERAGE; break; + case ScGeneralFunction::MAX: mnAggFunc = EXC_SXDI_FUNC_MAX; break; + case ScGeneralFunction::MIN: mnAggFunc = EXC_SXDI_FUNC_MIN; break; + case ScGeneralFunction::PRODUCT: mnAggFunc = EXC_SXDI_FUNC_PRODUCT; break; + case ScGeneralFunction::COUNTNUMS: mnAggFunc = EXC_SXDI_FUNC_COUNTNUM; break; + case ScGeneralFunction::STDEV: mnAggFunc = EXC_SXDI_FUNC_STDDEV; break; + case ScGeneralFunction::STDEVP: mnAggFunc = EXC_SXDI_FUNC_STDDEVP; break; + case ScGeneralFunction::VAR: mnAggFunc = EXC_SXDI_FUNC_VAR; break; + case ScGeneralFunction::VARP: mnAggFunc = EXC_SXDI_FUNC_VARP; break; + default: mnAggFunc = EXC_SXDI_FUNC_SUM; + } +} + +sal_Int32 XclPTDataFieldInfo::GetApiRefType() const +{ + namespace ScDPRefType = ::com::sun::star::sheet::DataPilotFieldReferenceType; + sal_Int32 nRefType; + switch( mnRefType ) + { + case EXC_SXDI_REF_DIFF: nRefType = ScDPRefType::ITEM_DIFFERENCE; break; + case EXC_SXDI_REF_PERC: nRefType = ScDPRefType::ITEM_PERCENTAGE; break; + case EXC_SXDI_REF_PERC_DIFF: nRefType = ScDPRefType::ITEM_PERCENTAGE_DIFFERENCE; break; + case EXC_SXDI_REF_RUN_TOTAL: nRefType = ScDPRefType::RUNNING_TOTAL; break; + case EXC_SXDI_REF_PERC_ROW: nRefType = ScDPRefType::ROW_PERCENTAGE; break; + case EXC_SXDI_REF_PERC_COL: nRefType = ScDPRefType::COLUMN_PERCENTAGE; break; + case EXC_SXDI_REF_PERC_TOTAL: nRefType = ScDPRefType::TOTAL_PERCENTAGE; break; + case EXC_SXDI_REF_INDEX: nRefType = ScDPRefType::INDEX; break; + default: nRefType = ScDPRefType::NONE; + } + return nRefType; +} + +void XclPTDataFieldInfo::SetApiRefType( sal_Int32 nRefType ) +{ + namespace ScDPRefType = ::com::sun::star::sheet::DataPilotFieldReferenceType; + switch( nRefType ) + { + case ScDPRefType::ITEM_DIFFERENCE: mnRefType = EXC_SXDI_REF_DIFF; break; + case ScDPRefType::ITEM_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC; break; + case ScDPRefType::ITEM_PERCENTAGE_DIFFERENCE: mnRefType = EXC_SXDI_REF_PERC_DIFF; break; + case ScDPRefType::RUNNING_TOTAL: mnRefType = EXC_SXDI_REF_RUN_TOTAL; break; + case ScDPRefType::ROW_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC_ROW; break; + case ScDPRefType::COLUMN_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC_COL; break; + case ScDPRefType::TOTAL_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC_TOTAL;break; + case ScDPRefType::INDEX: mnRefType = EXC_SXDI_REF_INDEX; break; + default: mnRefType = EXC_SXDI_REF_NORMAL; + } +} + +sal_Int32 XclPTDataFieldInfo::GetApiRefItemType() const +{ + sal_Int32 nRefItemType; + switch( mnRefItem ) + { + case EXC_SXDI_PREVITEM: nRefItemType = ScDPRefItemType::PREVIOUS; break; + case EXC_SXDI_NEXTITEM: nRefItemType = ScDPRefItemType::NEXT; break; + default: nRefItemType = ScDPRefItemType::NAMED; + } + return nRefItemType; +} + +void XclPTDataFieldInfo::SetApiRefItemType( sal_Int32 nRefItemType ) +{ + switch( nRefItemType ) + { + case ScDPRefItemType::PREVIOUS: mnRefItem = EXC_SXDI_PREVITEM; break; + case ScDPRefItemType::NEXT: mnRefItem = EXC_SXDI_NEXTITEM; break; + // nothing for named item reference + } +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTDataFieldInfo& rInfo ) +{ + rInfo.mnField = rStrm.ReaduInt16(); + rInfo.mnAggFunc = rStrm.ReaduInt16(); + rInfo.mnRefType = rStrm.ReaduInt16(); + rInfo.mnRefField = rStrm.ReaduInt16(); + rInfo.mnRefItem = rStrm.ReaduInt16(); + rInfo.mnNumFmt = rStrm.ReaduInt16(); + rStrm >> rInfo.maVisName; + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTDataFieldInfo& rInfo ) +{ + return rStrm + << rInfo.mnField + << rInfo.mnAggFunc + << rInfo.mnRefType + << rInfo.mnRefField + << rInfo.mnRefItem + << rInfo.mnNumFmt + << rInfo.maVisName; +} + +// Pivot table settings ======================================================= + +XclPTInfo::XclPTInfo() : + mnFirstHeadRow( 0 ), + mnCacheIdx( 0xFFFF ), + mnDataAxis( EXC_SXVD_AXIS_NONE ), + mnDataPos( EXC_SXVIEW_DATALAST ), + mnFields( 0 ), + mnRowFields( 0 ), + mnColFields( 0 ), + mnPageFields( 0 ), + mnDataFields( 0 ), + mnDataRows( 0 ), + mnDataCols( 0 ), + mnFlags( EXC_SXVIEW_DEFAULTFLAGS ), + mnAutoFmtIdx( EXC_SXVIEW_AUTOFMT ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTInfo& rInfo ) +{ + sal_uInt16 nTabLen, nDataLen; + + rStrm >> rInfo.maOutXclRange; + rInfo.mnFirstHeadRow = rStrm.ReaduInt16(); + rStrm >> rInfo.maDataXclPos; + rInfo.mnCacheIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + rInfo.mnDataAxis = rStrm.ReaduInt16(); + rInfo.mnDataPos = rStrm.ReaduInt16(); + rInfo.mnFields = rStrm.ReaduInt16(); + rInfo.mnRowFields = rStrm.ReaduInt16(); + rInfo.mnColFields = rStrm.ReaduInt16(); + rInfo.mnPageFields = rStrm.ReaduInt16(); + rInfo.mnDataFields = rStrm.ReaduInt16(); + rInfo.mnDataRows = rStrm.ReaduInt16(); + rInfo.mnDataCols = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnAutoFmtIdx = rStrm.ReaduInt16(); + nTabLen = rStrm.ReaduInt16(); + nDataLen = rStrm.ReaduInt16(); + rInfo.maTableName = rStrm.ReadUniString( nTabLen ); + rInfo.maDataName = rStrm.ReadUniString( nDataLen ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTInfo& rInfo ) +{ + XclExpString aXclTableName( rInfo.maTableName ); + XclExpString aXclDataName( rInfo.maDataName ); + + rStrm << rInfo.maOutXclRange + << rInfo.mnFirstHeadRow + << rInfo.maDataXclPos + << rInfo.mnCacheIdx + << sal_uInt16( 0 ) + << rInfo.mnDataAxis << rInfo.mnDataPos + << rInfo.mnFields + << rInfo.mnRowFields << rInfo.mnColFields + << rInfo.mnPageFields << rInfo.mnDataFields + << rInfo.mnDataRows << rInfo.mnDataCols + << rInfo.mnFlags + << rInfo.mnAutoFmtIdx + << aXclTableName.Len() << aXclDataName.Len(); + aXclTableName.WriteFlagField( rStrm ); + aXclTableName.WriteBuffer( rStrm ); + aXclDataName.WriteFlagField( rStrm ); + aXclDataName.WriteBuffer( rStrm ); + return rStrm; +} + +// Extended pivot table settings ============================================== + +XclPTExtInfo::XclPTExtInfo() : + mnSxformulaRecs( 0 ), + mnSxselectRecs( 0 ), + mnPagePerRow( 0 ), + mnPagePerCol( 0 ), + mnFlags( EXC_SXEX_DEFAULTFLAGS ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTExtInfo& rInfo ) +{ + rInfo.mnSxformulaRecs = rStrm.ReaduInt16(); + rStrm.Ignore( 6 ); + rInfo.mnSxselectRecs = rStrm.ReaduInt16(); + rInfo.mnPagePerRow = rStrm.ReaduInt16(); + rInfo.mnPagePerCol = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt32(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTExtInfo& rInfo ) +{ + return rStrm + << rInfo.mnSxformulaRecs + << EXC_PT_NOSTRING // length of alt. error text + << EXC_PT_NOSTRING // length of alt. empty text + << EXC_PT_NOSTRING // length of tag + << rInfo.mnSxselectRecs + << rInfo.mnPagePerRow + << rInfo.mnPagePerCol + << rInfo.mnFlags + << EXC_PT_NOSTRING // length of page field style name + << EXC_PT_NOSTRING // length of table style name + << EXC_PT_NOSTRING; // length of vacate style name +} + +// Pivot table autoformat settings ============================================ + +/** +classic : 10 08 00 00 00 00 00 00 20 00 00 00 01 00 00 00 00 +default : 10 08 00 00 00 00 00 00 20 00 00 00 01 00 00 00 00 +report01 : 10 08 02 00 00 00 00 00 20 00 00 00 00 10 00 00 00 +report02 : 10 08 02 00 00 00 00 00 20 00 00 00 01 10 00 00 00 +report03 : 10 08 02 00 00 00 00 00 20 00 00 00 02 10 00 00 00 +report04 : 10 08 02 00 00 00 00 00 20 00 00 00 03 10 00 00 00 +report05 : 10 08 02 00 00 00 00 00 20 00 00 00 04 10 00 00 00 +report06 : 10 08 02 00 00 00 00 00 20 00 00 00 05 10 00 00 00 +report07 : 10 08 02 00 00 00 00 00 20 00 00 00 06 10 00 00 00 +report08 : 10 08 02 00 00 00 00 00 20 00 00 00 07 10 00 00 00 +report09 : 10 08 02 00 00 00 00 00 20 00 00 00 08 10 00 00 00 +report10 : 10 08 02 00 00 00 00 00 20 00 00 00 09 10 00 00 00 +table01 : 10 08 00 00 00 00 00 00 20 00 00 00 0a 10 00 00 00 +table02 : 10 08 00 00 00 00 00 00 20 00 00 00 0b 10 00 00 00 +table03 : 10 08 00 00 00 00 00 00 20 00 00 00 0c 10 00 00 00 +table04 : 10 08 00 00 00 00 00 00 20 00 00 00 0d 10 00 00 00 +table05 : 10 08 00 00 00 00 00 00 20 00 00 00 0e 10 00 00 00 +table06 : 10 08 00 00 00 00 00 00 20 00 00 00 0f 10 00 00 00 +table07 : 10 08 00 00 00 00 00 00 20 00 00 00 10 10 00 00 00 +table08 : 10 08 00 00 00 00 00 00 20 00 00 00 11 10 00 00 00 +table09 : 10 08 00 00 00 00 00 00 20 00 00 00 12 10 00 00 00 +table10 : 10 08 00 00 00 00 00 00 20 00 00 00 13 10 00 00 00 +none : 10 08 00 00 00 00 00 00 20 00 00 00 15 10 00 00 00 +**/ + +XclPTViewEx9Info::XclPTViewEx9Info() : + mbReport( 0 ), + mnAutoFormat( 0 ), + mnGridLayout( 0x10 ) +{ +} + +void XclPTViewEx9Info::Init( const ScDPObject& rDPObj ) +{ + if( rDPObj.GetHeaderLayout() ) + { + mbReport = 0; + mnAutoFormat = 1; + mnGridLayout = 0; + } + else + { + // Report1 for now + // TODO : sync with autoformat indices + mbReport = 2; + mnAutoFormat = 1; + mnGridLayout = 0x10; + } + + const ScDPSaveData* pData = rDPObj.GetSaveData(); + if (pData) + { + const std::optional<OUString> & pGrandTotal = pData->GetGrandTotalName(); + if (pGrandTotal) + maGrandTotalName = *pGrandTotal; + } +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTViewEx9Info& rInfo ) +{ + rStrm.Ignore( 2 ); + rInfo.mbReport = rStrm.ReaduInt32(); /// 2 for report* fmts ? + rStrm.Ignore( 6 ); + rInfo.mnAutoFormat = rStrm.ReaduInt8(); + rInfo.mnGridLayout = rStrm.ReaduInt8(); + rInfo.maGrandTotalName = rStrm.ReadUniString(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTViewEx9Info& rInfo ) +{ + return rStrm + << EXC_PT_AUTOFMT_HEADER + << rInfo.mbReport + << EXC_PT_AUTOFMT_ZERO + << EXC_PT_AUTOFMT_FLAGS + << rInfo.mnAutoFormat + << rInfo.mnGridLayout + << XclExpString(rInfo.maGrandTotalName, XclStrFlags::NONE, EXC_PT_MAXSTRLEN); +} + +XclPTAddl::XclPTAddl() : + mbCompactMode(false) +{ +} + +XclImpStream& operator>>(XclImpStream& rStrm, XclPTAddl& rInfo) +{ + rStrm.Ignore(4); + sal_uInt8 sxc = rStrm.ReaduInt8(); + sal_uInt8 sxd = rStrm.ReaduInt8(); + if(sxc == 0x00 && sxd == 0x19) // SxcView / sxdVer12Info + { + sal_uInt32 nFlags = rStrm.ReaduInt32(); + rInfo.mbCompactMode = ((nFlags & 0x00000008) != 0); + } + return rStrm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlroot.cxx b/sc/source/filter/excel/xlroot.cxx new file mode 100644 index 000000000..bac3ca1b3 --- /dev/null +++ b/sc/source/filter/excel/xlroot.cxx @@ -0,0 +1,438 @@ +/* -*- 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 <memory> +#include <xlroot.hxx> +#include <sal/log.hxx> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <sot/storage.hxx> +#include <vcl/svapp.hxx> +#include <svl/numformat.hxx> +#include <svl/stritem.hxx> +#include <svl/languageoptions.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <vcl/font.hxx> +#include <vcl/settings.hxx> +#include <tools/diagnose_ex.h> + +#include <editeng/editstat.hxx> +#include <scitems.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <document.hxx> +#include <docpool.hxx> +#include <docuno.hxx> +#include <editutil.hxx> +#include <drwlayer.hxx> +#include <scextopt.hxx> +#include <patattr.hxx> +#include <fapihelper.hxx> +#include <xlconst.hxx> +#include <xlstyle.hxx> +#include <xlchart.hxx> +#include <xltracer.hxx> +#include <xltools.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/useroptions.hxx> +#include <root.hxx> + +namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::awt::XDevice; +using ::com::sun::star::awt::DeviceInfo; +using ::com::sun::star::frame::XFrame; + +using namespace ::com::sun::star; + +// Global data ================================================================ + +#ifdef DBG_UTIL +XclDebugObjCounter::~XclDebugObjCounter() +{ + OSL_ENSURE( mnObjCnt == 0, "XclDebugObjCounter::~XclDebugObjCounter - wrong root object count" ); +} +#endif + +XclRootData::XclRootData( XclBiff eBiff, SfxMedium& rMedium, + tools::SvRef<SotStorage> const & xRootStrg, ScDocument& rDoc, rtl_TextEncoding eTextEnc, bool bExport ) : + meBiff( eBiff ), + meOutput( EXC_OUTPUT_BINARY ), + mrMedium( rMedium ), + mxRootStrg( xRootStrg ), + mrDoc( rDoc ), + meTextEnc( eTextEnc ), + meSysLang( Application::GetSettings().GetLanguageTag().getLanguageType() ), + meDocLang( Application::GetSettings().GetLanguageTag().getLanguageType() ), + meUILang( Application::GetSettings().GetUILanguageTag().getLanguageType() ), + mnDefApiScript( ApiScriptType::LATIN ), + maScMaxPos( mrDoc.MaxCol(), mrDoc.MaxRow(), MAXTAB ), + maXclMaxPos( EXC_MAXCOL2, EXC_MAXROW2, EXC_MAXTAB2 ), + maMaxPos( EXC_MAXCOL2, EXC_MAXROW2, EXC_MAXTAB2 ), + mxFontPropSetHlp( std::make_shared<XclFontPropSetHelper>() ), + mxChPropSetHlp( std::make_shared<XclChPropSetHelper>() ), + mxRD( std::make_shared<RootData>() ), + mfScreenPixelX( 50.0 ), + mfScreenPixelY( 50.0 ), + mnCharWidth( 110 ), + mnSpaceWidth(45), + mnScTab( 0 ), + mbExport( bExport ) +{ + if (!utl::ConfigManager::IsFuzzing()) + maUserName = SvtUserOptions().GetLastName(); + if (maUserName.isEmpty()) + maUserName = "Calc"; + + switch( ScGlobal::GetDefaultScriptType() ) + { + case SvtScriptType::LATIN: mnDefApiScript = ApiScriptType::LATIN; break; + case SvtScriptType::ASIAN: mnDefApiScript = ApiScriptType::ASIAN; break; + case SvtScriptType::COMPLEX: mnDefApiScript = ApiScriptType::COMPLEX; break; + default: SAL_WARN( "sc", "XclRootData::XclRootData - unknown script type" ); + } + + // maximum cell position + switch( meBiff ) + { + case EXC_BIFF2: maXclMaxPos.Set( EXC_MAXCOL2, EXC_MAXROW2, EXC_MAXTAB2 ); break; + case EXC_BIFF3: maXclMaxPos.Set( EXC_MAXCOL3, EXC_MAXROW3, EXC_MAXTAB3 ); break; + case EXC_BIFF4: maXclMaxPos.Set( EXC_MAXCOL4, EXC_MAXROW4, EXC_MAXTAB4 ); break; + case EXC_BIFF5: maXclMaxPos.Set( EXC_MAXCOL5, EXC_MAXROW5, EXC_MAXTAB5 ); break; + case EXC_BIFF8: maXclMaxPos.Set( EXC_MAXCOL8, EXC_MAXROW8, EXC_MAXTAB8 ); break; + default: DBG_ERROR_BIFF(); + } + maMaxPos.SetCol( ::std::min( maScMaxPos.Col(), maXclMaxPos.Col() ) ); + maMaxPos.SetRow( ::std::min( maScMaxPos.Row(), maXclMaxPos.Row() ) ); + maMaxPos.SetTab( ::std::min( maScMaxPos.Tab(), maXclMaxPos.Tab() ) ); + + // document URL and path + if( const SfxItemSet* pItemSet = mrMedium.GetItemSet() ) + if( const SfxStringItem* pItem = pItemSet->GetItem<SfxStringItem>( SID_FILE_NAME ) ) + maDocUrl = pItem->GetValue(); + maBasePath = maDocUrl.copy( 0, maDocUrl.lastIndexOf( '/' ) + 1 ); + + // extended document options - always own object, try to copy existing data from document + if( const ScExtDocOptions* pOldDocOpt = mrDoc.GetExtDocOptions() ) + mxExtDocOpt = std::make_shared<ScExtDocOptions>( *pOldDocOpt ); + else + mxExtDocOpt = std::make_shared<ScExtDocOptions>(); + + // screen pixel size + try + { + Reference< frame::XDesktop2 > xFramesSupp = frame::Desktop::create( ::comphelper::getProcessComponentContext() ); + Reference< XFrame > xFrame( xFramesSupp->getActiveFrame(), UNO_SET_THROW ); + Reference< XDevice > xDevice( xFrame->getContainerWindow(), UNO_QUERY_THROW ); + DeviceInfo aDeviceInfo = xDevice->getInfo(); + mfScreenPixelX = (aDeviceInfo.PixelPerMeterX > 0) ? (100000.0 / aDeviceInfo.PixelPerMeterX) : 50.0; + mfScreenPixelY = (aDeviceInfo.PixelPerMeterY > 0) ? (100000.0 / aDeviceInfo.PixelPerMeterY) : 50.0; + } + catch( const Exception&) + { + TOOLS_WARN_EXCEPTION( "sc", "XclRootData::XclRootData - cannot get output device info"); + } +} + +XclRootData::~XclRootData() +{ +} + +XclRoot::XclRoot( XclRootData& rRootData ) : + mrData( rRootData ) +{ +#if defined(DBG_UTIL) && OSL_DEBUG_LEVEL > 0 + ++mrData.mnObjCnt; +#endif + + // filter tracer + mrData.mxTracer = std::make_shared<XclTracer>( GetDocUrl() ); +} + +XclRoot::XclRoot( const XclRoot& rRoot ) : + mrData( rRoot.mrData ) +{ +#if defined(DBG_UTIL) && OSL_DEBUG_LEVEL > 0 + ++mrData.mnObjCnt; +#endif +} + +XclRoot::~XclRoot() +{ +#if defined(DBG_UTIL) && OSL_DEBUG_LEVEL > 0 + --mrData.mnObjCnt; +#endif +} + +XclRoot& XclRoot::operator=( const XclRoot& rRoot ) +{ + (void)rRoot; // avoid compiler warning + // allowed for assignment in derived classes - but test if the same root data is used + OSL_ENSURE( &mrData == &rRoot.mrData, "XclRoot::operator= - incompatible root data" ); + return *this; +} + +void XclRoot::SetTextEncoding( rtl_TextEncoding eTextEnc ) +{ + if( eTextEnc != RTL_TEXTENCODING_DONTKNOW ) + mrData.meTextEnc = eTextEnc; +} + +void XclRoot::SetCharWidth( const XclFontData& rFontData ) +{ + mrData.mnCharWidth = 0; + if( OutputDevice* pPrinter = GetPrinter() ) + { + vcl::Font aFont( rFontData.maName, Size( 0, rFontData.mnHeight ) ); + aFont.SetFamily( rFontData.GetScFamily( GetTextEncoding() ) ); + aFont.SetCharSet( rFontData.GetFontEncoding() ); + aFont.SetWeight( rFontData.GetScWeight() ); + pPrinter->SetFont( aFont ); + // Usually digits have the same width, but in some fonts they don't ... + // Match the import in sc/source/filter/oox/unitconverter.cxx + // UnitConverter::finalizeImport() + for (sal_Unicode cChar = '0'; cChar <= '9'; ++cChar) + mrData.mnCharWidth = std::max( pPrinter->GetTextWidth( OUString(cChar)), mrData.mnCharWidth); + + // Set the width of space ' ' character. + mrData.mnSpaceWidth = pPrinter->GetTextWidth(OUString(' ')); + } + if( mrData.mnCharWidth <= 0 ) + { + // #i48717# Win98 with HP LaserJet returns 0 + SAL_WARN( "sc", "XclRoot::SetCharWidth - invalid character width (no printer?)" ); + mrData.mnCharWidth = 11 * rFontData.mnHeight / 20; + } + if (mrData.mnSpaceWidth <= 0) + { + SAL_WARN( "sc", "XclRoot::SetCharWidth - invalid character width (no printer?)" ); + mrData.mnSpaceWidth = 45; + } +} + +sal_Int32 XclRoot::GetHmmFromPixelX( double fPixelX ) const +{ + return static_cast< sal_Int32 >( fPixelX * mrData.mfScreenPixelX + 0.5 ); +} + +sal_Int32 XclRoot::GetHmmFromPixelY( double fPixelY ) const +{ + return static_cast< sal_Int32 >( fPixelY * mrData.mfScreenPixelY + 0.5 ); +} + +uno::Sequence< beans::NamedValue > XclRoot::RequestEncryptionData( ::comphelper::IDocPasswordVerifier& rVerifier ) const +{ + ::std::vector< OUString > aDefaultPasswords { XclRootData::gaDefPassword }; + return ScfApiHelper::QueryEncryptionDataForMedium( mrData.mrMedium, rVerifier, &aDefaultPasswords ); +} + +bool XclRoot::HasVbaStorage() const +{ + tools::SvRef<SotStorage> xRootStrg = GetRootStorage(); + return xRootStrg.is() && xRootStrg->IsContained( EXC_STORAGE_VBA_PROJECT ); +} + +tools::SvRef<SotStorage> XclRoot::OpenStorage( tools::SvRef<SotStorage> const & xStrg, const OUString& rStrgName ) const +{ + return mrData.mbExport ? + ScfTools::OpenStorageWrite( xStrg, rStrgName ) : + ScfTools::OpenStorageRead( xStrg, rStrgName ); +} + +tools::SvRef<SotStorage> XclRoot::OpenStorage( const OUString& rStrgName ) const +{ + return OpenStorage( GetRootStorage(), rStrgName ); +} + +tools::SvRef<SotStorageStream> XclRoot::OpenStream( tools::SvRef<SotStorage> const & xStrg, const OUString& rStrmName ) const +{ + return mrData.mbExport ? + ScfTools::OpenStorageStreamWrite( xStrg, rStrmName ) : + ScfTools::OpenStorageStreamRead( xStrg, rStrmName ); +} + +tools::SvRef<SotStorageStream> XclRoot::OpenStream( const OUString& rStrmName ) const +{ + return OpenStream( GetRootStorage(), rStrmName ); +} + +ScDocument& XclRoot::GetDoc() const +{ + return mrData.mrDoc; +} + +SfxObjectShell* XclRoot::GetDocShell() const +{ + return GetDoc().GetDocumentShell(); +} + +ScModelObj* XclRoot::GetDocModelObj() const +{ + SfxObjectShell* pDocShell = GetDocShell(); + return pDocShell ? comphelper::getFromUnoTunnel<ScModelObj>( pDocShell->GetModel() ) : nullptr; +} + +OutputDevice* XclRoot::GetPrinter() const +{ + return GetDoc().GetRefDevice(); +} + +ScStyleSheetPool& XclRoot::GetStyleSheetPool() const +{ + return *GetDoc().GetStyleSheetPool(); +} + +ScRangeName& XclRoot::GetNamedRanges() const +{ + return *GetDoc().GetRangeName(); +} + +SdrPage* XclRoot::GetSdrPage( SCTAB nScTab ) const +{ + return ((nScTab >= 0) && GetDoc().GetDrawLayer()) ? + GetDoc().GetDrawLayer()->GetPage( static_cast< sal_uInt16 >( nScTab ) ) : nullptr; +} + +SvNumberFormatter& XclRoot::GetFormatter() const +{ + return *GetDoc().GetFormatTable(); +} + +DateTime XclRoot::GetNullDate() const +{ + return GetFormatter().GetNullDate(); +} + +sal_uInt16 XclRoot::GetBaseYear() const +{ + // return 1904 for 1904-01-01, and 1900 for 1899-12-30 + return (GetNullDate().GetYear() == 1904) ? 1904 : 1900; +} + +const DateTime theOurCompatNullDate( Date( 30, 12, 1899 )); +const DateTime theExcelCutOverDate( Date( 1, 3, 1900 )); + +double XclRoot::GetDoubleFromDateTime( const DateTime& rDateTime ) const +{ + double fValue = rDateTime - GetNullDate(); + // adjust dates before 1900-03-01 to get correct time values in the range [0.0,1.0) + /* XXX: this is only used when reading BIFF, otherwise we'd have to check + * for dateCompatibility==true as mentioned below. */ + if( rDateTime < theExcelCutOverDate && GetNullDate() == theOurCompatNullDate ) + fValue -= 1.0; + return fValue; +} + +DateTime XclRoot::GetDateTimeFromDouble( double fValue ) const +{ + DateTime aDateTime = GetNullDate() + fValue; + // adjust dates before 1900-03-01 to get correct time values + /* FIXME: correction should only be done when writing BIFF or OOXML + * transitional with dateCompatibility==true (or absent for default true), + * but not if strict ISO/IEC 29500 which does not have the Excel error + * compatibility and the null date is the same 1899-12-30 as ours. */ + if( aDateTime < theExcelCutOverDate && GetNullDate() == theOurCompatNullDate ) + aDateTime.AddDays(1); + return aDateTime; +} + +ScEditEngineDefaulter& XclRoot::GetEditEngine() const +{ + if( !mrData.mxEditEngine ) + { + mrData.mxEditEngine = std::make_shared<ScEditEngineDefaulter>( GetDoc().GetEnginePool() ); + ScEditEngineDefaulter& rEE = *mrData.mxEditEngine; + rEE.SetRefMapMode(MapMode(MapUnit::Map100thMM)); + rEE.SetEditTextObjectPool( GetDoc().GetEditPool() ); + rEE.SetUpdateLayout( false ); + rEE.EnableUndo( false ); + rEE.SetControlWord( rEE.GetControlWord() & ~EEControlBits::ALLOWBIGOBJS ); + } + return *mrData.mxEditEngine; +} + +ScHeaderEditEngine& XclRoot::GetHFEditEngine() const +{ + if( !mrData.mxHFEditEngine ) + { + mrData.mxHFEditEngine = std::make_shared<ScHeaderEditEngine>( EditEngine::CreatePool().get() ); + ScHeaderEditEngine& rEE = *mrData.mxHFEditEngine; + rEE.SetRefMapMode(MapMode(MapUnit::MapTwip)); // headers/footers use twips as default metric + rEE.SetUpdateLayout( false ); + rEE.EnableUndo( false ); + rEE.SetControlWord( rEE.GetControlWord() & ~EEControlBits::ALLOWBIGOBJS ); + + // set Calc header/footer defaults + auto pEditSet = std::make_unique<SfxItemSet>( rEE.GetEmptyItemSet() ); + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aItemSet( *GetDoc().GetPool() ); + ScPatternAttr::FillToEditItemSet( *pEditSet, aItemSet ); + // FillToEditItemSet() adjusts font height to 1/100th mm, we need twips + pEditSet->Put( aItemSet.Get( ATTR_FONT_HEIGHT ).CloneSetWhich(EE_CHAR_FONTHEIGHT) ); + pEditSet->Put( aItemSet.Get( ATTR_CJK_FONT_HEIGHT ).CloneSetWhich(EE_CHAR_FONTHEIGHT_CJK) ); + pEditSet->Put( aItemSet.Get( ATTR_CTL_FONT_HEIGHT ).CloneSetWhich(EE_CHAR_FONTHEIGHT_CTL) ); + rEE.SetDefaults( std::move(pEditSet) ); // takes ownership + } + return *mrData.mxHFEditEngine; +} + +EditEngine& XclRoot::GetDrawEditEngine() const +{ + if( !mrData.mxDrawEditEng ) + { + mrData.mxDrawEditEng = std::make_shared<EditEngine>( &GetDoc().GetDrawLayer()->GetItemPool() ); + EditEngine& rEE = *mrData.mxDrawEditEng; + rEE.SetRefMapMode(MapMode(MapUnit::Map100thMM)); + rEE.SetUpdateLayout( false ); + rEE.EnableUndo( false ); + rEE.SetControlWord( rEE.GetControlWord() & ~EEControlBits::ALLOWBIGOBJS ); + } + return *mrData.mxDrawEditEng; +} + +XclFontPropSetHelper& XclRoot::GetFontPropSetHelper() const +{ + return *mrData.mxFontPropSetHlp; +} + +XclChPropSetHelper& XclRoot::GetChartPropSetHelper() const +{ + return *mrData.mxChPropSetHlp; +} + +ScExtDocOptions& XclRoot::GetExtDocOptions() const +{ + return *mrData.mxExtDocOpt; +} + +XclTracer& XclRoot::GetTracer() const +{ + return *mrData.mxTracer; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlstyle.cxx b/sc/source/filter/excel/xlstyle.cxx new file mode 100644 index 000000000..ae4a6f1f6 --- /dev/null +++ b/sc/source/filter/excel/xlstyle.cxx @@ -0,0 +1,1744 @@ +/* -*- 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 <xlstyle.hxx> +#include <com/sun/star/awt/FontFamily.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/font.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <rtl/tencinfo.h> +#include <svl/numformat.hxx> +#include <svtools/colorcfg.hxx> +#include <vcl/unohelp.hxx> +#include <editeng/svxfont.hxx> +#include <o3tl/unit_conversion.hxx> +#include <global.hxx> +#include <xlroot.hxx> +#include <xltools.hxx> +// Color data ================================================================= + +/** Standard EGA colors, bright. */ +#define EXC_PALETTE_EGA_COLORS_LIGHT \ + Color(0x000000), Color(0xFFFFFF), Color(0xFF0000), Color(0x00FF00), Color(0x0000FF), Color(0xFFFF00), Color(0xFF00FF), Color(0x00FFFF) +/** Standard EGA colors, dark. */ +#define EXC_PALETTE_EGA_COLORS_DARK \ + Color(0x800000), Color(0x008000), Color(0x000080), Color(0x808000), Color(0x800080), Color(0x008080), Color(0xC0C0C0), Color(0x808080) + +/** Default color table for BIFF2. */ +const Color spnDefColorTable2[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT +}; + +/** Default color table for BIFF3/BIFF4. */ +const Color spnDefColorTable3[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 8 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 16 */ EXC_PALETTE_EGA_COLORS_DARK +}; + +/** Default color table for BIFF5/BIFF7. */ +const Color spnDefColorTable5[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 8 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 16 */ EXC_PALETTE_EGA_COLORS_DARK, +/* 24 */ Color(0x8080FF), Color(0x802060), Color(0xFFFFC0), Color(0xA0E0E0), Color(0x600080), Color(0xFF8080), Color(0x0080C0), Color(0xC0C0FF), +/* 32 */ Color(0x000080), Color(0xFF00FF), Color(0xFFFF00), Color(0x00FFFF), Color(0x800080), Color(0x800000), Color(0x008080), Color(0x0000FF), +/* 40 */ Color(0x00CFFF), Color(0x69FFFF), Color(0xE0FFE0), Color(0xFFFF80), Color(0xA6CAF0), Color(0xDD9CB3), Color(0xB38FEE), Color(0xE3E3E3), +/* 48 */ Color(0x2A6FF9), Color(0x3FB8CD), Color(0x488436), Color(0x958C41), Color(0x8E5E42), Color(0xA0627A), Color(0x624FAC), Color(0x969696), +/* 56 */ Color(0x1D2FBE), Color(0x286676), Color(0x004500), Color(0x453E01), Color(0x6A2813), Color(0x85396A), Color(0x4A3285), Color(0x424242) +}; + +/** Default color table for BIFF8. */ +const Color spnDefColorTable8[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 8 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 16 */ EXC_PALETTE_EGA_COLORS_DARK, +/* 24 */ Color(0x9999FF), Color(0x993366), Color(0xFFFFCC), Color(0xCCFFFF), Color(0x660066), Color(0xFF8080), Color(0x0066CC), Color(0xCCCCFF), +/* 32 */ Color(0x000080), Color(0xFF00FF), Color(0xFFFF00), Color(0x00FFFF), Color(0x800080), Color(0x800000), Color(0x008080), Color(0x0000FF), +/* 40 */ Color(0x00CCFF), Color(0xCCFFFF), Color(0xCCFFCC), Color(0xFFFF99), Color(0x99CCFF), Color(0xFF99CC), Color(0xCC99FF), Color(0xFFCC99), +/* 48 */ Color(0x3366FF), Color(0x33CCCC), Color(0x99CC00), Color(0xFFCC00), Color(0xFF9900), Color(0xFF6600), Color(0x666699), Color(0x969696), +/* 56 */ Color(0x003366), Color(0x339966), Color(0x003300), Color(0x333300), Color(0x993300), Color(0x993366), Color(0x333399), Color(0x333333) +}; + +#undef EXC_PALETTE_EGA_COLORS_LIGHT +#undef EXC_PALETTE_EGA_COLORS_DARK + +XclDefaultPalette::XclDefaultPalette( const XclRoot& rRoot ) : + mpnColorTable( nullptr ), + mnTableSize( 0 ) +{ + const StyleSettings& rSett = Application::GetSettings().GetStyleSettings(); + mnFaceColor = rSett.GetFaceColor(); + // Don't use the system HelpBack and HelpText colours as it causes problems + // with modern gnome. This is because mnNoteText and mnNoteBack are used + // when colour indices ( instead of real colours ) are specified. + // Note: That this it is not an unusual scenario that we get the Note + // background specified as a real colour and the text specified as a + // colour index. That means the text colour would be picked from + // the system where the note background would be picked from a real colour. + // Previously the note text colour was picked from the system tooltip + // text colour, on modern gnome(e.g. 3) that tends to be 'white' with the + // default theme. + // Using the Libreoffice defaults ( instead of system specific colours + // ) lessens the chance of the one colour being an unsuitable combination + // because by default the note text is black and the note background is + // a light yellow colour ( very similar to Excel's normal defaults ) + mnNoteText = svtools::ColorConfig::GetDefaultColor( svtools::FONTCOLOR ); + mnNoteBack = svtools::ColorConfig::GetDefaultColor( svtools::CALCNOTESBACKGROUND ); + + // default colors + switch( rRoot.GetBiff() ) + { + case EXC_BIFF2: + mpnColorTable = spnDefColorTable2; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable2 ); + break; + case EXC_BIFF3: + case EXC_BIFF4: + mpnColorTable = spnDefColorTable3; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable3 ); + break; + case EXC_BIFF5: + mpnColorTable = spnDefColorTable5; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable5 ); + break; + case EXC_BIFF8: + mpnColorTable = spnDefColorTable8; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable8 ); + break; + default: + DBG_ERROR_BIFF(); + } +} + +Color XclDefaultPalette::GetDefColor( sal_uInt16 nXclIndex ) const +{ + Color nColor; + if( nXclIndex < mnTableSize ) + nColor = mpnColorTable[ nXclIndex ]; + else switch( nXclIndex ) + { + case EXC_COLOR_WINDOWTEXT3: + case EXC_COLOR_WINDOWTEXT: + case EXC_COLOR_CHWINDOWTEXT: nColor = COL_BLACK; break; + case EXC_COLOR_WINDOWBACK3: + case EXC_COLOR_WINDOWBACK: + case EXC_COLOR_CHWINDOWBACK: nColor = COL_WHITE; break; + case EXC_COLOR_BUTTONBACK: nColor = mnFaceColor; break; + case EXC_COLOR_CHBORDERAUTO: nColor = COL_BLACK; break; // TODO: really always black? + case EXC_COLOR_NOTEBACK: nColor = mnNoteBack; break; + case EXC_COLOR_NOTETEXT: nColor = mnNoteText; break; + case EXC_COLOR_FONTAUTO: nColor = COL_AUTO; break; + default: + SAL_WARN("sc", "XclDefaultPalette::GetDefColor - unknown default color index: " << nXclIndex ); + nColor = COL_AUTO; + } + return nColor; +} + +// Font Data ================================================================== + +namespace Awt = ::com::sun::star::awt; +namespace AwtFontFamily = Awt::FontFamily; +namespace AwtFontLineStyle = Awt::FontUnderline; +namespace AwtFontStrikeout = Awt::FontStrikeout; + +XclFontData::XclFontData() +{ + Clear(); +} + +XclFontData::XclFontData( const vcl::Font& rFont ) +{ + Clear(); + FillFromVclFont( rFont ); +} + +XclFontData::XclFontData( const SvxFont& rFont ) +{ + FillFromSvxFont( rFont ); +} + +void XclFontData::Clear() +{ + maName.clear(); + maStyle.clear(); + maColor = COL_AUTO; + mnHeight = 0; + mnWeight = EXC_FONTWGHT_DONTKNOW; + mnEscapem = EXC_FONTESC_NONE; + mnFamily = EXC_FONTFAM_SYSTEM; + mnCharSet = EXC_FONTCSET_ANSI_LATIN; + mnUnderline = EXC_FONTUNDERL_NONE; + mbItalic = mbStrikeout = mbOutline = mbShadow = false; +} + +void XclFontData::FillFromVclFont( const vcl::Font& rFont ) +{ + maName = XclTools::GetXclFontName( rFont.GetFamilyName() ); // substitute with MS fonts + maStyle.clear(); + maColor = rFont.GetColor(); + SetScUnderline( rFont.GetUnderline() ); + mnEscapem = EXC_FONTESC_NONE; + SetScHeight( rFont.GetFontSize().Height() ); + SetScWeight( rFont.GetWeight() ); + SetScFamily( rFont.GetFamilyType() ); + SetFontEncoding( rFont.GetCharSet() ); + SetScPosture( rFont.GetItalic() ); + SetScStrikeout( rFont.GetStrikeout() ); + mbOutline = rFont.IsOutline(); + mbShadow = rFont.IsShadow(); +} + +void XclFontData::FillFromSvxFont( const SvxFont& rFont ) +{ + FillFromVclFont( rFont ); + SetScEscapement( rFont.GetEscapement() ); +} + +// *** conversion of VCL/SVX constants *** ------------------------------------ + +FontFamily XclFontData::GetScFamily( rtl_TextEncoding eDefTextEnc ) const +{ + FontFamily eScFamily; + // ! format differs from Windows documentation: family is in lower nibble, pitch unknown + switch( mnFamily & 0x0F ) + { + case EXC_FONTFAM_ROMAN: eScFamily = FAMILY_ROMAN; break; + case EXC_FONTFAM_SWISS: eScFamily = FAMILY_SWISS; break; + case EXC_FONTFAM_MODERN: eScFamily = FAMILY_MODERN; break; + case EXC_FONTFAM_SCRIPT: eScFamily = FAMILY_SCRIPT; break; + case EXC_FONTFAM_DECORATIVE: eScFamily = FAMILY_DECORATIVE; break; + default: + eScFamily = + ((eDefTextEnc == RTL_TEXTENCODING_APPLE_ROMAN) && + (maName.equalsIgnoreAsciiCase( "Geneva" ) || maName.equalsIgnoreAsciiCase( "Chicago" ))) ? + FAMILY_SWISS : FAMILY_DONTKNOW; + } + return eScFamily; +} + +rtl_TextEncoding XclFontData::GetFontEncoding() const +{ + // convert Windows character set to text encoding identifier + return rtl_getTextEncodingFromWindowsCharset( mnCharSet ); +} + +FontItalic XclFontData::GetScPosture() const +{ + return mbItalic ? ITALIC_NORMAL : ITALIC_NONE; +} + +FontWeight XclFontData::GetScWeight() const +{ + FontWeight eScWeight; + + if( !mnWeight ) eScWeight = WEIGHT_DONTKNOW; + else if( mnWeight < 150 ) eScWeight = WEIGHT_THIN; + else if( mnWeight < 250 ) eScWeight = WEIGHT_ULTRALIGHT; + else if( mnWeight < 325 ) eScWeight = WEIGHT_LIGHT; + else if( mnWeight < 375 ) eScWeight = WEIGHT_SEMILIGHT; + else if( mnWeight < 450 ) eScWeight = WEIGHT_NORMAL; + else if( mnWeight < 550 ) eScWeight = WEIGHT_MEDIUM; + else if( mnWeight < 650 ) eScWeight = WEIGHT_SEMIBOLD; + else if( mnWeight < 750 ) eScWeight = WEIGHT_BOLD; + else if( mnWeight < 850 ) eScWeight = WEIGHT_ULTRABOLD; + else eScWeight = WEIGHT_BLACK; + + return eScWeight; +} + +FontLineStyle XclFontData::GetScUnderline() const +{ + FontLineStyle eScUnderl = LINESTYLE_NONE; + switch( mnUnderline ) + { + case EXC_FONTUNDERL_SINGLE: + case EXC_FONTUNDERL_SINGLE_ACC: eScUnderl = LINESTYLE_SINGLE; break; + case EXC_FONTUNDERL_DOUBLE: + case EXC_FONTUNDERL_DOUBLE_ACC: eScUnderl = LINESTYLE_DOUBLE; break; + } + return eScUnderl; +} + +SvxEscapement XclFontData::GetScEscapement() const +{ + SvxEscapement eScEscapem = SvxEscapement::Off; + switch( mnEscapem ) + { + case EXC_FONTESC_SUPER: eScEscapem = SvxEscapement::Superscript; break; + case EXC_FONTESC_SUB: eScEscapem = SvxEscapement::Subscript; break; + } + return eScEscapem; +} + +FontStrikeout XclFontData::GetScStrikeout() const +{ + return mbStrikeout ? STRIKEOUT_SINGLE : STRIKEOUT_NONE; +} + +void XclFontData::SetScHeight( sal_Int32 nTwips ) +{ + mnHeight = static_cast< sal_uInt16 >( ::std::min( nTwips, static_cast<sal_Int32>(0x7FFFL) ) ); +} + +void XclFontData::SetScFamily( FontFamily eScFamily ) +{ + switch( eScFamily ) + { + case FAMILY_DONTKNOW: mnFamily = EXC_FONTFAM_DONTKNOW; break; + case FAMILY_DECORATIVE: mnFamily = EXC_FONTFAM_DECORATIVE; break; + case FAMILY_MODERN: mnFamily = EXC_FONTFAM_MODERN; break; + case FAMILY_ROMAN: mnFamily = EXC_FONTFAM_ROMAN; break; + case FAMILY_SCRIPT: mnFamily = EXC_FONTFAM_SCRIPT; break; + case FAMILY_SWISS: mnFamily = EXC_FONTFAM_SWISS; break; + case FAMILY_SYSTEM: mnFamily = EXC_FONTFAM_SYSTEM; break; + default: + OSL_FAIL( "XclFontData::SetScFamily - unknown font family" ); + mnFamily = EXC_FONTFAM_DONTKNOW; + } +} + +void XclFontData::SetFontEncoding( rtl_TextEncoding eFontEnc ) +{ + // convert text encoding identifier to Windows character set + mnCharSet = rtl_getBestWindowsCharsetFromTextEncoding( eFontEnc ); +} + +void XclFontData::SetScPosture( FontItalic eScPosture ) +{ + mbItalic = (eScPosture == ITALIC_OBLIQUE) || (eScPosture == ITALIC_NORMAL); +} + +void XclFontData::SetScWeight( FontWeight eScWeight ) +{ + switch( eScWeight ) + { + case WEIGHT_DONTKNOW: mnWeight = EXC_FONTWGHT_DONTKNOW; break; + case WEIGHT_THIN: mnWeight = EXC_FONTWGHT_THIN; break; + case WEIGHT_ULTRALIGHT: mnWeight = EXC_FONTWGHT_ULTRALIGHT; break; + case WEIGHT_LIGHT: mnWeight = EXC_FONTWGHT_LIGHT; break; + case WEIGHT_SEMILIGHT: mnWeight = EXC_FONTWGHT_SEMILIGHT; break; + case WEIGHT_NORMAL: mnWeight = EXC_FONTWGHT_NORMAL; break; + case WEIGHT_MEDIUM: mnWeight = EXC_FONTWGHT_MEDIUM; break; + case WEIGHT_SEMIBOLD: mnWeight = EXC_FONTWGHT_SEMIBOLD; break; + case WEIGHT_BOLD: mnWeight = EXC_FONTWGHT_BOLD; break; + case WEIGHT_ULTRABOLD: mnWeight = EXC_FONTWGHT_ULTRABOLD; break; + case WEIGHT_BLACK: mnWeight = EXC_FONTWGHT_BLACK; break; + default: mnWeight = EXC_FONTWGHT_NORMAL; + } +} + +void XclFontData::SetScUnderline( FontLineStyle eScUnderl ) +{ + switch( eScUnderl ) + { + case LINESTYLE_NONE: + case LINESTYLE_DONTKNOW: mnUnderline = EXC_FONTUNDERL_NONE; break; + case LINESTYLE_DOUBLE: + case LINESTYLE_DOUBLEWAVE: mnUnderline = EXC_FONTUNDERL_DOUBLE; break; + default: mnUnderline = EXC_FONTUNDERL_SINGLE; + } +} + +void XclFontData::SetScEscapement( short nScEscapem ) +{ + if( nScEscapem > 0 ) + mnEscapem = EXC_FONTESC_SUPER; + else if( nScEscapem < 0 ) + mnEscapem = EXC_FONTESC_SUB; + else + mnEscapem = EXC_FONTESC_NONE; +} + +void XclFontData::SetScStrikeout( FontStrikeout eScStrikeout ) +{ + mbStrikeout = + (eScStrikeout == STRIKEOUT_SINGLE) || (eScStrikeout == STRIKEOUT_DOUBLE) || + (eScStrikeout == STRIKEOUT_BOLD) || (eScStrikeout == STRIKEOUT_SLASH) || + (eScStrikeout == STRIKEOUT_X); +} + +// *** conversion of API constants *** ---------------------------------------- + +float XclFontData::GetApiHeight() const +{ + return o3tl::convert<double>(mnHeight, o3tl::Length::twip, o3tl::Length::pt); +} + +sal_Int16 XclFontData::GetApiFamily() const +{ + sal_Int16 nApiFamily = AwtFontFamily::DONTKNOW; + switch( mnFamily ) + { + case FAMILY_DECORATIVE: nApiFamily = AwtFontFamily::DECORATIVE; break; + case FAMILY_MODERN: nApiFamily = AwtFontFamily::MODERN; break; + case FAMILY_ROMAN: nApiFamily = AwtFontFamily::ROMAN; break; + case FAMILY_SCRIPT: nApiFamily = AwtFontFamily::SCRIPT; break; + case FAMILY_SWISS: nApiFamily = AwtFontFamily::SWISS; break; + case FAMILY_SYSTEM: nApiFamily = AwtFontFamily::SYSTEM; break; + } + return nApiFamily; +} + +sal_Int16 XclFontData::GetApiFontEncoding() const +{ + // API constants are equal to rtl_TextEncoding constants + return static_cast< sal_Int16 >( GetFontEncoding() ); +} + +Awt::FontSlant XclFontData::GetApiPosture() const +{ + return mbItalic ? Awt::FontSlant_ITALIC : Awt::FontSlant_NONE; +} + +float XclFontData::GetApiWeight() const +{ + return vcl::unohelper::ConvertFontWeight( GetScWeight() ); +} + +sal_Int16 XclFontData::GetApiUnderline() const +{ + sal_Int16 nApiUnderl = AwtFontLineStyle::NONE; + switch( mnUnderline ) + { + case EXC_FONTUNDERL_SINGLE: + case EXC_FONTUNDERL_SINGLE_ACC: nApiUnderl = AwtFontLineStyle::SINGLE; break; + case EXC_FONTUNDERL_DOUBLE: + case EXC_FONTUNDERL_DOUBLE_ACC: nApiUnderl = AwtFontLineStyle::DOUBLE; break; + } + return nApiUnderl; +} + +sal_Int16 XclFontData::GetApiEscapement() const +{ + sal_Int16 nApiEscapem = 0; + switch( mnEscapem ) + { + case EXC_FONTESC_SUPER: nApiEscapem = 33; break; + case EXC_FONTESC_SUB: nApiEscapem = -33; break; + } + return nApiEscapem; +} + +sal_Int16 XclFontData::GetApiStrikeout() const +{ + return mbStrikeout ? AwtFontStrikeout::SINGLE : AwtFontStrikeout::NONE; +} + +void XclFontData::SetApiHeight( float fPoint ) +{ + mnHeight = std::min(o3tl::convert(fPoint, o3tl::Length::pt, o3tl::Length::twip) + 0.5, 32767.0); +} + +void XclFontData::SetApiFamily( sal_Int16 nApiFamily ) +{ + switch( nApiFamily ) + { + case AwtFontFamily::DECORATIVE: mnFamily = FAMILY_DECORATIVE; break; + case AwtFontFamily::MODERN: mnFamily = FAMILY_MODERN; break; + case AwtFontFamily::ROMAN: mnFamily = FAMILY_ROMAN; break; + case AwtFontFamily::SCRIPT: mnFamily = FAMILY_SCRIPT; break; + case AwtFontFamily::SWISS: mnFamily = FAMILY_SWISS; break; + case AwtFontFamily::SYSTEM: mnFamily = FAMILY_SYSTEM; break; + default: mnFamily = FAMILY_DONTKNOW; + } +} + +void XclFontData::SetApiPosture( Awt::FontSlant eApiPosture ) +{ + mbItalic = + (eApiPosture == Awt::FontSlant_OBLIQUE) || + (eApiPosture == Awt::FontSlant_ITALIC) || + (eApiPosture == Awt::FontSlant_REVERSE_OBLIQUE) || + (eApiPosture == Awt::FontSlant_REVERSE_ITALIC); +} + +void XclFontData::SetApiWeight( float fApiWeight ) +{ + SetScWeight( vcl::unohelper::ConvertFontWeight( fApiWeight ) ); +} + +void XclFontData::SetApiUnderline( sal_Int16 nApiUnderl ) +{ + switch( nApiUnderl ) + { + case AwtFontLineStyle::NONE: + case AwtFontLineStyle::DONTKNOW: mnUnderline = EXC_FONTUNDERL_NONE; break; + case AwtFontLineStyle::DOUBLE: + case AwtFontLineStyle::DOUBLEWAVE: mnUnderline = EXC_FONTUNDERL_DOUBLE; break; + default: mnUnderline = EXC_FONTUNDERL_SINGLE; + } +} + +void XclFontData::SetApiEscapement( sal_Int16 nApiEscapem ) +{ + if( nApiEscapem > 0 ) + mnEscapem = EXC_FONTESC_SUPER; + else if( nApiEscapem < 0 ) + mnEscapem = EXC_FONTESC_SUB; + else + mnEscapem = EXC_FONTESC_NONE; +} + +void XclFontData::SetApiStrikeout( sal_Int16 nApiStrikeout ) +{ + mbStrikeout = + (nApiStrikeout != AwtFontStrikeout::NONE) && + (nApiStrikeout != AwtFontStrikeout::DONTKNOW); +} + +bool operator==( const XclFontData& rLeft, const XclFontData& rRight ) +{ + return + (rLeft.mnHeight == rRight.mnHeight) && + (rLeft.mnWeight == rRight.mnWeight) && + (rLeft.mnUnderline == rRight.mnUnderline) && + (rLeft.maColor == rRight.maColor) && + (rLeft.mnEscapem == rRight.mnEscapem) && + (rLeft.mnFamily == rRight.mnFamily) && + (rLeft.mnCharSet == rRight.mnCharSet) && + (rLeft.mbItalic == rRight.mbItalic) && + (rLeft.mbStrikeout == rRight.mbStrikeout) && + (rLeft.mbOutline == rRight.mbOutline) && + (rLeft.mbShadow == rRight.mbShadow) && + (rLeft.maName == rRight.maName); +} + +namespace { + +/** Property names for common font settings. */ +const char *const sppcPropNamesChCommon[] = +{ + "CharUnderline", "CharStrikeout", "CharColor", "CharContoured", "CharShadowed", nullptr +}; +/** Property names for Western font settings. */ +const char *const sppcPropNamesChWstrn[] = +{ + "CharFontName", "CharHeight", "CharPosture", "CharWeight", nullptr +}; +/** Property names for Asian font settings. */ +const char *const sppcPropNamesChAsian[] = +{ + "CharFontNameAsian", "CharHeightAsian", "CharPostureAsian", "CharWeightAsian", nullptr +}; +/** Property names for Complex font settings. */ +const char *const sppcPropNamesChCmplx[] = +{ + "CharFontNameComplex", "CharHeightComplex", "CharPostureComplex", "CharWeightComplex", nullptr +}; +/** Property names for escapement. */ +const char *const sppcPropNamesChEscapement[] = +{ + "CharEscapement", "CharEscapementHeight", nullptr +}; +const sal_Int8 EXC_API_ESC_HEIGHT = 58; /// Default escapement font height. + +/** Property names for Western font settings without font name. */ +const char *const *const sppcPropNamesChWstrnNoName = sppcPropNamesChWstrn + 1; +/** Property names for Asian font settings without font name. */ +const char *const *const sppcPropNamesChAsianNoName = sppcPropNamesChAsian + 1; +/** Property names for Complex font settings without font name. */ +const char *const *const sppcPropNamesChCmplxNoName = sppcPropNamesChCmplx + 1; + +/** Property names for font settings in form controls. */ +const char *const sppcPropNamesControl[] = +{ + "FontName", "FontFamily", "FontCharset", "FontHeight", "FontSlant", + "FontWeight", "FontLineStyle", "FontStrikeout", "TextColor", nullptr +}; + +/** Inserts all passed API font settings into the font data object. */ +void lclSetApiFontSettings( XclFontData& rFontData, + const OUString& rApiFontName, float fApiHeight, float fApiWeight, + Awt::FontSlant eApiPosture, sal_Int16 nApiUnderl, sal_Int16 nApiStrikeout ) +{ + rFontData.maName = XclTools::GetXclFontName( rApiFontName ); + rFontData.SetApiHeight( fApiHeight ); + rFontData.SetApiWeight( fApiWeight ); + rFontData.SetApiPosture( eApiPosture ); + rFontData.SetApiUnderline( nApiUnderl ); + rFontData.SetApiStrikeout( nApiStrikeout ); +} + +/** Writes script dependent properties to a font property set helper. */ +void lclWriteChartFont( ScfPropertySet& rPropSet, + ScfPropSetHelper& rHlpName, ScfPropSetHelper& rHlpNoName, + const XclFontData& rFontData, bool bHasFontName ) +{ + // select the font helper + ScfPropSetHelper& rPropSetHlp = bHasFontName ? rHlpName : rHlpNoName; + // initialize the font helper (must be called before writing any properties) + rPropSetHlp.InitializeWrite(); + // write font name + if( bHasFontName ) + rPropSetHlp << rFontData.maName; + // write remaining properties + rPropSetHlp << rFontData.GetApiHeight() << rFontData.GetApiPosture() << rFontData.GetApiWeight(); + // write properties to property set + rPropSetHlp.WriteToPropertySet( rPropSet ); +} + +} // namespace + +XclFontPropSetHelper::XclFontPropSetHelper() : + maHlpChCommon( sppcPropNamesChCommon ), + maHlpChWstrn( sppcPropNamesChWstrn ), + maHlpChAsian( sppcPropNamesChAsian ), + maHlpChCmplx( sppcPropNamesChCmplx ), + maHlpChWstrnNoName( sppcPropNamesChWstrnNoName ), + maHlpChAsianNoName( sppcPropNamesChAsianNoName ), + maHlpChCmplxNoName( sppcPropNamesChCmplxNoName ), + maHlpChEscapement( sppcPropNamesChEscapement ), + maHlpControl( sppcPropNamesControl ) +{ +} + +void XclFontPropSetHelper::ReadFontProperties( XclFontData& rFontData, + const ScfPropertySet& rPropSet, XclFontPropSetType eType, sal_Int16 nScript ) +{ + switch( eType ) + { + case EXC_FONTPROPSET_CHART: + { + OUString aApiFontName; + float fApiHeight, fApiWeight; + sal_Int16 nApiUnderl = 0, nApiStrikeout = 0; + Awt::FontSlant eApiPosture; + + // read script type dependent properties + ScfPropSetHelper& rPropSetHlp = GetChartHelper( nScript ); + rPropSetHlp.ReadFromPropertySet( rPropSet ); + rPropSetHlp >> aApiFontName >> fApiHeight >> eApiPosture >> fApiWeight; + // read common properties + maHlpChCommon.ReadFromPropertySet( rPropSet ); + maHlpChCommon >> nApiUnderl + >> nApiStrikeout + >> rFontData.maColor + >> rFontData.mbOutline + >> rFontData.mbShadow; + + // convert API property values to Excel settings + lclSetApiFontSettings( rFontData, aApiFontName, + fApiHeight, fApiWeight, eApiPosture, nApiUnderl, nApiStrikeout ); + + // font escapement + sal_Int16 nApiEscapement = 0; + sal_Int8 nApiEscHeight = 0; + maHlpChEscapement.ReadFromPropertySet( rPropSet ); + maHlpChEscapement.ReadFromPropertySet( rPropSet ); + maHlpChEscapement.ReadFromPropertySet( rPropSet ); + maHlpChEscapement >> nApiEscapement >> nApiEscHeight; + rFontData.SetApiEscapement( nApiEscapement ); + } + break; + + case EXC_FONTPROPSET_CONTROL: + { + OUString aApiFontName; + float fApiHeight(0.0), fApiWeight(0.0); + sal_Int16 nApiFamily(0), nApiCharSet(0), nApiPosture(0), nApiUnderl(0), nApiStrikeout(0); + + // read font properties + maHlpControl.ReadFromPropertySet( rPropSet ); + maHlpControl >> aApiFontName + >> nApiFamily + >> nApiCharSet + >> fApiHeight + >> nApiPosture + >> fApiWeight + >> nApiUnderl + >> nApiStrikeout + >> rFontData.maColor; + + // convert API property values to Excel settings + Awt::FontSlant eApiPosture = static_cast< Awt::FontSlant >( nApiPosture ); + lclSetApiFontSettings( rFontData, aApiFontName, + fApiHeight, fApiWeight, eApiPosture, nApiUnderl, nApiStrikeout ); + rFontData.SetApiFamily( nApiFamily ); + rFontData.SetFontEncoding( nApiCharSet ); + } + break; + } +} + +void XclFontPropSetHelper::WriteFontProperties( + ScfPropertySet& rPropSet, XclFontPropSetType eType, + const XclFontData& rFontData, bool bHasWstrn, bool bHasAsian, bool bHasCmplx, + const Color* pFontColor ) +{ + switch( eType ) + { + case EXC_FONTPROPSET_CHART: + { + // write common properties + maHlpChCommon.InitializeWrite(); + const Color& rColor = pFontColor ? *pFontColor : rFontData.maColor; + maHlpChCommon << rFontData.GetApiUnderline() + << rFontData.GetApiStrikeout() + << rColor + << rFontData.mbOutline + << rFontData.mbShadow; + maHlpChCommon.WriteToPropertySet( rPropSet ); + + // write script type dependent properties + lclWriteChartFont( rPropSet, maHlpChWstrn, maHlpChWstrnNoName, rFontData, bHasWstrn ); + lclWriteChartFont( rPropSet, maHlpChAsian, maHlpChAsianNoName, rFontData, bHasAsian ); + lclWriteChartFont( rPropSet, maHlpChCmplx, maHlpChCmplxNoName, rFontData, bHasCmplx ); + + // font escapement + if( rFontData.GetScEscapement() != SvxEscapement::Off ) + { + maHlpChEscapement.InitializeWrite(); + maHlpChEscapement << rFontData.GetApiEscapement() << EXC_API_ESC_HEIGHT; + maHlpChEscapement.WriteToPropertySet( rPropSet ); + } + } + break; + + case EXC_FONTPROPSET_CONTROL: + { + maHlpControl.InitializeWrite(); + maHlpControl << rFontData.maName + << rFontData.GetApiFamily() + << rFontData.GetApiFontEncoding() + << static_cast< sal_Int16 >( rFontData.GetApiHeight() + 0.5 ) + << rFontData.GetApiPosture() + << rFontData.GetApiWeight() + << rFontData.GetApiUnderline() + << rFontData.GetApiStrikeout() + << rFontData.maColor; + maHlpControl.WriteToPropertySet( rPropSet ); + } + break; + } +} + +ScfPropSetHelper& XclFontPropSetHelper::GetChartHelper( sal_Int16 nScript ) +{ + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + switch( nScript ) + { + case ApiScriptType::LATIN: return maHlpChWstrn; + case ApiScriptType::ASIAN: return maHlpChAsian; + case ApiScriptType::COMPLEX: return maHlpChCmplx; + default: OSL_FAIL( "XclFontPropSetHelper::GetChartHelper - unknown script type" ); + } + return maHlpChWstrn; +} + +// Number formats ============================================================= + +namespace { + +/** Special number format index describing a reused format. */ +const NfIndexTableOffset PRV_NF_INDEX_REUSE = NF_INDEX_TABLE_ENTRIES; + +/** German primary language not defined, LANGUAGE_GERMAN belongs to Germany. */ +constexpr LanguageType PRV_LANGUAGE_GERMAN_PRIM = primary(LANGUAGE_GERMAN); +/** French primary language not defined, LANGUAGE_FRENCH belongs to France. */ +constexpr LanguageType PRV_LANGUAGE_FRENCH_PRIM = primary(LANGUAGE_FRENCH); +/** Parent language identifier for Asian languages. */ +constexpr LanguageType PRV_LANGUAGE_ASIAN_PRIM = primary(LANGUAGE_CHINESE); + +/** Stores the number format used in Calc for an Excel built-in number format. */ +struct XclBuiltInFormat +{ + sal_uInt16 mnXclNumFmt; /// Excel built-in index. + const char* mpFormat; /// Format string, may be 0 (meOffset used then). + NfIndexTableOffset meOffset; /// SvNumberFormatter format index, if mpFormat==0. + sal_uInt16 mnXclReuseFmt; /// Use this Excel format, if meOffset==PRV_NF_INDEX_REUSE. +}; + +/** Defines a literal Excel built-in number format. */ +#define EXC_NUMFMT_STRING( nXclNumFmt, pcUtf8 ) \ + { nXclNumFmt, pcUtf8, NF_NUMBER_STANDARD, 0 } + +/** Defines an Excel built-in number format that maps to an own built-in format. */ +#define EXC_NUMFMT_OFFSET( nXclNumFmt, eOffset ) \ + { nXclNumFmt, nullptr, eOffset, 0 } + +/** Defines an Excel built-in number format that is the same as the specified. */ +#define EXC_NUMFMT_REUSE( nXclNumFmt, nXclReuse ) \ + { nXclNumFmt, nullptr, PRV_NF_INDEX_REUSE, nXclReuse } + +/** Terminates an Excel built-in number format table. */ +#define EXC_NUMFMT_ENDTABLE() \ + { EXC_FORMAT_NOTFOUND, nullptr, NF_NUMBER_STANDARD, 0 } + +// Currency unit characters +#define UTF8_BAHT "\340\270\277" +#define UTF8_EURO "\342\202\254" +#define UTF8_POUND_UK "\302\243" +#define UTF8_SHEQEL "\342\202\252" +#define UTF8_WON "\357\277\246" +#define UTF8_YEN_CS "\357\277\245" +#define UTF8_YEN_JP "\302\245" + +// Japanese/Chinese date/time characters +#define UTF8_CJ_YEAR "\345\271\264" +#define UTF8_CJ_MON "\346\234\210" +#define UTF8_CJ_DAY "\346\227\245" +#define UTF8_CJ_HOUR "\346\231\202" +#define UTF8_CJ_MIN "\345\210\206" +#define UTF8_CJ_SEC "\347\247\222" + +// Chinese Simplified date/time characters +#define UTF8_CS_HOUR "\346\227\266" + +// Korean date/time characters +#define UTF8_KO_YEAR "\353\205\204" +#define UTF8_KO_MON "\354\233\224" +#define UTF8_KO_DAY "\354\235\274" +#define UTF8_KO_HOUR "\354\213\234" +#define UTF8_KO_MIN "\353\266\204" +#define UTF8_KO_SEC "\354\264\210" + +/** Default number format table. Last parent of all other tables, used for unknown languages. */ +const XclBuiltInFormat spBuiltInFormats_DONTKNOW[] = +{ + EXC_NUMFMT_OFFSET( 0, NF_NUMBER_STANDARD ), // General + EXC_NUMFMT_OFFSET( 1, NF_NUMBER_INT ), // 0 + EXC_NUMFMT_OFFSET( 2, NF_NUMBER_DEC2 ), // 0.00 + EXC_NUMFMT_OFFSET( 3, NF_NUMBER_1000INT ), // #,##0 + EXC_NUMFMT_OFFSET( 4, NF_NUMBER_1000DEC2 ), // #,##0.00 + // 5...8 contained in file + EXC_NUMFMT_OFFSET( 9, NF_PERCENT_INT ), // 0% + EXC_NUMFMT_OFFSET( 10, NF_PERCENT_DEC2 ), // 0.00% + EXC_NUMFMT_OFFSET( 11, NF_SCIENTIFIC_000E00 ), // 0.00E+00 + EXC_NUMFMT_OFFSET( 12, NF_FRACTION_1D ), // # ?/? + EXC_NUMFMT_OFFSET( 13, NF_FRACTION_2D ), // # ??/?? + + // 14...22 date and time formats + EXC_NUMFMT_OFFSET( 14, NF_DATE_SYS_DDMMYYYY ), + EXC_NUMFMT_OFFSET( 15, NF_DATE_SYS_DMMMYY ), + EXC_NUMFMT_OFFSET( 16, NF_DATE_SYS_DDMMM ), + EXC_NUMFMT_OFFSET( 17, NF_DATE_SYS_MMYY ), + EXC_NUMFMT_OFFSET( 18, NF_TIME_HHMMAMPM ), + EXC_NUMFMT_OFFSET( 19, NF_TIME_HHMMSSAMPM ), + EXC_NUMFMT_OFFSET( 20, NF_TIME_HHMM ), + EXC_NUMFMT_OFFSET( 21, NF_TIME_HHMMSS ), + EXC_NUMFMT_OFFSET( 22, NF_DATETIME_SYSTEM_SHORT_HHMM ), + + // 23...36 international formats + EXC_NUMFMT_REUSE( 23, 0 ), + EXC_NUMFMT_REUSE( 24, 0 ), + EXC_NUMFMT_REUSE( 25, 0 ), + EXC_NUMFMT_REUSE( 26, 0 ), + EXC_NUMFMT_REUSE( 27, 14 ), + EXC_NUMFMT_REUSE( 28, 14 ), + EXC_NUMFMT_REUSE( 29, 14 ), + EXC_NUMFMT_REUSE( 30, 14 ), + EXC_NUMFMT_REUSE( 31, 14 ), + EXC_NUMFMT_REUSE( 32, 21 ), + EXC_NUMFMT_REUSE( 33, 21 ), + EXC_NUMFMT_REUSE( 34, 21 ), + EXC_NUMFMT_REUSE( 35, 21 ), + EXC_NUMFMT_REUSE( 36, 14 ), + + // 37...44 accounting formats + // 41...44 contained in file + EXC_NUMFMT_STRING( 37, "#,##0;-#,##0" ), + EXC_NUMFMT_STRING( 38, "#,##0;[RED]-#,##0" ), + EXC_NUMFMT_STRING( 39, "#,##0.00;-#,##0.00" ), + EXC_NUMFMT_STRING( 40, "#,##0.00;[RED]-#,##0.00" ), + + // 45...49 more special formats + EXC_NUMFMT_STRING( 45, "mm:ss" ), + EXC_NUMFMT_STRING( 46, "[h]:mm:ss" ), + EXC_NUMFMT_STRING( 47, "mm:ss.0" ), + EXC_NUMFMT_STRING( 48, "##0.0E+0" ), + EXC_NUMFMT_OFFSET( 49, NF_TEXT ), + + // 50...81 international formats + EXC_NUMFMT_REUSE( 50, 14 ), + EXC_NUMFMT_REUSE( 51, 14 ), + EXC_NUMFMT_REUSE( 52, 14 ), + EXC_NUMFMT_REUSE( 53, 14 ), + EXC_NUMFMT_REUSE( 54, 14 ), + EXC_NUMFMT_REUSE( 55, 14 ), + EXC_NUMFMT_REUSE( 56, 14 ), + EXC_NUMFMT_REUSE( 57, 14 ), + EXC_NUMFMT_REUSE( 58, 14 ), + EXC_NUMFMT_REUSE( 59, 1 ), + EXC_NUMFMT_REUSE( 60, 2 ), + EXC_NUMFMT_REUSE( 61, 3 ), + EXC_NUMFMT_REUSE( 62, 4 ), + EXC_NUMFMT_REUSE( 67, 9 ), + EXC_NUMFMT_REUSE( 68, 10 ), + EXC_NUMFMT_REUSE( 69, 12 ), + EXC_NUMFMT_REUSE( 70, 13 ), + EXC_NUMFMT_REUSE( 71, 14 ), + EXC_NUMFMT_REUSE( 72, 14 ), + EXC_NUMFMT_REUSE( 73, 15 ), + EXC_NUMFMT_REUSE( 74, 16 ), + EXC_NUMFMT_REUSE( 75, 17 ), + EXC_NUMFMT_REUSE( 76, 20 ), + EXC_NUMFMT_REUSE( 77, 21 ), + EXC_NUMFMT_REUSE( 78, 22 ), + EXC_NUMFMT_REUSE( 79, 45 ), + EXC_NUMFMT_REUSE( 80, 46 ), + EXC_NUMFMT_REUSE( 81, 47 ), + + // 82...163 not used, must not occur in a file (Excel may crash) + + EXC_NUMFMT_ENDTABLE() +}; + +// ENGLISH -------------------------------------------------------------------- + +/** Base table for English locales. */ +const XclBuiltInFormat spBuiltInFormats_ENGLISH[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY hh:mm" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_UK[] = +{ + EXC_NUMFMT_STRING( 63, UTF8_POUND_UK "#,##0;-" UTF8_POUND_UK "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_POUND_UK "#,##0;[RED]-" UTF8_POUND_UK "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_POUND_UK "#,##0.00;-" UTF8_POUND_UK "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_POUND_UK "#,##0.00;[RED]-" UTF8_POUND_UK "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_EIRE[] = +{ + EXC_NUMFMT_STRING( 63, UTF8_EURO "#,##0;-" UTF8_EURO "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_EURO "#,##0;[RED]-" UTF8_EURO "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_EURO "#,##0.00;-" UTF8_EURO "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_EURO "#,##0.00;[RED]-" UTF8_EURO "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_US[] = +{ + EXC_NUMFMT_STRING( 14, "M/D/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "M/D/YYYY h:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0_);(#,##0)" ), + EXC_NUMFMT_STRING( 38, "#,##0_);[RED](#,##0)" ), + EXC_NUMFMT_STRING( 39, "#,##0.00_);(#,##0.00)" ), + EXC_NUMFMT_STRING( 40, "#,##0.00_);[RED](#,##0.00)" ), + EXC_NUMFMT_STRING( 63, "$#,##0_);($#,##0)" ), + EXC_NUMFMT_STRING( 64, "$#,##0_);[RED]($#,##0)" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00_);($#,##0.00)" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00_);[RED]($#,##0.00)" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_CAN[] = +{ + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY h:mm" ), + EXC_NUMFMT_STRING( 63, "$#,##0;-$#,##0" ), + EXC_NUMFMT_STRING( 64, "$#,##0;[RED]-$#,##0" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00;-$#,##0.00" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00;[RED]-$#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_AUS[] = +{ + EXC_NUMFMT_STRING( 14, "D/MM/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "D/MM/YYYY h:mm" ), + EXC_NUMFMT_STRING( 63, "$#,##0;-$#,##0" ), + EXC_NUMFMT_STRING( 64, "$#,##0;[RED]-$#,##0" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00;-$#,##0.00" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00;[RED]-$#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_SAFRICA[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY/MM/DD" ), + EXC_NUMFMT_OFFSET( 18, NF_TIME_HHMMAMPM ), + EXC_NUMFMT_OFFSET( 19, NF_TIME_HHMMSSAMPM ), + EXC_NUMFMT_STRING( 22, "YYYY/MM/DD hh:mm" ), + EXC_NUMFMT_STRING( 63, "\\R #,##0;\\R -#,##0" ), + EXC_NUMFMT_STRING( 64, "\\R #,##0;[RED]\\R -#,##0" ), + EXC_NUMFMT_STRING( 65, "\\R #,##0.00;\\R -#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\\R #,##0.00;[RED]\\R -#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// FRENCH --------------------------------------------------------------------- + +/** Base table for French locales. */ +const XclBuiltInFormat spBuiltInFormats_FRENCH[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_FRANCE[] = +{ + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0\\ _" UTF8_EURO ";-#,##0\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0\\ _" UTF8_EURO ";[RED]-#,##0\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00\\ _" UTF8_EURO ";-#,##0.00\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00\\ _" UTF8_EURO ";[RED]-#,##0.00\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0\\ " UTF8_EURO ";-#,##0\\ " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0\\ " UTF8_EURO ";[RED]-#,##0\\ " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00\\ " UTF8_EURO ";-#,##0.00\\ " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00\\ " UTF8_EURO ";[RED]-#,##0.00\\ " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_CANADIAN[] = +{ + EXC_NUMFMT_STRING( 22, "YYYY-MM-DD hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0\\ _$_-;#,##0\\ _$-" ), + EXC_NUMFMT_STRING( 38, "#,##0\\ _$_-;[RED]#,##0\\ _$-" ), + EXC_NUMFMT_STRING( 39, "#,##0.00\\ _$_-;#,##0.00\\ _$-" ), + EXC_NUMFMT_STRING( 40, "#,##0.00\\ _$_-;[RED]#,##0.00\\ _$-" ), + EXC_NUMFMT_STRING( 63, "#,##0\\ $_-;#,##0\\ $-" ), + EXC_NUMFMT_STRING( 64, "#,##0\\ $_-;[RED]#,##0\\ $-" ), + EXC_NUMFMT_STRING( 65, "#,##0.00\\ $_-;#,##0.00\\ $-" ), + EXC_NUMFMT_STRING( 66, "#,##0.00\\ $_-;[RED]#,##0.00\\ $-" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_SWISS[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 22, "DD.MM.YYYY hh:mm" ), + EXC_NUMFMT_STRING( 63, "\"SFr. \"#,##0;\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"SFr. \"#,##0;[RED]\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"SFr. \"#,##0.00;\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"SFr. \"#,##0.00;[RED]\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_BELGIAN[] = +{ + EXC_NUMFMT_STRING( 14, "D/MM/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "D/MM/YYYY h:mm" ), + EXC_NUMFMT_ENDTABLE() +}; + +// GERMAN --------------------------------------------------------------------- + +/** Base table for German locales. */ +const XclBuiltInFormat spBuiltInFormats_GERMAN[] = +{ + EXC_NUMFMT_STRING( 15, "DD. MMM YY" ), + EXC_NUMFMT_STRING( 16, "DD. MMM" ), + EXC_NUMFMT_STRING( 17, "MMM YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "DD.MM.YYYY hh:mm" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_GERMANY[] = +{ + EXC_NUMFMT_STRING( 37, "#,##0 _" UTF8_EURO ";-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0 _" UTF8_EURO ";[RED]-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _" UTF8_EURO ";-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _" UTF8_EURO ";[RED]-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0 " UTF8_EURO ";-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0 " UTF8_EURO ";[RED]-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00 " UTF8_EURO ";-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00 " UTF8_EURO ";[RED]-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_AUSTRIAN[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 63, UTF8_EURO " #,##0;-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_EURO " #,##0;[RED]-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_EURO " #,##0.00;-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_EURO " #,##0.00;[RED]-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_SWISS[] = +{ + EXC_NUMFMT_STRING( 63, "\"SFr. \"#,##0;\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"SFr. \"#,##0;[RED]\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"SFr. \"#,##0.00;\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"SFr. \"#,##0.00;[RED]\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_LUXEMBOURG[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 37, "#,##0 _" UTF8_EURO ";-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0 _" UTF8_EURO ";[RED]-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _" UTF8_EURO ";-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _" UTF8_EURO ";[RED]-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0 " UTF8_EURO ";-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0 " UTF8_EURO ";[RED]-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00 " UTF8_EURO ";-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00 " UTF8_EURO ";[RED]-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_LIECHTENSTEIN[] = +{ + EXC_NUMFMT_STRING( 63, "\"CHF \"#,##0;\"CHF \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"CHF \"#,##0;[RED]\"CHF \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"CHF \"#,##0.00;\"CHF \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"CHF \"#,##0.00;[RED]\"CHF \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// ITALIAN -------------------------------------------------------------------- + +const XclBuiltInFormat spBuiltInFormats_ITALIAN_ITALY[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY h:mm" ), + EXC_NUMFMT_STRING( 63, UTF8_EURO " #,##0;-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_EURO " #,##0;[RED]-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_EURO " #,##0.00;-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_EURO " #,##0.00;[RED]-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ITALIAN_SWISS[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "DD.MM.YYYY hh:mm" ), + EXC_NUMFMT_STRING( 63, "\"SFr. \"#,##0;\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"SFr. \"#,##0;[RED]\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"SFr. \"#,##0.00;\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"SFr. \"#,##0.00;[RED]\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// SWEDISH -------------------------------------------------------------------- + +const XclBuiltInFormat spBuiltInFormats_SWEDISH_SWEDEN[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "YYYY-MM-DD hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0 _k_r;-#,##0 _k_r" ), + EXC_NUMFMT_STRING( 38, "#,##0 _k_r;[RED]-#,##0 _k_r" ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _k_r;-#,##0.00 _k_r" ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _k_r;[RED]-#,##0.00 _k_r" ), + EXC_NUMFMT_STRING( 63, "#,##0 \"kr\";-#,##0 \"kr\"" ), + EXC_NUMFMT_STRING( 64, "#,##0 \"kr\";[RED]-#,##0 \"kr\"" ), + EXC_NUMFMT_STRING( 65, "#,##0.00 \"kr\";-#,##0.00 \"kr\"" ), + EXC_NUMFMT_STRING( 66, "#,##0.00 \"kr\";[RED]-#,##0.00 \"kr\"" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_SWEDISH_FINLAND[] = +{ + EXC_NUMFMT_STRING( 9, "0 %" ), + EXC_NUMFMT_STRING( 10, "0.00 %" ), + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "D.M.YYYY hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0 _" UTF8_EURO ";-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0 _" UTF8_EURO ";[RED]-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _" UTF8_EURO ";-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _" UTF8_EURO ";[RED]-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0 " UTF8_EURO ";-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0 " UTF8_EURO ";[RED]-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00 " UTF8_EURO ";-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00 " UTF8_EURO ";[RED]-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +// ASIAN ---------------------------------------------------------------------- + +/** Base table for Asian locales. */ +const XclBuiltInFormat spBuiltInFormats_ASIAN[] = +{ + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 23, "$#,##0_);($#,##0)" ), + EXC_NUMFMT_STRING( 24, "$#,##0_);[RED]($#,##0)" ), + EXC_NUMFMT_STRING( 25, "$#,##0.00_);($#,##0.00)" ), + EXC_NUMFMT_STRING( 26, "$#,##0.00_);[RED]($#,##0.00)" ), + EXC_NUMFMT_REUSE( 29, 28 ), + EXC_NUMFMT_REUSE( 36, 27 ), + EXC_NUMFMT_REUSE( 50, 27 ), + EXC_NUMFMT_REUSE( 51, 28 ), + EXC_NUMFMT_REUSE( 52, 34 ), + EXC_NUMFMT_REUSE( 53, 35 ), + EXC_NUMFMT_REUSE( 54, 28 ), + EXC_NUMFMT_REUSE( 55, 34 ), + EXC_NUMFMT_REUSE( 56, 35 ), + EXC_NUMFMT_REUSE( 57, 27 ), + EXC_NUMFMT_REUSE( 58, 28 ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_JAPANESE[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY/M/D" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 22, "YYYY/M/D h:mm" ), + EXC_NUMFMT_STRING( 27, "[$-0411]GE.M.D" ), + EXC_NUMFMT_STRING( 28, "[$-0411]GGGE" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 30, "[$-0411]M/D/YY" ), + EXC_NUMFMT_STRING( 31, "[$-0411]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0411]h" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0411]h" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0411]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON ), + EXC_NUMFMT_STRING( 35, "[$-0411]M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 63, UTF8_YEN_JP "#,##0;-" UTF8_YEN_JP "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_YEN_JP "#,##0;[RED]-" UTF8_YEN_JP "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_YEN_JP "#,##0.00;-" UTF8_YEN_JP "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_YEN_JP "#,##0.00;[RED]-" UTF8_YEN_JP "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_KOREAN[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY-MM-DD" ), + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 22, "YYYY-MM-DD h:mm" ), + EXC_NUMFMT_STRING( 27, "[$-0412]YYYY" UTF8_CJ_YEAR " MM" UTF8_CJ_MON " DD" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 28, "[$-0412]MM-DD" ), + EXC_NUMFMT_STRING( 30, "[$-0412]MM-DD-YY" ), + EXC_NUMFMT_STRING( 31, "[$-0412]YYYY" UTF8_KO_YEAR " MM" UTF8_KO_MON " DD" UTF8_KO_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0412]h" UTF8_KO_HOUR " mm" UTF8_KO_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0412]h" UTF8_KO_HOUR " mm" UTF8_KO_MIN " ss" UTF8_KO_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0412]YYYY\"/\"MM\"/\"DD" ), + EXC_NUMFMT_STRING( 35, "[$-0412]YYYY-MM-DD" ), + EXC_NUMFMT_STRING( 63, UTF8_WON "#,##0;-" UTF8_WON "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_WON "#,##0;[RED]-" UTF8_WON "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_WON "#,##0.00;-" UTF8_WON "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_WON "#,##0.00;[RED]-" UTF8_WON "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_CHINESE_SIMPLIFIED[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY-M-D" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 22, "YYYY-M-D h:mm" ), + EXC_NUMFMT_STRING( 27, "[$-0804]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON ), + EXC_NUMFMT_STRING( 28, "[$-0804]M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 30, "[$-0804]M-D-YY" ), + EXC_NUMFMT_STRING( 31, "[$-0804]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0804]h" UTF8_CS_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0804]h" UTF8_CS_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0804]AM/PMh" UTF8_CS_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 35, "[$-0804]AM/PMh" UTF8_CS_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_REUSE( 52, 27 ), + EXC_NUMFMT_REUSE( 53, 28 ), + EXC_NUMFMT_STRING( 63, UTF8_YEN_CS "#,##0;-" UTF8_YEN_CS "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_YEN_CS "#,##0;[RED]-" UTF8_YEN_CS "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_YEN_CS "#,##0.00;-" UTF8_YEN_CS "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_YEN_CS "#,##0.00;[RED]-" UTF8_YEN_CS "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_CHINESE_TRADITIONAL[] = +{ + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "hh:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "hh:mm:ss AM/PM" ), + EXC_NUMFMT_OFFSET( 20, NF_TIME_HHMM ), + EXC_NUMFMT_OFFSET( 21, NF_TIME_HHMMSS ), + EXC_NUMFMT_STRING( 22, "YYYY/M/D hh:mm" ), + EXC_NUMFMT_STRING( 23, "US$#,##0_);(US$#,##0)" ), + EXC_NUMFMT_STRING( 24, "US$#,##0_);[RED](US$#,##0)" ), + EXC_NUMFMT_STRING( 25, "US$#,##0.00_);(US$#,##0.00)" ), + EXC_NUMFMT_STRING( 26, "US$#,##0.00_);[RED](US$#,##0.00)" ), + EXC_NUMFMT_STRING( 27, "[$-0404]E/M/D" ), + EXC_NUMFMT_STRING( 28, "[$-0404]E" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 30, "[$-0404]M/D/YY" ), + EXC_NUMFMT_STRING( 31, "[$-0404]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0404]hh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0404]hh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0404]AM/PMhh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 35, "[$-0404]AM/PMhh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 63, "$#,##0;-$#,##0" ), + EXC_NUMFMT_STRING( 64, "$#,##0;[RED]-$#,##0" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00;-$#,##0.00" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00;[RED]-$#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// OTHER ---------------------------------------------------------------------- + +const XclBuiltInFormat spBuiltInFormats_HEBREW[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMMM" ), + EXC_NUMFMT_STRING( 17, "MMMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 63, UTF8_SHEQEL " #,##0;" UTF8_SHEQEL " -#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_SHEQEL " #,##0;[RED]" UTF8_SHEQEL " -#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_SHEQEL " #,##0.00;" UTF8_SHEQEL " -#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_SHEQEL " #,##0.00;[RED]" UTF8_SHEQEL " -#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_THAI[] = +{ + EXC_NUMFMT_STRING( 14, "D/M/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "D/M/YYYY h:mm" ), + EXC_NUMFMT_STRING( 59, "t0" ), + EXC_NUMFMT_STRING( 60, "t0.00" ), + EXC_NUMFMT_STRING( 61, "t#,##0" ), + EXC_NUMFMT_STRING( 62, "t#,##0.00" ), + EXC_NUMFMT_STRING( 63, "t" UTF8_BAHT "#,##0_);t(" UTF8_BAHT "#,##0)" ), + EXC_NUMFMT_STRING( 64, "t" UTF8_BAHT "#,##0_);[RED]t(" UTF8_BAHT "#,##0)" ), + EXC_NUMFMT_STRING( 65, "t" UTF8_BAHT "#,##0.00_);t(" UTF8_BAHT "#,##0.00)" ), + EXC_NUMFMT_STRING( 66, "t" UTF8_BAHT "#,##0.00_);[RED]t(" UTF8_BAHT "#,##0.00)" ), + EXC_NUMFMT_STRING( 67, "t0%" ), + EXC_NUMFMT_STRING( 68, "t0.00%" ), + EXC_NUMFMT_STRING( 69, "t# ?/?" ), + EXC_NUMFMT_STRING( 70, "t# ?\?/?\?" ), + EXC_NUMFMT_STRING( 71, "tD/M/EE" ), + EXC_NUMFMT_STRING( 72, "tD-MMM-E" ), + EXC_NUMFMT_STRING( 73, "tD-MMM" ), + EXC_NUMFMT_STRING( 74, "tMMM-E" ), + EXC_NUMFMT_STRING( 75, "th:mm" ), + EXC_NUMFMT_STRING( 76, "th:mm:ss" ), + EXC_NUMFMT_STRING( 77, "tD/M/EE h:mm" ), + EXC_NUMFMT_STRING( 78, "tmm:ss" ), + EXC_NUMFMT_STRING( 79, "t[h]:mm:ss" ), + EXC_NUMFMT_STRING( 80, "tmm:ss.0" ), + EXC_NUMFMT_STRING( 81, "D/M/E" ), + EXC_NUMFMT_ENDTABLE() +}; + +#undef EXC_NUMFMT_ENDTABLE +#undef EXC_NUMFMT_REUSE +#undef EXC_NUMFMT_OFFSET +#undef EXC_NUMFMT_STRING + +/** Specifies a number format table for a specific language. */ +struct XclBuiltInFormatTable +{ + LanguageType meLanguage; /// The language of this table. + LanguageType meParentLang; /// The language of the parent table. + const XclBuiltInFormat* mpFormats; /// The number format table. +}; + +const XclBuiltInFormatTable spBuiltInFormatTables[] = +{ // language parent language format table + { LANGUAGE_DONTKNOW, LANGUAGE_NONE, spBuiltInFormats_DONTKNOW }, + + { LANGUAGE_ENGLISH, LANGUAGE_DONTKNOW, spBuiltInFormats_ENGLISH }, + { LANGUAGE_ENGLISH_UK, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_UK }, + { LANGUAGE_ENGLISH_EIRE, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_EIRE }, + { LANGUAGE_ENGLISH_US, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_US }, + { LANGUAGE_ENGLISH_CAN, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_CAN }, + { LANGUAGE_ENGLISH_AUS, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_AUS }, + { LANGUAGE_ENGLISH_SAFRICA, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_SAFRICA }, + { LANGUAGE_ENGLISH_NZ, LANGUAGE_ENGLISH_AUS, nullptr }, + + { PRV_LANGUAGE_FRENCH_PRIM, LANGUAGE_DONTKNOW, spBuiltInFormats_FRENCH }, + { LANGUAGE_FRENCH, PRV_LANGUAGE_FRENCH_PRIM, spBuiltInFormats_FRENCH_FRANCE }, + { LANGUAGE_FRENCH_CANADIAN, PRV_LANGUAGE_FRENCH_PRIM, spBuiltInFormats_FRENCH_CANADIAN }, + { LANGUAGE_FRENCH_SWISS, PRV_LANGUAGE_FRENCH_PRIM, spBuiltInFormats_FRENCH_SWISS }, + { LANGUAGE_FRENCH_BELGIAN, LANGUAGE_FRENCH, spBuiltInFormats_FRENCH_BELGIAN }, + { LANGUAGE_FRENCH_LUXEMBOURG, LANGUAGE_FRENCH, nullptr }, + { LANGUAGE_FRENCH_MONACO, LANGUAGE_FRENCH, nullptr }, + + { PRV_LANGUAGE_GERMAN_PRIM, LANGUAGE_DONTKNOW, spBuiltInFormats_GERMAN }, + { LANGUAGE_GERMAN, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_GERMANY }, + { LANGUAGE_GERMAN_AUSTRIAN, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_AUSTRIAN }, + { LANGUAGE_GERMAN_SWISS, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_SWISS }, + { LANGUAGE_GERMAN_LUXEMBOURG, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_LUXEMBOURG }, + { LANGUAGE_GERMAN_LIECHTENSTEIN, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_LIECHTENSTEIN }, + + { LANGUAGE_ITALIAN, LANGUAGE_DONTKNOW, spBuiltInFormats_ITALIAN_ITALY }, + { LANGUAGE_ITALIAN_SWISS, LANGUAGE_DONTKNOW, spBuiltInFormats_ITALIAN_SWISS }, + + { LANGUAGE_SWEDISH, LANGUAGE_DONTKNOW, spBuiltInFormats_SWEDISH_SWEDEN }, + { LANGUAGE_SWEDISH_FINLAND, LANGUAGE_DONTKNOW, spBuiltInFormats_SWEDISH_FINLAND }, + + { PRV_LANGUAGE_ASIAN_PRIM, LANGUAGE_DONTKNOW, spBuiltInFormats_ASIAN }, + { LANGUAGE_JAPANESE, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_JAPANESE }, + { LANGUAGE_KOREAN, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_KOREAN }, + { LANGUAGE_CHINESE_SIMPLIFIED, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_CHINESE_SIMPLIFIED }, + { LANGUAGE_CHINESE_TRADITIONAL, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_CHINESE_TRADITIONAL }, + + { LANGUAGE_HEBREW, LANGUAGE_DONTKNOW, spBuiltInFormats_HEBREW }, + { LANGUAGE_THAI, LANGUAGE_DONTKNOW, spBuiltInFormats_THAI } +}; + +} // namespace + +XclNumFmtBuffer::XclNumFmtBuffer( const XclRoot& rRoot ) : + meSysLang( rRoot.GetSysLanguage() ), + mnStdScNumFmt( rRoot.GetFormatter().GetStandardIndex( ScGlobal::eLnge ) ) +{ + // *** insert default formats (BIFF5+ only)*** + + if( rRoot.GetBiff() >= EXC_BIFF5 ) + InsertBuiltinFormats(); +} + +void XclNumFmtBuffer::InitializeImport() +{ + maFmtMap.clear(); +} + +void XclNumFmtBuffer::InsertFormat( sal_uInt16 nXclNumFmt, const OUString& rFormat ) +{ + XclNumFmt& rNumFmt = maFmtMap[ nXclNumFmt ]; + rNumFmt.maFormat = rFormat; + // #i62053# rFormat may be an empty string, meOffset must be initialized + rNumFmt.meOffset = NF_NUMBER_STANDARD; + rNumFmt.meLanguage = LANGUAGE_SYSTEM; +} + +void XclNumFmtBuffer::InsertBuiltinFormats() +{ + // build a map containing tables for all languages + typedef ::std::map< LanguageType, const XclBuiltInFormatTable* > XclBuiltInMap; + XclBuiltInMap aBuiltInMap; + for(const auto &rTable : spBuiltInFormatTables) + aBuiltInMap[ rTable.meLanguage ] = &rTable; + + // build a list of table pointers for the current language, with all parent tables + typedef ::std::vector< const XclBuiltInFormatTable* > XclBuiltInVec; + XclBuiltInVec aBuiltInVec; + for( XclBuiltInMap::const_iterator aMIt = aBuiltInMap.find( meSysLang ), aMEnd = aBuiltInMap.end(); + aMIt != aMEnd; aMIt = aBuiltInMap.find( aMIt->second->meParentLang ) ) + aBuiltInVec.push_back( aMIt->second ); + // language not supported + if( aBuiltInVec.empty() ) + { + SAL_WARN("sc", "XclNumFmtBuffer::InsertBuiltinFormats - language not supported (#i29949#) 0x" << std::hex << meSysLang ); + XclBuiltInMap::const_iterator aMIt = aBuiltInMap.find( LANGUAGE_DONTKNOW ); + OSL_ENSURE( aMIt != aBuiltInMap.end(), "XclNumFmtBuffer::InsertBuiltinFormats - default map not found" ); + if( aMIt != aBuiltInMap.end() ) + aBuiltInVec.push_back( aMIt->second ); + } + + // insert the default formats in the format map, from root parent to system language + std::map< sal_uInt16, sal_uInt16 > aReuseMap; + for( XclBuiltInVec::reverse_iterator aVIt = aBuiltInVec.rbegin(), aVEnd = aBuiltInVec.rend(); aVIt != aVEnd; ++aVIt ) + { + // put LANGUAGE_SYSTEM for all entries in default table + LanguageType eLang = ((*aVIt)->meLanguage == LANGUAGE_DONTKNOW) ? LANGUAGE_SYSTEM : meSysLang; + for( const XclBuiltInFormat* pBuiltIn = (*aVIt)->mpFormats; pBuiltIn && (pBuiltIn->mnXclNumFmt != EXC_FORMAT_NOTFOUND); ++pBuiltIn ) + { + XclNumFmt& rNumFmt = maFmtMap[ pBuiltIn->mnXclNumFmt ]; + + rNumFmt.meOffset = pBuiltIn->meOffset; + rNumFmt.meLanguage = eLang; + + if( pBuiltIn->mpFormat ) + rNumFmt.maFormat = OUString( pBuiltIn->mpFormat, strlen(pBuiltIn->mpFormat), RTL_TEXTENCODING_UTF8 ); + else + rNumFmt.maFormat.clear(); + + if( pBuiltIn->meOffset == PRV_NF_INDEX_REUSE ) + aReuseMap[ pBuiltIn->mnXclNumFmt ] = pBuiltIn->mnXclReuseFmt; + else + aReuseMap.erase( pBuiltIn->mnXclNumFmt ); + } + } + + // copy reused number formats + for( const auto& [rXclNumFmt, rXclReuseFmt] : aReuseMap ) + maFmtMap[ rXclNumFmt ] = maFmtMap[ rXclReuseFmt ]; +} + +// Cell formatting data (XF) ================================================== + +XclCellProt::XclCellProt() : + mbLocked( true ), // default in Excel and Calc + mbHidden( false ) +{ +} + +bool operator==( const XclCellProt& rLeft, const XclCellProt& rRight ) +{ + return (rLeft.mbLocked == rRight.mbLocked) && (rLeft.mbHidden == rRight.mbHidden); +} + +XclCellAlign::XclCellAlign() : + mnHorAlign( EXC_XF_HOR_GENERAL ), + mnVerAlign( EXC_XF_VER_BOTTOM ), + mnOrient( EXC_ORIENT_NONE ), + mnTextDir( EXC_XF_TEXTDIR_CONTEXT ), + mnRotation( EXC_ROT_NONE ), + mnIndent( 0 ), + mbLineBreak( false ), + mbShrink( false ) +{ +} + +SvxCellHorJustify XclCellAlign::GetScHorAlign() const +{ + SvxCellHorJustify eHorJust = SvxCellHorJustify::Standard; + switch( mnHorAlign ) + { + case EXC_XF_HOR_GENERAL: eHorJust = SvxCellHorJustify::Standard; break; + case EXC_XF_HOR_LEFT: eHorJust = SvxCellHorJustify::Left; break; + case EXC_XF_HOR_CENTER_AS: + case EXC_XF_HOR_CENTER: eHorJust = SvxCellHorJustify::Center; break; + case EXC_XF_HOR_RIGHT: eHorJust = SvxCellHorJustify::Right; break; + case EXC_XF_HOR_FILL: eHorJust = SvxCellHorJustify::Repeat; break; + case EXC_XF_HOR_JUSTIFY: + case EXC_XF_HOR_DISTRIB: eHorJust = SvxCellHorJustify::Block; break; + default: OSL_FAIL( "XclCellAlign::GetScHorAlign - unknown horizontal alignment" ); + } + return eHorJust; +} + +SvxCellJustifyMethod XclCellAlign::GetScHorJustifyMethod() const +{ + return (mnHorAlign == EXC_XF_HOR_DISTRIB) ? SvxCellJustifyMethod::Distribute : SvxCellJustifyMethod::Auto; +} + +SvxCellVerJustify XclCellAlign::GetScVerAlign() const +{ + SvxCellVerJustify eVerJust = SvxCellVerJustify::Standard; + switch( mnVerAlign ) + { + case EXC_XF_VER_TOP: eVerJust = SvxCellVerJustify::Top; break; + case EXC_XF_VER_CENTER: eVerJust = SvxCellVerJustify::Center; break; + case EXC_XF_VER_BOTTOM: eVerJust = SvxCellVerJustify::Standard; break; + case EXC_XF_VER_JUSTIFY: + case EXC_XF_VER_DISTRIB: eVerJust = SvxCellVerJustify::Block; break; + default: OSL_FAIL( "XclCellAlign::GetScVerAlign - unknown vertical alignment" ); + } + return eVerJust; +} + +SvxCellJustifyMethod XclCellAlign::GetScVerJustifyMethod() const +{ + return (mnVerAlign == EXC_XF_VER_DISTRIB) ? SvxCellJustifyMethod::Distribute : SvxCellJustifyMethod::Auto; +} + +SvxFrameDirection XclCellAlign::GetScFrameDir() const +{ + SvxFrameDirection eFrameDir = SvxFrameDirection::Environment; + switch( mnTextDir ) + { + case EXC_XF_TEXTDIR_CONTEXT: eFrameDir = SvxFrameDirection::Environment; break; + case EXC_XF_TEXTDIR_LTR: eFrameDir = SvxFrameDirection::Horizontal_LR_TB; break; + case EXC_XF_TEXTDIR_RTL: eFrameDir = SvxFrameDirection::Horizontal_RL_TB; break; + default: OSL_FAIL( "XclCellAlign::GetScFrameDir - unknown CTL text direction" ); + } + return eFrameDir; +} + +void XclCellAlign::SetScHorAlign( SvxCellHorJustify eHorJust ) +{ + switch( eHorJust ) + { + case SvxCellHorJustify::Standard: mnHorAlign = EXC_XF_HOR_GENERAL; break; + case SvxCellHorJustify::Left: mnHorAlign = EXC_XF_HOR_LEFT; break; + case SvxCellHorJustify::Center: mnHorAlign = EXC_XF_HOR_CENTER; break; + case SvxCellHorJustify::Right: mnHorAlign = EXC_XF_HOR_RIGHT; break; + case SvxCellHorJustify::Block: mnHorAlign = EXC_XF_HOR_JUSTIFY; break; + case SvxCellHorJustify::Repeat: mnHorAlign = EXC_XF_HOR_FILL; break; + default: mnHorAlign = EXC_XF_HOR_GENERAL; + OSL_FAIL( "XclCellAlign::SetScHorAlign - unknown horizontal alignment" ); + } +} + +void XclCellAlign::SetScVerAlign( SvxCellVerJustify eVerJust ) +{ + switch( eVerJust ) + { + case SvxCellVerJustify::Standard: mnVerAlign = EXC_XF_VER_BOTTOM; break; + case SvxCellVerJustify::Top: mnVerAlign = EXC_XF_VER_TOP; break; + case SvxCellVerJustify::Center: mnVerAlign = EXC_XF_VER_CENTER; break; + case SvxCellVerJustify::Bottom: mnVerAlign = EXC_XF_VER_BOTTOM; break; + default: mnVerAlign = EXC_XF_VER_BOTTOM; + OSL_FAIL( "XclCellAlign::SetScVerAlign - unknown vertical alignment" ); + } +} + +void XclCellAlign::SetScFrameDir( SvxFrameDirection eFrameDir ) +{ + switch( eFrameDir ) + { + case SvxFrameDirection::Environment: mnTextDir = EXC_XF_TEXTDIR_CONTEXT; break; + case SvxFrameDirection::Horizontal_LR_TB: mnTextDir = EXC_XF_TEXTDIR_LTR; break; + case SvxFrameDirection::Horizontal_RL_TB: mnTextDir = EXC_XF_TEXTDIR_RTL; break; + default: mnTextDir = EXC_XF_TEXTDIR_CONTEXT; + OSL_FAIL( "XclCellAlign::SetScFrameDir - unknown CTL text direction" ); + } +} + +bool operator==( const XclCellAlign& rLeft, const XclCellAlign& rRight ) +{ + return + (rLeft.mnHorAlign == rRight.mnHorAlign) && (rLeft.mnVerAlign == rRight.mnVerAlign) && + (rLeft.mnTextDir == rRight.mnTextDir) && (rLeft.mnOrient == rRight.mnOrient) && + (rLeft.mnRotation == rRight.mnRotation) && (rLeft.mnIndent == rRight.mnIndent) && + (rLeft.mbLineBreak == rRight.mbLineBreak) && (rLeft.mbShrink == rRight.mbShrink); +} + +XclCellBorder::XclCellBorder() : + mnLeftColor( 0 ), + mnRightColor( 0 ), + mnTopColor( 0 ), + mnBottomColor( 0 ), + mnDiagColor( 0 ), + mnLeftLine( EXC_LINE_NONE ), + mnRightLine( EXC_LINE_NONE ), + mnTopLine( EXC_LINE_NONE ), + mnBottomLine( EXC_LINE_NONE ), + mnDiagLine( EXC_LINE_NONE ), + mbDiagTLtoBR( false ), + mbDiagBLtoTR( false ) +{ +} + +bool operator==( const XclCellBorder& rLeft, const XclCellBorder& rRight ) +{ + return + (rLeft.mnLeftColor == rRight.mnLeftColor) && (rLeft.mnRightColor == rRight.mnRightColor) && + (rLeft.mnTopColor == rRight.mnTopColor) && (rLeft.mnBottomColor == rRight.mnBottomColor) && + (rLeft.mnLeftLine == rRight.mnLeftLine) && (rLeft.mnRightLine == rRight.mnRightLine) && + (rLeft.mnTopLine == rRight.mnTopLine) && (rLeft.mnBottomLine == rRight.mnBottomLine) && + (rLeft.mnDiagColor == rRight.mnDiagColor) && (rLeft.mnDiagLine == rRight.mnDiagLine) && + (rLeft.mbDiagTLtoBR == rRight.mbDiagTLtoBR) && (rLeft.mbDiagBLtoTR == rRight.mbDiagBLtoTR); +} + +XclCellArea::XclCellArea() : + mnForeColor( EXC_COLOR_WINDOWTEXT ), + mnBackColor( EXC_COLOR_WINDOWBACK ), + mnPattern( EXC_PATT_NONE ) +{ +} + +XclCellArea::XclCellArea(sal_uInt8 nPattern) : + mnForeColor( EXC_COLOR_WINDOWTEXT ), + mnBackColor( EXC_COLOR_WINDOWBACK ), + mnPattern( nPattern ) +{ +} + +bool XclCellArea::IsTransparent() const +{ + return (mnPattern == EXC_PATT_NONE) && (mnBackColor == EXC_COLOR_WINDOWBACK); +} + +bool operator==( const XclCellArea& rLeft, const XclCellArea& rRight ) +{ + return + (rLeft.mnForeColor == rRight.mnForeColor) && (rLeft.mnBackColor == rRight.mnBackColor) && + (rLeft.mnPattern == rRight.mnPattern); +} + +XclXFBase::XclXFBase( bool bCellXF ) : + mnParent( bCellXF ? EXC_XF_DEFAULTSTYLE : EXC_XF_STYLEPARENT ), + mbCellXF( bCellXF ) +{ + SetAllUsedFlags( false ); +} + +XclXFBase::~XclXFBase() +{ +} + +void XclXFBase::SetAllUsedFlags( bool bUsed ) +{ + mbProtUsed = mbFontUsed = mbFmtUsed = mbAlignUsed = mbBorderUsed = mbAreaUsed = bUsed; +} + +bool XclXFBase::HasUsedFlags() const +{ + return mbProtUsed || mbFontUsed || mbFmtUsed || mbAlignUsed || mbBorderUsed || mbAreaUsed; +} + +bool XclXFBase::Equals( const XclXFBase& rCmp ) const +{ + return + (mbCellXF == rCmp.mbCellXF) && (mnParent == rCmp.mnParent) && + (mbProtUsed == rCmp.mbProtUsed) && (mbFontUsed == rCmp.mbFontUsed) && + (mbFmtUsed == rCmp.mbFmtUsed) && (mbAlignUsed == rCmp.mbAlignUsed) && + (mbBorderUsed == rCmp.mbBorderUsed) && (mbAreaUsed == rCmp.mbAreaUsed); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltoolbar.cxx b/sc/source/filter/excel/xltoolbar.cxx new file mode 100644 index 000000000..efb54925b --- /dev/null +++ b/sc/source/filter/excel/xltoolbar.cxx @@ -0,0 +1,439 @@ +/* -*- 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/. + */ +#include "xltoolbar.hxx" +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/ui/XUIConfigurationPersistence.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <comphelper/indexedpropertyvalues.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <rtl/ref.hxx> +#include <map> + +using namespace com::sun::star; + +typedef std::map< sal_Int16, OUString > IdToString; + +namespace { + +class MSOExcelCommandConvertor : public MSOCommandConvertor +{ + IdToString msoToOOcmd; + IdToString tcidToOOcmd; +public: + MSOExcelCommandConvertor(); + virtual OUString MSOCommandToOOCommand( sal_Int16 msoCmd ) override; + virtual OUString MSOTCIDToOOCommand( sal_Int16 key ) override; +}; + +} + +MSOExcelCommandConvertor::MSOExcelCommandConvertor() +{ +/* + // mso command id to ooo command string + // #FIXME and *HUNDREDS* of id's to added here + msoToOOcmd[ 0x20b ] = ".uno:CloseDoc"; + msoToOOcmd[ 0x50 ] = ".uno:Open"; + + // mso tcid to ooo command string + // #FIXME and *HUNDREDS* of id's to added here + tcidToOOcmd[ 0x9d9 ] = ".uno:Print"; +*/ +} + +OUString MSOExcelCommandConvertor::MSOCommandToOOCommand( sal_Int16 key ) +{ + OUString sResult; + IdToString::iterator it = msoToOOcmd.find( key ); + if ( it != msoToOOcmd.end() ) + sResult = it->second; + return sResult; +} + +OUString MSOExcelCommandConvertor::MSOTCIDToOOCommand( sal_Int16 key ) +{ + OUString sResult; + IdToString::iterator it = tcidToOOcmd.find( key ); + if ( it != tcidToOOcmd.end() ) + sResult = it->second; + return sResult; +} + +CTBS::CTBS() : bSignature(0), bVersion(0), reserved1(0), reserved2(0), reserved3(0), ctb(0), ctbViews(0), ictbView(0) +{ +} + +ScCTB::ScCTB(sal_uInt16 nNum ) : nViews( nNum ), ectbid(0) +{ +} + +bool ScCTB::Read( SvStream &rS ) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + tb.Read( rS ); + + { + const size_t nMinRecordSize = 20; // TBVisualData reads 20 bytes + const size_t nMaxPossibleRecords = rS.remainingSize() / nMinRecordSize; + if (nViews > nMaxPossibleRecords) + { + SAL_WARN("sc.filter", "ScCTB::Read more entries claimed than stream could contain"); + return false; + } + } + + for ( sal_uInt16 index = 0; index < nViews; ++index ) + { + TBVisualData aVisData; + aVisData.Read( rS ); + rVisualData.push_back( aVisData ); + } + rS.ReadUInt32( ectbid ); + + sal_Int16 nCL = tb.getcCL(); + if (nCL > 0) + { + auto nIndexes = o3tl::make_unsigned(nCL); + + const size_t nMinRecordSize = 11; // ScTBC's TBCHeader reads min 11 bytes + const size_t nMaxPossibleRecords = rS.remainingSize() / nMinRecordSize; + if (nIndexes > nMaxPossibleRecords) + { + SAL_WARN("sc.filter", "ScCTB::Read more entries claimed than stream could contain"); + return false; + } + + for (decltype(nIndexes) index = 0; index < nIndexes; ++index) + { + ScTBC aTBC; + aTBC.Read( rS ); + rTBC.push_back( aTBC ); + } + } + + return true; +} + +#ifdef DEBUG_SC_EXCEL +void ScCTB::Print( FILE* fp ) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] ScCTB -- dump\n", nOffSet ); + indent_printf( fp, " nViews 0x%x\n", nViews); + tb.Print( fp ); + + sal_Int32 counter = 0; + for ( auto& rItem : rVisualData ) + { + indent_printf( fp, " TBVisualData [%d]\n", counter++ ); + Indent b; + rItem.Print( fp ); + } + indent_printf( fp, " ectbid 0x%x\n", ectbid); + counter = 0; + for ( auto& rItem : rTBC ) + { + indent_printf( fp, " ScTBC [%d]\n", counter++); + Indent c; + rItem.Print( fp ); + } +} +#endif + +bool ScCTB::IsMenuToolbar() const +{ + return tb.IsMenuToolbar(); +} + +bool ScCTB::ImportMenuTB( ScCTBWrapper& rWrapper, const css::uno::Reference< css::container::XIndexContainer >& xMenuDesc, CustomToolBarImportHelper& helper ) +{ + for ( auto& rItem : rTBC ) + { + if ( !rItem.ImportToolBarControl( rWrapper, xMenuDesc, helper, IsMenuToolbar() ) ) + return false; + } + return true; +} + +bool ScCTB::ImportCustomToolBar( ScCTBWrapper& rWrapper, CustomToolBarImportHelper& helper ) +{ + + bool bRes = false; + try + { + if ( !tb.IsEnabled() ) + return true; // didn't fail, just ignoring + + // Create default setting + uno::Reference< container::XIndexContainer > xIndexContainer( helper.getCfgManager()->createSettings(), uno::UNO_SET_THROW ); + uno::Reference< container::XIndexAccess > xIndexAccess( xIndexContainer, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps( xIndexContainer, uno::UNO_QUERY_THROW ); + WString& name = tb.getName(); + // set UI name for toolbar + xProps->setPropertyValue("UIName", uno::Any( name.getString() ) ); + + OUString sToolBarName = "private:resource/toolbar/custom_" + name.getString(); + for ( auto& rItem : rTBC ) + { + if ( !rItem.ImportToolBarControl( rWrapper, xIndexContainer, helper, IsMenuToolbar() ) ) + return false; + } + + helper.getCfgManager()->insertSettings( sToolBarName, xIndexAccess ); + helper.applyIcons(); + + uno::Reference< ui::XUIConfigurationPersistence > xPersistence( helper.getCfgManager()->getImageManager(), uno::UNO_QUERY_THROW ); + xPersistence->store(); + + xPersistence.set( helper.getCfgManager(), uno::UNO_QUERY_THROW ); + xPersistence->store(); + + bRes = true; + } + catch( uno::Exception& ) + { + bRes = false; + } + return bRes; +} +bool CTBS::Read( SvStream &rS ) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + rS.ReadUChar( bSignature ).ReadUChar( bVersion ).ReadUInt16( reserved1 ).ReadUInt16( reserved2 ).ReadUInt16( reserved3 ).ReadUInt16( ctb ).ReadUInt16( ctbViews ).ReadUInt16( ictbView ); + return true; +} + +#ifdef DEBUG_SC_EXCEL +void CTBS::Print( FILE* fp ) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] CTBS -- dump\n", nOffSet ); + + indent_printf( fp, " bSignature 0x%x\n", bSignature); + indent_printf( fp, " bVersion 0x%x\n", bVersion); + + indent_printf( fp, " reserved1 0x%x\n", reserved1 ); + indent_printf( fp, " reserved2 0x%x\n", reserved2 ); + indent_printf( fp, " reserved3 0x%x\n", reserved3 ); + + indent_printf( fp, " ctb 0x%x\n", ctb ); + indent_printf( fp, " ctbViews 0x%x\n", ctbViews ); + indent_printf( fp, " ictbView 0x%x\n", ictbView ); +} +#endif + +ScTBC::ScTBC() +{ +} + +bool +ScTBC::Read(SvStream &rS) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + if ( !tbch.Read( rS ) ) + return false; + sal_uInt16 tcid = tbch.getTcID(); + sal_uInt8 tct = tbch.getTct(); + if ( ( tcid != 0x0001 && tcid != 0x06CC && tcid != 0x03D8 && tcid != 0x03EC && tcid != 0x1051 ) && ( ( tct > 0 && tct < 0x0B ) || ( ( tct > 0x0B && tct < 0x10 ) || tct == 0x15 ) ) ) + { + tbcCmd = std::make_shared<TBCCmd>(); + if ( ! tbcCmd->Read( rS ) ) + return false; + } + if ( tct != 0x16 ) + { + tbcd = std::make_shared<TBCData>( tbch ); + if ( !tbcd->Read( rS ) ) + return false; + } + return true; +} + +#ifdef DEBUG_SC_EXCEL +void +ScTBC::Print(FILE* fp) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] ScTBC -- dump\n", nOffSet ); + tbch.Print( fp ); + if ( tbcCmd.get() ) + tbcCmd->Print( fp ); + if ( tbcd.get() ) + tbcd->Print( fp ); +} +#endif + +bool ScTBC::ImportToolBarControl( ScCTBWrapper& rWrapper, const css::uno::Reference< css::container::XIndexContainer >& toolbarcontainer, CustomToolBarImportHelper& helper, bool bIsMenuToolbar ) +{ + // how to identify built-in-command ? +// bool bBuiltin = false; + if ( tbcd ) + { + std::vector< css::beans::PropertyValue > props; + bool bBeginGroup = false; + tbcd->ImportToolBarControl( helper, props, bBeginGroup, bIsMenuToolbar ); + TBCMenuSpecific* pMenu = tbcd->getMenuSpecific(); + if ( pMenu ) + { + // search for ScCTB with the appropriate name ( it contains the + // menu items, although we cannot import ( or create ) a menu on + // a custom toolbar we can import the menu items in a separate + // toolbar ( better than nothing ) + ScCTB* pCustTB = rWrapper.GetCustomizationData( pMenu->Name() ); + if ( pCustTB ) + { + rtl::Reference< comphelper::IndexedPropertyValuesContainer > xMenuDesc = new comphelper::IndexedPropertyValuesContainer(); + if ( !pCustTB->ImportMenuTB( rWrapper, xMenuDesc, helper ) ) + return false; + if ( !bIsMenuToolbar ) + { + if ( !helper.createMenu( pMenu->Name(), xMenuDesc ) ) + return false; + } + else + { + beans::PropertyValue aProp; + aProp.Name = "ItemDescriptorContainer"; + aProp.Value <<= uno::Reference< container::XIndexContainer >(xMenuDesc); + props.push_back( aProp ); + } + } + } + + if ( bBeginGroup ) + { + // insert spacer + uno::Sequence sProps{ comphelper::makePropertyValue("Type", + ui::ItemType::SEPARATOR_LINE) }; + toolbarcontainer->insertByIndex( toolbarcontainer->getCount(), uno::Any( sProps ) ); + } + toolbarcontainer->insertByIndex( toolbarcontainer->getCount(), uno::Any( comphelper::containerToSequence(props) ) ); + } + return true; +} + +#ifdef DEBUG_SC_EXCEL +void +TBCCmd::Print(FILE* fp) +{ + Indent a; + indent_printf( fp, " TBCCmd -- dump\n" ); + indent_printf( fp, " cmdID 0x%x\n", cmdID ); + indent_printf( fp, " A ( fHideDrawing ) %s\n", A ? "true" : "false" ); + indent_printf( fp, " B ( reserved - ignored ) %s\n", A ? "true" : "false" ); + indent_printf( fp, " cmdType 0x%x\n", cmdType ); + indent_printf( fp, " C ( reserved - ignored ) %s\n", A ? "true" : "false" ); + indent_printf( fp, " reserved3 0x%x\n", reserved3 ); +} +#endif + +bool TBCCmd::Read( SvStream &rS ) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + rS.ReadUInt16( cmdID ); + sal_uInt16 temp; + rS.ReadUInt16( temp ); + A = (temp & 0x8000 ) == 0x8000; + B = (temp & 0x4000) == 0x4000; + cmdType = ( temp & 0x3E00 ) >> 9; + C = ( temp & 0x100 ) == 0x100; + reserved3 = ( temp & 0xFF ); + return true; +} + +ScCTBWrapper::ScCTBWrapper() +{ +} + +ScCTBWrapper::~ScCTBWrapper() +{ +} + +bool +ScCTBWrapper::Read( SvStream &rS) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + if (!ctbSet.Read(rS)) + return false; + + //ScCTB is 1 TB which is min 15bytes, nViews TBVisualData which is min 20bytes + //and one 32bit number (4 bytes) + const size_t nMinRecordSize = 19 + o3tl::sanitizing_min(ctbSet.ctbViews * 20, 0); + const size_t nMaxPossibleRecords = rS.remainingSize()/nMinRecordSize; + if (ctbSet.ctb > nMaxPossibleRecords) + return false; + + for ( sal_uInt16 index = 0; index < ctbSet.ctb; ++index ) + { + ScCTB aCTB( ctbSet.ctbViews ); + if ( !aCTB.Read( rS ) ) + return false; + rCTB.push_back( aCTB ); + } + return true; +} + +#ifdef DEBUG_SC_EXCEL +void +ScCTBWrapper::Print( FILE* fp ) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] ScCTBWrapper -- dump\n", nOffSet ); + ctbSet.Print( fp ); + for ( auto& rItem : rCTB ) + { + Indent b; + rItem.Print( fp ); + } +} +#endif + +ScCTB* ScCTBWrapper::GetCustomizationData( const OUString& sTBName ) +{ + ScCTB* pCTB = nullptr; + auto it = std::find_if(rCTB.begin(), rCTB.end(), [&sTBName](ScCTB& rItem) { return rItem.GetName() == sTBName; }); + if (it != rCTB.end()) + pCTB = &(*it); + return pCTB; +} + +void ScCTBWrapper::ImportCustomToolBar( SfxObjectShell& rDocSh ) +{ + if(rCTB.empty()) + return; + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference< ui::XModuleUIConfigurationManagerSupplier > xAppCfgSupp( ui::theModuleUIConfigurationManagerSupplier::get(xContext) ); + + for ( auto& rItem : rCTB ) + { + // for each customtoolbar + CustomToolBarImportHelper helper( rDocSh, xAppCfgSupp->getUIConfigurationManager( "com.sun.star.sheet.SpreadsheetDocument" ) ); + helper.setMSOCommandMap( new MSOExcelCommandConvertor() ); + // Ignore menu toolbars, excel doesn't ( afaics ) store + // menu customizations ( but you can have menus in a customtoolbar + // such menus will be dealt with when they are encountered + // as part of importing the appropriate MenuSpecific toolbar control ) + + if ( !rItem.IsMenuToolbar() && !rItem.ImportCustomToolBar( *this, helper ) ) + return; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltoolbar.hxx b/sc/source/filter/excel/xltoolbar.hxx new file mode 100644 index 000000000..f7da78b2f --- /dev/null +++ b/sc/source/filter/excel/xltoolbar.hxx @@ -0,0 +1,106 @@ +/* -*- 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/. + */ +#pragma once + +#include <filter/msfilter/mstoolbar.hxx> + +namespace com::sun::star::container { class XIndexContainer; } + +class ScCTBWrapper; +// hmm I don't normally use these packed structures +// but... hey always good to do something different +class TBCCmd : public TBBase +{ +public: + TBCCmd() : cmdID(0), A(false), B(false), cmdType(0), C(false), reserved3(0) + {} + sal_uInt16 cmdID; + bool A:1; + bool B:1; + sal_uInt16 cmdType:5; + bool C:1; + sal_uInt16 reserved3:8; + bool Read( SvStream& rS ) override; +#ifdef DEBUG_SC_EXCEL + virtual void Print(FILE* fp) override; +#endif +}; + +class ScTBC : public TBBase +{ + TBCHeader tbch; + std::shared_ptr<TBCCmd> tbcCmd; // optional + std::shared_ptr<TBCData> tbcd; +public: + ScTBC(); +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + bool Read(SvStream &rS) override; + bool ImportToolBarControl( ScCTBWrapper&, const css::uno::Reference< css::container::XIndexContainer >& toolbarcontainer, CustomToolBarImportHelper& helper, bool bIsMenuBar ); +}; + +class ScCTB : public TBBase +{ + sal_uInt16 nViews; + TB tb; + std::vector<TBVisualData> rVisualData; + sal_uInt32 ectbid; + std::vector< ScTBC > rTBC; +public: + explicit ScCTB(sal_uInt16); +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + bool Read(SvStream &rS) override; + bool IsMenuToolbar() const; + bool ImportCustomToolBar( ScCTBWrapper&, CustomToolBarImportHelper& ); + bool ImportMenuTB( ScCTBWrapper&, const css::uno::Reference< css::container::XIndexContainer >&, CustomToolBarImportHelper& ); + const OUString& GetName() { return tb.getName().getString(); } + +}; + +class CTBS : public TBBase +{ +public: + sal_uInt8 bSignature; + sal_uInt8 bVersion; + sal_uInt16 reserved1; + sal_uInt16 reserved2; + sal_uInt16 reserved3; + sal_uInt16 ctb; + sal_uInt16 ctbViews; + sal_uInt16 ictbView; + CTBS(const CTBS&); + CTBS& operator = ( const CTBS&); + CTBS(); +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + bool Read(SvStream &rS) override; +}; + +class ScCTBWrapper : public TBBase +{ + CTBS ctbSet; + + std::vector< ScCTB > rCTB; + +public: + ScCTBWrapper(); + virtual ~ScCTBWrapper() override; + bool Read(SvStream &rS) override; +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + void ImportCustomToolBar( SfxObjectShell& rDocSh ); + ScCTB* GetCustomizationData( const OUString& name ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltools.cxx b/sc/source/filter/excel/xltools.cxx new file mode 100644 index 000000000..3a69e7da0 --- /dev/null +++ b/sc/source/filter/excel/xltools.cxx @@ -0,0 +1,744 @@ +/* -*- 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 <algorithm> +#include <math.h> +#include <string_view> + +#include <o3tl/unit_conversion.hxx> +#include <sal/mathconf.h> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <tools/solar.h> +#include <o3tl/string_view.hxx> +#include <unotools/fontdefs.hxx> +#include <filter/msfilter/msvbahelper.hxx> +#include <xestream.hxx> +#include <formula/errorcodes.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <xlstyle.hxx> +#include <xlname.hxx> +#include <xistream.hxx> +#include <xltools.hxx> + +// GUID import/export + +XclGuid::XclGuid() + : mpnData{} +{ +} + +XclGuid::XclGuid( + sal_uInt32 nData1, sal_uInt16 nData2, sal_uInt16 nData3, + sal_uInt8 nData41, sal_uInt8 nData42, sal_uInt8 nData43, sal_uInt8 nData44, + sal_uInt8 nData45, sal_uInt8 nData46, sal_uInt8 nData47, sal_uInt8 nData48 ) +{ + // convert to little endian -> makes streaming easy + UInt32ToSVBT32( nData1, mpnData ); + ShortToSVBT16( nData2, mpnData + 4 ); + ShortToSVBT16( nData3, mpnData + 6 ); + mpnData[ 8 ] = nData41; + mpnData[ 9 ] = nData42; + mpnData[ 10 ] = nData43; + mpnData[ 11 ] = nData44; + mpnData[ 12 ] = nData45; + mpnData[ 13 ] = nData46; + mpnData[ 14 ] = nData47; + mpnData[ 15 ] = nData48; +} + +bool operator==( const XclGuid& rCmp1, const XclGuid& rCmp2 ) +{ + return ::std::equal( rCmp1.mpnData, std::end( rCmp1.mpnData ), rCmp2.mpnData ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclGuid& rGuid ) +{ + rStrm.Read( rGuid.mpnData, 16 ); // mpnData always in little endian + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclGuid& rGuid ) +{ + rStrm.Write( rGuid.mpnData, 16 ); // mpnData already in little endian + return rStrm; +} + +// Excel Tools + +// GUID's +const XclGuid XclTools::maGuidStdLink( + 0x79EAC9D0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B ); + +const XclGuid XclTools::maGuidUrlMoniker( + 0x79EAC9E0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B ); + +const XclGuid XclTools::maGuidFileMoniker( + 0x00000303, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 ); + +// numeric conversion + +double XclTools::GetDoubleFromRK( sal_Int32 nRKValue ) +{ + union + { + double fVal; + sal_math_Double smD; + }; + fVal = 0.0; + + if( ::get_flag( nRKValue, EXC_RK_INTFLAG ) ) + { + sal_Int32 nTemp = nRKValue >> 2; + ::set_flag< sal_Int32 >( nTemp, 0xE0000000, nRKValue < 0 ); + fVal = nTemp; + } + else + { + smD.w32_parts.msw = nRKValue & EXC_RK_VALUEMASK; + } + + if( ::get_flag( nRKValue, EXC_RK_100FLAG ) ) + fVal /= 100.0; + + return fVal; +} + +bool XclTools::GetRKFromDouble( sal_Int32& rnRKValue, double fValue ) +{ + double fFrac, fInt; + + // integer + fFrac = modf( fValue, &fInt ); + if( (fFrac == 0.0) && (fInt >= -536870912.0) && (fInt <= 536870911.0) ) // 2^29 + { + rnRKValue + = static_cast<sal_Int32>( + static_cast<sal_uInt32>(static_cast<sal_Int32>(fInt)) << 2) + | EXC_RK_INT; + return true; + } + + // integer/100 + fFrac = modf( fValue * 100.0, &fInt ); + if( (fFrac == 0.0) && (fInt >= -536870912.0) && (fInt <= 536870911.0) ) + { + rnRKValue + = static_cast<sal_Int32>( + static_cast<sal_uInt32>(static_cast<sal_Int32>(fInt)) << 2) + | EXC_RK_INT100; + return true; + } + + // double + return false; +} + +Degree100 XclTools::GetScRotation( sal_uInt16 nXclRot, Degree100 nRotForStacked ) +{ + if( nXclRot == EXC_ROT_STACKED ) + return nRotForStacked; + OSL_ENSURE( nXclRot <= 180, "XclTools::GetScRotation - illegal rotation angle" ); + return Degree100(static_cast< sal_Int32 >( (nXclRot <= 180) ? (100 * ((nXclRot > 90) ? (450 - nXclRot) : nXclRot)) : 0 )); +} + +sal_uInt8 XclTools::GetXclRotation( Degree100 nScRot ) +{ + sal_Int32 nXclRot = nScRot.get() / 100; + if( (0 <= nXclRot) && (nXclRot <= 90) ) + return static_cast< sal_uInt8 >( nXclRot ); + if( nXclRot < 180 ) + return static_cast< sal_uInt8 >( 270 - nXclRot ); + if( nXclRot < 270 ) + return static_cast< sal_uInt8 >( nXclRot - 180 ); + if( nXclRot < 360 ) + return static_cast< sal_uInt8 >( 450 - nXclRot ); + return 0; +} + +sal_uInt8 XclTools::GetXclRotFromOrient( sal_uInt8 nXclOrient ) +{ + switch( nXclOrient ) + { + case EXC_ORIENT_NONE: return EXC_ROT_NONE; + case EXC_ORIENT_STACKED: return EXC_ROT_STACKED; + case EXC_ORIENT_90CCW: return EXC_ROT_90CCW; + case EXC_ORIENT_90CW: return EXC_ROT_90CW; + default: OSL_FAIL( "XclTools::GetXclRotFromOrient - unknown text orientation" ); + } + return EXC_ROT_NONE; +} + +sal_uInt8 XclTools::GetXclOrientFromRot( sal_uInt16 nXclRot ) +{ + if( nXclRot == EXC_ROT_STACKED ) + return EXC_ORIENT_STACKED; + OSL_ENSURE( nXclRot <= 180, "XclTools::GetXclOrientFromRot - unknown text rotation" ); + if( (45 < nXclRot) && (nXclRot <= 90) ) + return EXC_ORIENT_90CCW; + if( (135 < nXclRot) && (nXclRot <= 180) ) + return EXC_ORIENT_90CW; + return EXC_ORIENT_NONE; +} + +sal_uInt8 XclTools::GetXclErrorCode( FormulaError nScError ) +{ + switch( nScError ) + { + case FormulaError::IllegalArgument: return EXC_ERR_VALUE; + case FormulaError::IllegalFPOperation: return EXC_ERR_NUM; // maybe DIV/0 or NUM... + case FormulaError::DivisionByZero: return EXC_ERR_DIV0; + case FormulaError::IllegalParameter: return EXC_ERR_VALUE; + case FormulaError::PairExpected: return EXC_ERR_VALUE; + case FormulaError::OperatorExpected: return EXC_ERR_VALUE; + case FormulaError::VariableExpected: return EXC_ERR_VALUE; + case FormulaError::ParameterExpected: return EXC_ERR_VALUE; + case FormulaError::NoValue: return EXC_ERR_VALUE; + case FormulaError::CircularReference: return EXC_ERR_VALUE; + case FormulaError::NoCode: return EXC_ERR_NULL; + case FormulaError::NoRef: return EXC_ERR_REF; + case FormulaError::NoName: return EXC_ERR_NAME; + case FormulaError::NoAddin: return EXC_ERR_NAME; + case FormulaError::NoMacro: return EXC_ERR_NAME; + case FormulaError::NotAvailable: return EXC_ERR_NA; + default: break; + } + return EXC_ERR_NA; +} + +FormulaError XclTools::GetScErrorCode( sal_uInt8 nXclError ) +{ + switch( nXclError ) + { + case EXC_ERR_NULL: return FormulaError::NoCode; + case EXC_ERR_DIV0: return FormulaError::DivisionByZero; + case EXC_ERR_VALUE: return FormulaError::NoValue; + case EXC_ERR_REF: return FormulaError::NoRef; + case EXC_ERR_NAME: return FormulaError::NoName; + case EXC_ERR_NUM: return FormulaError::IllegalFPOperation; + case EXC_ERR_NA: return FormulaError::NotAvailable; + default: OSL_FAIL( "XclTools::GetScErrorCode - unknown error code" ); + } + return FormulaError::NotAvailable; +} + +double XclTools::ErrorToDouble( sal_uInt8 nXclError ) +{ + return CreateDoubleError(GetScErrorCode( nXclError )); +} + +XclBoolError XclTools::ErrorToEnum( double& rfDblValue, bool bErrOrBool, sal_uInt8 nValue ) +{ + XclBoolError eType; + if( bErrOrBool ) + { + // error value + switch( nValue ) + { + case EXC_ERR_NULL: eType = xlErrNull; break; + case EXC_ERR_DIV0: eType = xlErrDiv0; break; + case EXC_ERR_VALUE: eType = xlErrValue; break; + case EXC_ERR_REF: eType = xlErrRef; break; + case EXC_ERR_NAME: eType = xlErrName; break; + case EXC_ERR_NUM: eType = xlErrNum; break; + case EXC_ERR_NA: eType = xlErrNA; break; + default: eType = xlErrUnknown; + } + rfDblValue = 0.0; + } + else + { + // Boolean value + eType = nValue ? xlErrTrue : xlErrFalse; + rfDblValue = nValue ? 1.0 : 0.0; + } + return eType; +} + +template <typename N> static N to(double f) { return limit_cast<N>(f + 0.5); } + +sal_uInt16 XclTools::GetTwipsFromInch( double fInches ) +{ + return to<sal_uInt16>(o3tl::convert(fInches, o3tl::Length::in, o3tl::Length::twip)); +} + +sal_uInt16 XclTools::GetTwipsFromHmm( sal_Int32 nHmm ) +{ + return limit_cast<sal_uInt16>(o3tl::convert(nHmm, o3tl::Length::mm100, o3tl::Length::twip)); +} + +double XclTools::GetInchFromTwips( sal_Int32 nTwips ) +{ + return o3tl::convert<double>(nTwips, o3tl::Length::twip, o3tl::Length::in); +} + +double XclTools::GetInchFromHmm( sal_Int32 nHmm ) +{ + return o3tl::convert<double>(nHmm, o3tl::Length::mm100, o3tl::Length::in); +} + +sal_Int32 XclTools::GetHmmFromInch( double fInches ) +{ + return to<sal_Int32>(o3tl::convert(fInches, o3tl::Length::in, o3tl::Length::mm100)); +} + +sal_Int32 XclTools::GetHmmFromTwips( sal_Int32 nTwips ) +{ + return limit_cast<sal_Int32>(o3tl::convert(nTwips, o3tl::Length::twip, o3tl::Length::mm100)); +} + +sal_uInt16 XclTools::GetScColumnWidth( sal_uInt16 nXclWidth, tools::Long nScCharWidth ) +{ + double fScWidth = static_cast< double >( nXclWidth ) / 256.0 * nScCharWidth - 0.5; + return limit_cast< sal_uInt16 >( fScWidth ); +} + +sal_uInt16 XclTools::GetXclColumnWidth( sal_uInt16 nScWidth, tools::Long nScCharWidth ) +{ + double fXclWidth = ( static_cast< double >( nScWidth ) + 0.5 ) * 256.0 / nScCharWidth; + return limit_cast< sal_uInt16 >( fXclWidth ); +} + +// takes font height in twips (1/20 pt = 1/1440 in) +// returns correction value in 1/256th of *digit width* of default font +double XclTools::GetXclDefColWidthCorrection( tools::Long nXclDefFontHeight ) +{ + // Excel uses *max digit width of default font* (W) as cell width unit. Also it has 5-pixel + // "correction" to cell widths (ECMA-376-1:2016 18.3.1.81): each cell has 1-pixel padding, then + // 3 pixels for the border (which may be 1-pixel - hairline - then it will have 2 additional + // 1-pixel spacings from each side; or e.g. 2 hairlines with 1-pixel spacing in the middle; or + // thick 3-pixel). Obviously, correction size entirely depends on pixel size (and it is actually + // different in Excel on monitors with different resolution). Thus actual (displayed/printed) + // cell widths consist of X*W+5px; stored in file is the X (or X*256 if 1/256th of digit width + // units are used) value. + // This formula apparently converts this 5-pixel correction to 1/256th of digit width units. + // Looks like it is created from + // + // 5 * 256 * 1440 * 2.1333 / (96 * max(N-15, 60)) + 50.0 + // + // where 5 - pixel correction; 256 - used to produce 1/256th of digit width; 1440 - used to + // convert font height N (in twips) to inches; 2.1333 - an (empirical?) quotient to convert + // font *height* into digit *width*; 96 - "standard" monitor resolution (DPI). + // Additionally the formula uses 15 (of unknown origin), 60 (minimal font height 3 pt), and + // 50.0 (also of unknown origin). + // + // TODO: convert this to take font digit width directly (and possibly DPI?), to avoid guessing + // the digit width and pixel size. Or DPI might stay 96, to not follow Excel dependency on DPI + // in addition to used font, and have absolute size of the correction fixed 5/96 in. + return 40960.0 / ::std::max( nXclDefFontHeight - 15, tools::Long(60) ) + 50.0; +} + +// formatting + +Color XclTools::GetPatternColor( const Color& rPattColor, const Color& rBackColor, sal_uInt16 nXclPattern ) +{ + // 0x00 == 0% transparence (full rPattColor) + // 0x80 == 100% transparence (full rBackColor) + static const sal_uInt8 pnRatioTable[] = + { + 0x80, 0x00, 0x40, 0x20, 0x60, 0x40, 0x40, 0x40, // 00 - 07 + 0x40, 0x40, 0x20, 0x60, 0x60, 0x60, 0x60, 0x48, // 08 - 15 + 0x50, 0x70, 0x78 // 16 - 18 + }; + return (nXclPattern < SAL_N_ELEMENTS( pnRatioTable )) ? + ScfTools::GetMixedColor( rPattColor, rBackColor, pnRatioTable[ nXclPattern ] ) : rPattColor; +} + +// text encoding + +namespace { + +const struct XclCodePageEntry +{ + sal_uInt16 mnCodePage; + rtl_TextEncoding meTextEnc; +} +pCodePageTable[] = +{ + { 437, RTL_TEXTENCODING_IBM_437 }, // OEM US +// { 720, RTL_TEXTENCODING_IBM_720 }, // OEM Arabic + { 737, RTL_TEXTENCODING_IBM_737 }, // OEM Greek + { 775, RTL_TEXTENCODING_IBM_775 }, // OEM Baltic + { 850, RTL_TEXTENCODING_IBM_850 }, // OEM Latin I + { 852, RTL_TEXTENCODING_IBM_852 }, // OEM Latin II (Central European) + { 855, RTL_TEXTENCODING_IBM_855 }, // OEM Cyrillic + { 857, RTL_TEXTENCODING_IBM_857 }, // OEM Turkish +// { 858, RTL_TEXTENCODING_IBM_858 }, // OEM Multilingual Latin I with Euro + { 860, RTL_TEXTENCODING_IBM_860 }, // OEM Portuguese + { 861, RTL_TEXTENCODING_IBM_861 }, // OEM Icelandic + { 862, RTL_TEXTENCODING_IBM_862 }, // OEM Hebrew + { 863, RTL_TEXTENCODING_IBM_863 }, // OEM Canadian (French) + { 864, RTL_TEXTENCODING_IBM_864 }, // OEM Arabic + { 865, RTL_TEXTENCODING_IBM_865 }, // OEM Nordic + { 866, RTL_TEXTENCODING_IBM_866 }, // OEM Cyrillic (Russian) + { 869, RTL_TEXTENCODING_IBM_869 }, // OEM Greek (Modern) + { 874, RTL_TEXTENCODING_MS_874 }, // MS Windows Thai + { 932, RTL_TEXTENCODING_MS_932 }, // MS Windows Japanese Shift-JIS + { 936, RTL_TEXTENCODING_MS_936 }, // MS Windows Chinese Simplified GBK + { 949, RTL_TEXTENCODING_MS_949 }, // MS Windows Korean (Wansung) + { 950, RTL_TEXTENCODING_MS_950 }, // MS Windows Chinese Traditional BIG5 + { 1200, RTL_TEXTENCODING_DONTKNOW }, // Unicode (BIFF8) - return *_DONTKNOW to preserve old code page + { 1250, RTL_TEXTENCODING_MS_1250 }, // MS Windows Latin II (Central European) + { 1251, RTL_TEXTENCODING_MS_1251 }, // MS Windows Cyrillic + { 1252, RTL_TEXTENCODING_MS_1252 }, // MS Windows Latin I (BIFF4-BIFF8) + { 1253, RTL_TEXTENCODING_MS_1253 }, // MS Windows Greek + { 1254, RTL_TEXTENCODING_MS_1254 }, // MS Windows Turkish + { 1255, RTL_TEXTENCODING_MS_1255 }, // MS Windows Hebrew + { 1256, RTL_TEXTENCODING_MS_1256 }, // MS Windows Arabic + { 1257, RTL_TEXTENCODING_MS_1257 }, // MS Windows Baltic + { 1258, RTL_TEXTENCODING_MS_1258 }, // MS Windows Vietnamese + { 1361, RTL_TEXTENCODING_MS_1361 }, // MS Windows Korean (Johab) + { 10000, RTL_TEXTENCODING_APPLE_ROMAN }, // Apple Roman + { 32768, RTL_TEXTENCODING_APPLE_ROMAN }, // Apple Roman + { 32769, RTL_TEXTENCODING_MS_1252 } // MS Windows Latin I (BIFF2-BIFF3) +}; +const XclCodePageEntry* const pCodePageTableEnd = std::end(pCodePageTable); + +struct XclCodePageEntry_CPPred +{ + explicit XclCodePageEntry_CPPred( sal_uInt16 nCodePage ) : mnCodePage( nCodePage ) {} + bool operator()( const XclCodePageEntry& rEntry ) const { return rEntry.mnCodePage == mnCodePage; } + sal_uInt16 mnCodePage; +}; + +struct XclCodePageEntry_TEPred +{ + explicit XclCodePageEntry_TEPred( rtl_TextEncoding eTextEnc ) : meTextEnc( eTextEnc ) {} + bool operator()( const XclCodePageEntry& rEntry ) const { return rEntry.meTextEnc == meTextEnc; } + rtl_TextEncoding meTextEnc; +}; + +} // namespace + +rtl_TextEncoding XclTools::GetTextEncoding( sal_uInt16 nCodePage ) +{ + const XclCodePageEntry* pEntry = ::std::find_if( pCodePageTable, pCodePageTableEnd, XclCodePageEntry_CPPred( nCodePage ) ); + if( pEntry == pCodePageTableEnd ) + { + SAL_WARN("sc", "XclTools::GetTextEncoding - unknown code page: 0x" << std::hex << nCodePage ); + return RTL_TEXTENCODING_DONTKNOW; + } + return pEntry->meTextEnc; +} + +sal_uInt16 XclTools::GetXclCodePage( rtl_TextEncoding eTextEnc ) +{ + if( eTextEnc == RTL_TEXTENCODING_UNICODE ) + return 1200; // for BIFF8 + + const XclCodePageEntry* pEntry = ::std::find_if( pCodePageTable, pCodePageTableEnd, XclCodePageEntry_TEPred( eTextEnc ) ); + if( pEntry == pCodePageTableEnd ) + { + SAL_WARN("sc", "XclTools::GetXclCodePage - unsupported text encoding: 0x" << std::hex << eTextEnc ); + return 1252; + } + return pEntry->mnCodePage; +} + +OUString XclTools::GetXclFontName( const OUString& rFontName ) +{ + // substitute with MS fonts + OUString aNewName = GetSubsFontName(rFontName, SubsFontFlags::ONLYONE | SubsFontFlags::MS); + return aNewName.isEmpty() ? rFontName : aNewName; +} + +// built-in defined names +const char maDefNamePrefix[] = "Excel_BuiltIn_"; /// Prefix for built-in defined names. +const char maDefNamePrefixXml[] = "_xlnm."; /// Prefix for built-in defined names for OOX + +const char* const ppcDefNames[] = +{ + "Consolidate_Area", + "Auto_Open", + "Auto_Close", + "Extract", + "Database", + "Criteria", + "Print_Area", + "Print_Titles", + "Recorder", + "Data_Form", + "Auto_Activate", + "Auto_Deactivate", + "Sheet_Title", + "_FilterDatabase" +}; + +OUString XclTools::GetXclBuiltInDefName( sal_Unicode cBuiltIn ) +{ + OSL_ENSURE( SAL_N_ELEMENTS( ppcDefNames ) == EXC_BUILTIN_UNKNOWN, + "XclTools::GetXclBuiltInDefName - built-in defined name list modified" ); + + if( cBuiltIn < SAL_N_ELEMENTS( ppcDefNames ) ) + return OUString::createFromAscii(ppcDefNames[cBuiltIn]); + else + return OUString::number(cBuiltIn); +} + +OUString XclTools::GetBuiltInDefName( sal_Unicode cBuiltIn ) +{ + return maDefNamePrefix + GetXclBuiltInDefName(cBuiltIn); +} + +OUString XclTools::GetBuiltInDefNameXml( sal_Unicode cBuiltIn ) +{ + return maDefNamePrefixXml + GetXclBuiltInDefName(cBuiltIn); +} + +sal_Unicode XclTools::GetBuiltInDefNameIndex( const OUString& rDefName ) +{ + sal_Int32 nPrefixLen = 0; + if( rDefName.startsWithIgnoreAsciiCase( maDefNamePrefix ) ) + nPrefixLen = strlen(maDefNamePrefix); + else if( rDefName.startsWithIgnoreAsciiCase( maDefNamePrefixXml ) ) + nPrefixLen = strlen(maDefNamePrefixXml); + if( nPrefixLen > 0 ) + { + for( sal_Unicode cBuiltIn = 0; cBuiltIn < EXC_BUILTIN_UNKNOWN; ++cBuiltIn ) + { + OUString aBuiltInName(GetXclBuiltInDefName(cBuiltIn)); + sal_Int32 nBuiltInLen = aBuiltInName.getLength(); + if( rDefName.matchIgnoreAsciiCase( aBuiltInName, nPrefixLen ) ) + { + // name can be followed by underline or space character + sal_Int32 nNextCharPos = nPrefixLen + nBuiltInLen; + sal_Unicode cNextChar = (rDefName.getLength() > nNextCharPos) ? rDefName[nNextCharPos] : '\0'; + if( (cNextChar == '\0') || (cNextChar == ' ') || (cNextChar == '_') ) + return cBuiltIn; + } + } + } + return EXC_BUILTIN_UNKNOWN; +} + +// built-in style names + +const char maStyleNamePrefix1[] = "Excel_BuiltIn_"; /// Prefix for built-in cell style names. +const char maStyleNamePrefix2[] = "Excel Built-in "; /// Prefix for built-in cell style names from OOX filter. + +const char* const ppcStyleNames[] = +{ + "", // "Normal" not used directly, but localized "Default" + "RowLevel_", // outline level will be appended + "ColumnLevel_", // outline level will be appended + "Comma", + "Currency", + "Percent", + "Comma_0", + "Currency_0", + "Hyperlink", + "Followed_Hyperlink" +}; + +OUString XclTools::GetBuiltInStyleName( sal_uInt8 nStyleId, std::u16string_view rName, sal_uInt8 nLevel ) +{ + OUString aStyleName; + + if( nStyleId == EXC_STYLE_NORMAL ) // "Normal" becomes "Default" style + { + aStyleName = ScResId( STR_STYLENAME_STANDARD ); + } + else + { + OUStringBuffer aBuf(maStyleNamePrefix1); + if( nStyleId < SAL_N_ELEMENTS( ppcStyleNames ) ) + aBuf.appendAscii(ppcStyleNames[nStyleId]); + else if (!rName.empty()) + aBuf.append(rName); + else + aBuf.append(static_cast<sal_Int32>(nStyleId)); + + if( (nStyleId == EXC_STYLE_ROWLEVEL) || (nStyleId == EXC_STYLE_COLLEVEL) ) + aBuf.append(static_cast<sal_Int32>(nLevel+1)); + + aStyleName = aBuf.makeStringAndClear(); + } + + return aStyleName; +} + +bool XclTools::IsBuiltInStyleName( const OUString& rStyleName, sal_uInt8* pnStyleId, sal_Int32* pnNextChar ) +{ + // "Default" becomes "Normal" + if (rStyleName == ScResId(STR_STYLENAME_STANDARD)) + { + if( pnStyleId ) *pnStyleId = EXC_STYLE_NORMAL; + if( pnNextChar ) *pnNextChar = rStyleName.getLength(); + return true; + } + + // try the other built-in styles + sal_uInt8 nFoundId = 0; + sal_Int32 nNextChar = 0; + + sal_Int32 nPrefixLen = 0; + if( rStyleName.startsWithIgnoreAsciiCase( maStyleNamePrefix1 ) ) + nPrefixLen = strlen(maStyleNamePrefix1); + else if( rStyleName.startsWithIgnoreAsciiCase( maStyleNamePrefix2 ) ) + nPrefixLen = strlen(maStyleNamePrefix2); + if( nPrefixLen > 0 ) + { + for( sal_uInt8 nId = 0; nId < SAL_N_ELEMENTS( ppcStyleNames ); ++nId ) + { + if( nId != EXC_STYLE_NORMAL ) + { + OUString aShortName = OUString::createFromAscii(ppcStyleNames[nId]); + if( rStyleName.matchIgnoreAsciiCase( aShortName, nPrefixLen ) && + (nNextChar < nPrefixLen + aShortName.getLength())) + { + nFoundId = nId; + nNextChar = nPrefixLen + aShortName.getLength(); + } + } + } + } + + if( nNextChar > 0 ) + { + if( pnStyleId ) *pnStyleId = nFoundId; + if( pnNextChar ) *pnNextChar = nNextChar; + return true; + } + + if( pnStyleId ) *pnStyleId = EXC_STYLE_USERDEF; + if( pnNextChar ) *pnNextChar = 0; + return nPrefixLen > 0; // also return true for unknown built-in styles +} + +bool XclTools::GetBuiltInStyleId( sal_uInt8& rnStyleId, sal_uInt8& rnLevel, const OUString& rStyleName ) +{ + sal_uInt8 nStyleId; + sal_Int32 nNextChar; + if( IsBuiltInStyleName( rStyleName, &nStyleId, &nNextChar ) && (nStyleId != EXC_STYLE_USERDEF) ) + { + if( (nStyleId == EXC_STYLE_ROWLEVEL) || (nStyleId == EXC_STYLE_COLLEVEL) ) + { + std::u16string_view aLevel = rStyleName.subView(nNextChar); + sal_Int32 nLevel = o3tl::toInt32(aLevel); + if (std::u16string_view(OUString::number(nLevel)) == aLevel + && nLevel > 0 && nLevel <= EXC_STYLE_LEVELCOUNT) + { + rnStyleId = nStyleId; + rnLevel = static_cast< sal_uInt8 >( nLevel - 1 ); + return true; + } + } + else if( rStyleName.getLength() == nNextChar ) + { + rnStyleId = nStyleId; + rnLevel = EXC_STYLE_NOLEVEL; + return true; + } + } + + rnStyleId = EXC_STYLE_USERDEF; + rnLevel = EXC_STYLE_NOLEVEL; + return false; +} + +// conditional formatting style names + +const char maCFStyleNamePrefix1[] = "Excel_CondFormat_"; /// Prefix for cond. formatting style names. +const char maCFStyleNamePrefix2[] = "ConditionalStyle_"; /// Prefix for cond. formatting style names from OOX filter. +const char maCFStyleNamePrefix3[] = "ExtConditionalStyle_"; + +OUString XclTools::GetCondFormatStyleName( SCTAB nScTab, sal_Int32 nFormat, sal_uInt16 nCondition ) +{ + return maCFStyleNamePrefix1 + + OUString::number(static_cast<sal_Int32>(nScTab+1)) + + "_" + + OUString::number(static_cast<sal_Int32>(nFormat+1)) + + "_" + + OUString::number(static_cast<sal_Int32>(nCondition+1)); +} + +bool XclTools::IsCondFormatStyleName( const OUString& rStyleName ) +{ + if( rStyleName.startsWithIgnoreAsciiCase( maCFStyleNamePrefix1 ) ) + return true; + + if( rStyleName.startsWithIgnoreAsciiCase( maCFStyleNamePrefix2 ) ) + return true; + + if (rStyleName.startsWithIgnoreAsciiCase(maCFStyleNamePrefix3)) + return true; + + return false; +} + +// stream handling + +void XclTools::SkipSubStream( XclImpStream& rStrm ) +{ + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_EOF; + if( (nRecId == EXC_ID2_BOF) || (nRecId == EXC_ID3_BOF) || (nRecId == EXC_ID4_BOF) || (nRecId == EXC_ID5_BOF) ) + SkipSubStream( rStrm ); + } +} + +// Basic macro names + +const char maSbMacroPrefix[] = "vnd.sun.star.script:"; /// Prefix for StarBasic macros. +const char maSbMacroSuffix[] = "?language=Basic&location=document"; /// Suffix for StarBasic macros. + +OUString XclTools::GetSbMacroUrl( const OUString& rMacroName, SfxObjectShell* pDocShell ) +{ + OSL_ENSURE( !rMacroName.isEmpty(), "XclTools::GetSbMacroUrl - macro name is empty" ); + ::ooo::vba::MacroResolvedInfo aMacroInfo = ::ooo::vba::resolveVBAMacro( pDocShell, rMacroName ); + if( aMacroInfo.mbFound ) + return ::ooo::vba::makeMacroURL( aMacroInfo.msResolvedMacro ); + return OUString(); +} + +OUString XclTools::GetXclMacroName( const OUString& rSbMacroUrl ) +{ + sal_Int32 nSbMacroUrlLen = rSbMacroUrl.getLength(); + sal_Int32 nMacroNameLen = nSbMacroUrlLen - strlen(maSbMacroPrefix) - strlen(maSbMacroSuffix); + if( (nMacroNameLen > 0) && rSbMacroUrl.startsWithIgnoreAsciiCase( maSbMacroPrefix ) && + rSbMacroUrl.endsWithIgnoreAsciiCase( maSbMacroSuffix ) ) + { + sal_Int32 nPrjDot = rSbMacroUrl.indexOf( '.', strlen(maSbMacroPrefix) ) + 1; + return rSbMacroUrl.copy( nPrjDot, nSbMacroUrlLen - nPrjDot - strlen(maSbMacroSuffix) ); + } + return OUString(); +} + +// read/write colors + +XclImpStream& operator>>( XclImpStream& rStrm, Color& rColor ) +{ + sal_uInt8 nR = rStrm.ReaduInt8(); + sal_uInt8 nG = rStrm.ReaduInt8(); + sal_uInt8 nB = rStrm.ReaduInt8(); + rStrm.Ignore( 1 );//nD + rColor = Color( nR, nG, nB ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const Color& rColor ) +{ + return rStrm << rColor.GetRed() << rColor.GetGreen() << rColor.GetBlue() << sal_uInt8( 0 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltracer.cxx b/sc/source/filter/excel/xltracer.cxx new file mode 100644 index 000000000..c6931dbf0 --- /dev/null +++ b/sc/source/filter/excel/xltracer.cxx @@ -0,0 +1,133 @@ +/* -*- 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 <xltracer.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <address.hxx> + +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::beans::PropertyValue; + +XclTracer::XclTracer(std::u16string_view /*rDocUrl*/) + : mbEnabled(false) + , maFirstTimes(eTraceLength, true) +{ +} + +XclTracer::~XclTracer() {} + +void XclTracer::ProcessTraceOnce(XclTracerId eProblem) +{ + if (mbEnabled && maFirstTimes[eProblem]) + { + maFirstTimes[eProblem] = false; + } +} + +void XclTracer::TraceInvalidAddress(const ScAddress& rPos, const ScAddress& rMaxPos) +{ + TraceInvalidRow(rPos.Row(), rMaxPos.Row()); + TraceInvalidTab(rPos.Tab(), rMaxPos.Tab()); +} + +void XclTracer::TraceInvalidRow(sal_uInt32 nRow, sal_uInt32 nMaxRow) +{ + if (nRow > nMaxRow) + ProcessTraceOnce(eRowLimitExceeded); +} + +void XclTracer::TraceInvalidTab(SCTAB nTab, SCTAB nMaxTab) +{ + if (nTab > nMaxTab) + ProcessTraceOnce(eTabLimitExceeded); +} + +void XclTracer::TracePrintRange() { ProcessTraceOnce(ePrintRange); } + +void XclTracer::TraceDates(sal_uInt16 nNumFmt) +{ + // Short Date = 14 and Short Date+Time = 22 + if (nNumFmt == 14 || nNumFmt == 22) + ProcessTraceOnce(eShortDate); +} + +void XclTracer::TraceBorderLineStyle(bool bBorderLineStyle) +{ + if (bBorderLineStyle) + ProcessTraceOnce(eBorderLineStyle); +} + +void XclTracer::TraceFillPattern(bool bFillPattern) +{ + if (bFillPattern) + ProcessTraceOnce(eFillPattern); +} + +void XclTracer::TraceFormulaMissingArg() +{ + // missing parameter in Formula record + ProcessTraceOnce(eFormulaMissingArg); +} + +void XclTracer::TracePivotDataSource(bool bExternal) +{ + if (bExternal) + ProcessTraceOnce(ePivotDataSource); +} + +void XclTracer::TracePivotChartExists() +{ + // Pivot Charts not currently displayed. + ProcessTraceOnce(ePivotChartExists); +} + +void XclTracer::TraceChartUnKnownType() { ProcessTraceOnce(eChartUnKnownType); } + +void XclTracer::TraceChartOnlySheet() { ProcessTraceOnce(eChartOnlySheet); } + +void XclTracer::TraceChartDataTable() +{ + // Data table is not supported. + ProcessTraceOnce(eChartDataTable); +} + +void XclTracer::TraceChartLegendPosition() +{ + // If position is set to "not docked or inside the plot area" then + // we cannot guarantee the legend position. + ProcessTraceOnce(eChartLegendPosition); +} + +void XclTracer::TraceUnsupportedObjects() +{ + // Called from Excel 5.0 - limited Graphical object support. + ProcessTraceOnce(eUnsupportedObject); +} + +void XclTracer::TraceObjectNotPrintable() { ProcessTraceOnce(eObjectNotPrintable); } + +void XclTracer::TraceDVType(bool bType) +{ + // Custom types work if 'Data->validity dialog' is not OKed. + if (bType) + ProcessTraceOnce(eDVType); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlview.cxx b/sc/source/filter/excel/xlview.cxx new file mode 100644 index 000000000..b5768e2df --- /dev/null +++ b/sc/source/filter/excel/xlview.cxx @@ -0,0 +1,103 @@ +/* -*- 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 <xlview.hxx> +#include <osl/diagnose.h> + +// Structs ==================================================================== + +XclDocViewData::XclDocViewData() : + mnWinX( 0 ), + mnWinY( 0 ), + mnWinWidth( 0 ), + mnWinHeight( 0 ), + mnFlags( EXC_WIN1_HOR_SCROLLBAR | EXC_WIN1_VER_SCROLLBAR | EXC_WIN1_TABBAR ), + mnDisplXclTab( 0 ), + mnFirstVisXclTab( 0 ), + mnXclSelectCnt( 1 ), + mnTabBarWidth( 600 ) +{ +} + +XclTabViewData::XclTabViewData() : + maFirstXclPos( ScAddress::UNINITIALIZED ), + maSecondXclPos( ScAddress::UNINITIALIZED ) +{ + SetDefaults(); +} + +XclTabViewData::~XclTabViewData() +{ +} + +void XclTabViewData::SetDefaults() +{ + maSelMap.clear(); + maGridColor = COL_AUTO; + maFirstXclPos.Set( 0, 0 ); + maSecondXclPos.Set( 0, 0 ); + mnSplitX = mnSplitY = 0; + mnNormalZoom = EXC_WIN2_NORMALZOOM_DEF; + mnPageZoom = EXC_WIN2_PAGEZOOM_DEF; + mnCurrentZoom = 0; // default to mnNormalZoom or mnPageZoom + mnActivePane = EXC_PANE_TOPLEFT; + mbSelected = mbDisplayed = false; + mbMirrored = false; + mbFrozenPanes = false; + mbPageMode = false; + mbDefGridColor = true; + mbShowFormulas = false; + mbShowGrid = mbShowHeadings = mbShowZeros = mbShowOutline = true; + maTabBgColor = COL_AUTO; + mnTabBgColorId = 0; +} + +bool XclTabViewData::IsSplit() const +{ + return (mnSplitX > 0) || (mnSplitY > 0); +} + +bool XclTabViewData::HasPane( sal_uInt8 nPaneId ) const +{ + switch( nPaneId ) + { + case EXC_PANE_BOTTOMRIGHT: return (mnSplitX > 0) && (mnSplitY > 0); + case EXC_PANE_TOPRIGHT: return mnSplitX > 0; + case EXC_PANE_BOTTOMLEFT: return mnSplitY > 0; + case EXC_PANE_TOPLEFT: return true; + } + OSL_FAIL( "XclExpPane::HasPane - wrong pane ID" ); + return false; +} + +const XclSelectionData* XclTabViewData::GetSelectionData( sal_uInt8 nPane ) const +{ + XclSelectionMap::const_iterator aIt = maSelMap.find( nPane ); + return (aIt == maSelMap.end()) ? nullptr : aIt->second.get(); +} + +XclSelectionData& XclTabViewData::CreateSelectionData( sal_uInt8 nPane ) +{ + XclSelectionDataRef& rxSelData = maSelMap[ nPane ]; + if( !rxSelData ) + rxSelData = std::make_shared<XclSelectionData>(); + return *rxSelData; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |