diff options
Diffstat (limited to 'oox/source/xls')
55 files changed, 35224 insertions, 0 deletions
diff --git a/oox/source/xls/addressconverter.cxx b/oox/source/xls/addressconverter.cxx new file mode 100644 index 000000000000..5a379000fe9e --- /dev/null +++ b/oox/source/xls/addressconverter.cxx @@ -0,0 +1,784 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/addressconverter.hxx" +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/strbuf.hxx> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/sheet/XCellRangeAddressable.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/biffoutputstream.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::rtl::OStringBuffer; +using ::rtl::OUStringToOString; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::XCellRangeAddressable; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +//! TODO: this limit may be changed +const sal_Int16 API_MAXTAB = 255; + +const sal_Int32 OOX_MAXCOL = static_cast< sal_Int32 >( (1 << 14) - 1 ); +const sal_Int32 OOX_MAXROW = static_cast< sal_Int32 >( (1 << 20) - 1 ); +const sal_Int16 OOX_MAXTAB = static_cast< sal_Int16 >( (1 << 15) - 1 ); + +const sal_Int32 BIFF2_MAXCOL = 255; +const sal_Int32 BIFF2_MAXROW = 16383; +const sal_Int16 BIFF2_MAXTAB = 0; + +const sal_Int32 BIFF3_MAXCOL = BIFF2_MAXCOL; +const sal_Int32 BIFF3_MAXROW = BIFF2_MAXROW; +const sal_Int16 BIFF3_MAXTAB = BIFF2_MAXTAB; + +const sal_Int32 BIFF4_MAXCOL = BIFF3_MAXCOL; +const sal_Int32 BIFF4_MAXROW = BIFF3_MAXROW; +const sal_Int16 BIFF4_MAXTAB = 32767; + +const sal_Int32 BIFF5_MAXCOL = BIFF4_MAXCOL; +const sal_Int32 BIFF5_MAXROW = BIFF4_MAXROW; +const sal_Int16 BIFF5_MAXTAB = BIFF4_MAXTAB; + +const sal_Int32 BIFF8_MAXCOL = BIFF5_MAXCOL; +const sal_Int32 BIFF8_MAXROW = 65535; +const sal_Int16 BIFF8_MAXTAB = BIFF5_MAXTAB; + +const sal_Unicode BIFF_URL_DRIVE = '\x01'; /// DOS drive letter or UNC path. +const sal_Unicode BIFF_URL_ROOT = '\x02'; /// Root directory of current drive. +const sal_Unicode BIFF_URL_SUBDIR = '\x03'; /// Subdirectory delimiter. +const sal_Unicode BIFF_URL_PARENT = '\x04'; /// Parent directory. +const sal_Unicode BIFF_URL_RAW = '\x05'; /// Unencoded URL. +const sal_Unicode BIFF_URL_INSTALL = '\x06'; /// Application installation directory. +const sal_Unicode BIFF_URL_INSTALL2 = '\x07'; /// Alternative application installation directory. +const sal_Unicode BIFF_URL_LIBRARY = '\x08'; /// Library directory in application installation. +const sal_Unicode BIFF4_URL_SHEET = '\x09'; /// BIFF4 internal sheet. +const sal_Unicode BIFF_URL_UNC = '@'; /// UNC path root. + +const sal_Unicode BIFF_DCON_ENCODED = '\x01'; /// First character of an encoded path from DCON* records. +const sal_Unicode BIFF_DCON_INTERN = '\x02'; /// First character of an encoded sheet name from DCON* records. + + +inline sal_uInt16 lclGetBiffAddressSize( bool bCol16Bit, bool bRow32Bit ) +{ + return (bCol16Bit ? 2 : 1) + (bRow32Bit ? 4 : 2); +} + +inline sal_uInt16 lclGetBiffRangeSize( bool bCol16Bit, bool bRow32Bit ) +{ + return 2 * lclGetBiffAddressSize( bCol16Bit, bRow32Bit ); +} + +} // namespace + +// ============================================================================ +// ============================================================================ + +CellAddress ApiCellRangeList::getBaseAddress() const +{ + if( empty() ) + return CellAddress(); + return CellAddress( front().Sheet, front().StartColumn, front().StartRow ); +} + +// ============================================================================ + +void BinAddress::read( RecordInputStream& rStrm ) +{ + rStrm >> mnRow >> mnCol; +} + +void BinAddress::read( BiffInputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) +{ + mnRow = bRow32Bit ? rStrm.readInt32() : rStrm.readuInt16(); + mnCol = bCol16Bit ? rStrm.readuInt16() : rStrm.readuInt8(); +} + +void BinAddress::write( BiffOutputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) const +{ + if( bRow32Bit ) + rStrm << mnRow; + else + rStrm << static_cast< sal_uInt16 >( mnRow ); + if( bCol16Bit ) + rStrm << static_cast< sal_uInt16 >( mnCol ); + else + rStrm << static_cast< sal_uInt8 >( mnCol ); +} + +// ============================================================================ + +bool BinRange::contains( const BinAddress& rAddr ) const +{ + return (maFirst.mnCol <= rAddr.mnCol) && (rAddr.mnCol <= maLast.mnCol) && + (maFirst.mnRow <= rAddr.mnRow) && (rAddr.mnRow <= maLast.mnRow); +} + +void BinRange::read( RecordInputStream& rStrm ) +{ + rStrm >> maFirst.mnRow >> maLast.mnRow >> maFirst.mnCol >> maLast.mnCol; +} + +void BinRange::read( BiffInputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) +{ + maFirst.mnRow = bRow32Bit ? rStrm.readInt32() : rStrm.readuInt16(); + maLast.mnRow = bRow32Bit ? rStrm.readInt32() : rStrm.readuInt16(); + maFirst.mnCol = bCol16Bit ? rStrm.readuInt16() : rStrm.readuInt8(); + maLast.mnCol = bCol16Bit ? rStrm.readuInt16() : rStrm.readuInt8(); +} + +void BinRange::write( BiffOutputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) const +{ + if( bRow32Bit ) + rStrm << maFirst.mnRow << maLast.mnRow; + else + rStrm << static_cast< sal_uInt16 >( maFirst.mnRow ) << static_cast< sal_uInt16 >( maLast.mnRow ); + if( bCol16Bit ) + rStrm << static_cast< sal_uInt16 >( maFirst.mnCol ) << static_cast< sal_uInt16 >( maLast.mnCol ); + else + rStrm << static_cast< sal_uInt8 >( maFirst.mnCol ) << static_cast< sal_uInt8 >( maLast.mnCol ); +} + +// ============================================================================ + +BinRange BinRangeList::getEnclosingRange() const +{ + BinRange aRange; + if( !empty() ) + { + const_iterator aIt = begin(), aEnd = end(); + aRange = *aIt; + for( ++aIt; aIt != aEnd; ++aIt ) + { + aRange.maFirst.mnCol = ::std::min( aRange.maFirst.mnCol, aIt->maFirst.mnCol ); + aRange.maFirst.mnRow = ::std::min( aRange.maFirst.mnRow, aIt->maFirst.mnRow ); + aRange.maLast.mnCol = ::std::max( aRange.maLast.mnCol, aIt->maLast.mnCol ); + aRange.maLast.mnRow = ::std::max( aRange.maLast.mnRow, aIt->maLast.mnRow ); + } + } + return aRange; +} + +void BinRangeList::read( RecordInputStream& rStrm ) +{ + sal_Int32 nCount = rStrm.readInt32(); + resize( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 16 ) ); + for( iterator aIt = begin(), aEnd = end(); aIt != aEnd; ++aIt ) + aIt->read( rStrm ); +} + +void BinRangeList::read( BiffInputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) +{ + sal_uInt16 nCount = rStrm.readuInt16(); + resize( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / lclGetBiffRangeSize( bCol16Bit, bRow32Bit ) ) ); + for( iterator aIt = begin(), aEnd = end(); aIt != aEnd; ++aIt ) + aIt->read( rStrm, bCol16Bit, bRow32Bit ); +} + +void BinRangeList::write( BiffOutputStream& rStrm, bool bCol16Bit, bool bRow32Bit ) const +{ + writeSubList( rStrm, 0, size(), bCol16Bit, bRow32Bit ); +} + +void BinRangeList::writeSubList( BiffOutputStream& rStrm, size_t nBegin, size_t nCount, bool bCol16Bit, bool bRow32Bit ) const +{ + OSL_ENSURE( nBegin <= size(), "BiffRangeList::writeSubList - invalid start position" ); + size_t nEnd = ::std::min< size_t >( nBegin + nCount, size() ); + sal_uInt16 nBiffCount = getLimitedValue< sal_uInt16, size_t >( nEnd - nBegin, 0, SAL_MAX_UINT16 ); + rStrm << nBiffCount; + rStrm.setPortionSize( lclGetBiffRangeSize( bCol16Bit, bRow32Bit ) ); + for( const_iterator aIt = begin() + nBegin, aEnd = begin() + nEnd; aIt != aEnd; ++aIt ) + aIt->write( rStrm, bCol16Bit, bRow32Bit ); +} + +// ============================================================================ +// ============================================================================ + +AddressConverter::AddressConverter( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mbColOverflow( false ), + mbRowOverflow( false ), + mbTabOverflow( false ) +{ + maDConChars.set( 0xFFFF, '\x01', 0xFFFF, '\x02', 0xFFFF ); + switch( getFilterType() ) + { + case FILTER_OOX: + initializeMaxPos( OOX_MAXTAB, OOX_MAXCOL, OOX_MAXROW ); + break; + case FILTER_BIFF: switch( getBiff() ) + { + case BIFF2: + initializeMaxPos( BIFF2_MAXTAB, BIFF2_MAXCOL, BIFF2_MAXROW ); + maLinkChars.set( 0xFFFF, '\x01', '\x02', 0xFFFF, 0xFFFF ); + break; + case BIFF3: + initializeMaxPos( BIFF3_MAXTAB, BIFF3_MAXCOL, BIFF3_MAXROW ); + maLinkChars.set( 0xFFFF, '\x01', '\x02', 0xFFFF, 0xFFFF ); + break; + case BIFF4: + initializeMaxPos( BIFF4_MAXTAB, BIFF4_MAXCOL, BIFF4_MAXROW ); + maLinkChars.set( 0xFFFF, '\x01', '\x02', 0xFFFF, '\x00' ); + break; + case BIFF5: + initializeMaxPos( BIFF5_MAXTAB, BIFF5_MAXCOL, BIFF5_MAXROW ); + maLinkChars.set( '\x04', '\x01', '\x02', '\x03', '\x00' ); + break; + case BIFF8: + initializeMaxPos( BIFF8_MAXTAB, BIFF8_MAXCOL, BIFF8_MAXROW ); + maLinkChars.set( '\x04', '\x01', 0xFFFF, '\x02', '\x00' ); + break; + case BIFF_UNKNOWN: break; + } + break; + case FILTER_UNKNOWN: break; + } +} + +// ---------------------------------------------------------------------------- + +bool AddressConverter::parseOoxAddress2d( + sal_Int32& ornColumn, sal_Int32& ornRow, + const OUString& rString, sal_Int32 nStart, sal_Int32 nLength ) +{ + ornColumn = ornRow = 0; + if( (nStart < 0) || (nStart >= rString.getLength()) || (nLength < 2) ) + return false; + + const sal_Unicode* pcChar = rString.getStr() + nStart; + const sal_Unicode* pcEndChar = pcChar + ::std::min( nLength, rString.getLength() - nStart ); + + enum { STATE_COL, STATE_ROW } eState = STATE_COL; + while( pcChar < pcEndChar ) + { + sal_Unicode cChar = *pcChar; + switch( eState ) + { + case STATE_COL: + { + if( ('a' <= cChar) && (cChar <= 'z') ) + (cChar -= 'a') += 'A'; + if( ('A' <= cChar) && (cChar <= 'Z') ) + { + /* Return, if 1-based column index is already 6 characters + long (12356631 is column index for column AAAAAA). */ + if( ornColumn >= 12356631 ) + return false; + (ornColumn *= 26) += (cChar - 'A' + 1); + } + else if( ornColumn > 0 ) + { + --pcChar; + eState = STATE_ROW; + } + else + return false; + } + break; + + case STATE_ROW: + { + if( ('0' <= cChar) && (cChar <= '9') ) + { + // return, if 1-based row is already 9 digits long + if( ornRow >= 100000000 ) + return false; + (ornRow *= 10) += (cChar - '0'); + } + else + return false; + } + break; + } + ++pcChar; + } + + --ornColumn; + --ornRow; + return (ornColumn >= 0) && (ornRow >= 0); +} + +bool AddressConverter::parseOoxRange2d( + sal_Int32& ornStartColumn, sal_Int32& ornStartRow, + sal_Int32& ornEndColumn, sal_Int32& ornEndRow, + const OUString& rString, sal_Int32 nStart, sal_Int32 nLength ) +{ + ornStartColumn = ornStartRow = ornEndColumn = ornEndRow = 0; + if( (nStart < 0) || (nStart >= rString.getLength()) || (nLength < 2) ) + return false; + + sal_Int32 nEnd = nStart + ::std::min( nLength, rString.getLength() - nStart ); + sal_Int32 nColonPos = rString.indexOf( ':', nStart ); + if( (nStart < nColonPos) && (nColonPos + 1 < nEnd) ) + { + return + parseOoxAddress2d( ornStartColumn, ornStartRow, rString, nStart, nColonPos - nStart ) && + parseOoxAddress2d( ornEndColumn, ornEndRow, rString, nColonPos + 1, nLength - nColonPos - 1 ); + } + + if( parseOoxAddress2d( ornStartColumn, ornStartRow, rString, nStart, nLength ) ) + { + ornEndColumn = ornStartColumn; + ornEndRow = ornStartRow; + return true; + } + + return false; +} + +namespace { + +bool lclAppendUrlChar( OUStringBuffer& orUrl, sal_Unicode cChar, bool bEncodeSpecial ) +{ + // #126855# encode special characters + if( bEncodeSpecial ) switch( cChar ) + { + case '#': orUrl.appendAscii( "%23" ); return true; + case '%': orUrl.appendAscii( "%25" ); return true; + } + orUrl.append( cChar ); + return cChar >= ' '; +} + +} // namespace + +BiffTargetType AddressConverter::parseBiffTargetUrl( + OUString& orClassName, OUString& orTargetUrl, OUString& orSheetName, + const OUString& rBiffTargetUrl, bool bFromDConRec ) +{ + OUStringBuffer aTargetUrl; + OUStringBuffer aSheetName; + // default target type: some URL with/without sheet name, may be overridden below + BiffTargetType eTargetType = BIFF_TARGETTYPE_URL; + const ControlCharacters& rCChars = bFromDConRec ? maDConChars : maLinkChars; + + enum + { + STATE_START, + STATE_ENCODED_PATH_START, /// Start of encoded file path. + STATE_ENCODED_PATH, /// Inside encoded file path. + STATE_ENCODED_DRIVE, /// DOS drive letter or start of UNC path. + STATE_ENCODED_URL, /// Encoded URL, e.g. http links. + STATE_UNENCODED, /// Unencoded URL, could be DDE or OLE. + STATE_DDE_OLE, /// Second part of DDE or OLE link. + STATE_FILENAME, /// File name enclosed in brackets. + STATE_SHEETNAME, /// Sheet name following enclosed file name. + STATE_UNSUPPORTED, /// Unsupported special paths. + STATE_ERROR + } + eState = STATE_START; + + const sal_Unicode* pcChar = rBiffTargetUrl.getStr(); + const sal_Unicode* pcEnd = pcChar + rBiffTargetUrl.getLength(); + for( ; (eState != STATE_ERROR) && (pcChar < pcEnd); ++pcChar ) + { + sal_Unicode cChar = *pcChar; + switch( eState ) + { + case STATE_START: + if( (cChar == rCChars.mcThisWorkbook) || (cChar == rCChars.mcThisSheet) || (cChar == rCChars.mcSameSheet) ) + { + if( pcChar + 1 < pcEnd ) + eState = STATE_ERROR; + if( cChar == rCChars.mcSameSheet ) + eTargetType = BIFF_TARGETTYPE_SAMESHEET; + } + else if( cChar == rCChars.mcExternal ) + eState = (pcChar + 1 < pcEnd) ? STATE_ENCODED_PATH_START : STATE_ERROR; + else if( cChar == rCChars.mcInternal ) + eState = (pcChar + 1 < pcEnd) ? STATE_SHEETNAME : STATE_ERROR; + else + eState = lclAppendUrlChar( aTargetUrl, cChar, true ) ? STATE_UNENCODED : STATE_ERROR; + break; + + case STATE_ENCODED_PATH_START: + if( cChar == BIFF_URL_DRIVE ) + eState = STATE_ENCODED_DRIVE; + else if( cChar == BIFF_URL_ROOT ) + { + aTargetUrl.append( sal_Unicode( '/' ) ); + eState = STATE_ENCODED_PATH; + } + else if( cChar == BIFF_URL_PARENT ) + aTargetUrl.appendAscii( "../" ); + else if( cChar == BIFF_URL_RAW ) + eState = STATE_ENCODED_URL; + else if( cChar == BIFF_URL_INSTALL ) + eState = STATE_UNSUPPORTED; + else if( cChar == BIFF_URL_INSTALL2 ) + eState = STATE_UNSUPPORTED; + else if( cChar == BIFF_URL_LIBRARY ) + { + eState = STATE_ENCODED_PATH; + eTargetType = BIFF_TARGETTYPE_LIBRARY; + } + else if( (getBiff() == BIFF4) && (cChar == BIFF4_URL_SHEET) ) + eState = STATE_SHEETNAME; + else if( cChar == '[' ) + eState = STATE_FILENAME; + else if( lclAppendUrlChar( aTargetUrl, cChar, true ) ) + eState = STATE_ENCODED_PATH; + else + eState = STATE_ERROR; + break; + + case STATE_ENCODED_PATH: + if( cChar == BIFF_URL_SUBDIR ) + aTargetUrl.append( sal_Unicode( '/' ) ); + else if( cChar == '[' ) + eState = STATE_FILENAME; + else if( !lclAppendUrlChar( aTargetUrl, cChar, true ) ) + eState = STATE_ERROR; + break; + + case STATE_ENCODED_DRIVE: + if( cChar == BIFF_URL_UNC ) + { + aTargetUrl.appendAscii( "file://" ); + eState = STATE_ENCODED_PATH; + } + else + { + aTargetUrl.appendAscii( "file:///" ); + eState = lclAppendUrlChar( aTargetUrl, cChar, false ) ? STATE_ENCODED_PATH : STATE_ERROR; + aTargetUrl.appendAscii( ":/" ); + } + break; + + case STATE_ENCODED_URL: + { + sal_Int32 nLength = cChar; + if( nLength + 1 == pcEnd - pcChar ) + aTargetUrl.append( pcChar + 1, nLength ); + else + eState = STATE_ERROR; + } + break; + + case STATE_UNENCODED: + if( cChar == BIFF_URL_SUBDIR ) + { + orClassName = aTargetUrl.makeStringAndClear(); + eState = bFromDConRec ? STATE_ERROR : STATE_DDE_OLE; + eTargetType = BIFF_TARGETTYPE_DDE_OLE; + } + else if( cChar == '[' ) + eState = STATE_FILENAME; + else if( !lclAppendUrlChar( aTargetUrl, cChar, true ) ) + eState = STATE_ERROR; + break; + + case STATE_DDE_OLE: + if( !lclAppendUrlChar( aTargetUrl, cChar, true ) ) + eState = STATE_ERROR; + break; + + case STATE_FILENAME: + if( cChar == ']' ) + eState = STATE_SHEETNAME; + else if( !lclAppendUrlChar( aTargetUrl, cChar, true ) ) + eState = STATE_ERROR; + break; + + case STATE_SHEETNAME: + if( !lclAppendUrlChar( aSheetName, cChar, false ) ) + eState = STATE_ERROR; + break; + + case STATE_UNSUPPORTED: + pcChar = pcEnd - 1; + break; + + case STATE_ERROR: + break; + } + } + + OSL_ENSURE( (eState != STATE_ERROR) && (pcChar == pcEnd), + OStringBuffer( "AddressConverter::parseBiffTargetUrl - parser error in target \"" ). + append( OUStringToOString( rBiffTargetUrl, RTL_TEXTENCODING_UTF8 ) ).append( '"' ).getStr() ); + bool bParserOk = (eState != STATE_ERROR) && (eState != STATE_UNSUPPORTED) && (pcChar == pcEnd); + + if( bParserOk ) + { + orTargetUrl = aTargetUrl.makeStringAndClear(); + orSheetName = aSheetName.makeStringAndClear(); + } + else + { + orClassName = orTargetUrl = orSheetName = OUString(); + } + + return bParserOk ? eTargetType : BIFF_TARGETTYPE_UNKNOWN; +} + +// ---------------------------------------------------------------------------- + +bool AddressConverter::checkCol( sal_Int32 nCol, bool bTrackOverflow ) +{ + bool bValid = (0 <= nCol) && (nCol <= maMaxPos.Column); + if( !bValid && bTrackOverflow ) + mbColOverflow = true; + return bValid; +} + +bool AddressConverter::checkRow( sal_Int32 nRow, bool bTrackOverflow ) +{ + bool bValid = (0 <= nRow) && (nRow <= maMaxPos.Row); + if( !bValid && bTrackOverflow ) + mbRowOverflow = true; + return bValid; +} + +bool AddressConverter::checkTab( sal_Int16 nSheet, bool bTrackOverflow ) +{ + bool bValid = (0 <= nSheet) && (nSheet <= maMaxPos.Sheet); + if( !bValid && bTrackOverflow ) + mbTabOverflow |= (nSheet > maMaxPos.Sheet); // do not warn for deleted refs (-1) + return bValid; +} + +// ---------------------------------------------------------------------------- + +bool AddressConverter::checkCellAddress( const CellAddress& rAddress, bool bTrackOverflow ) +{ + return + checkTab( rAddress.Sheet, bTrackOverflow ) && + checkCol( rAddress.Column, bTrackOverflow ) && + checkRow( rAddress.Row, bTrackOverflow ); +} + +bool AddressConverter::convertToCellAddressUnchecked( CellAddress& orAddress, + const OUString& rString, sal_Int16 nSheet ) +{ + orAddress.Sheet = nSheet; + return parseOoxAddress2d( orAddress.Column, orAddress.Row, rString ); +} + +bool AddressConverter::convertToCellAddress( CellAddress& orAddress, + const OUString& rString, sal_Int16 nSheet, bool bTrackOverflow ) +{ + return + convertToCellAddressUnchecked( orAddress, rString, nSheet ) && + checkCellAddress( orAddress, bTrackOverflow ); +} + +CellAddress AddressConverter::createValidCellAddress( + const OUString& rString, sal_Int16 nSheet, bool bTrackOverflow ) +{ + CellAddress aAddress; + if( !convertToCellAddress( aAddress, rString, nSheet, bTrackOverflow ) ) + { + aAddress.Sheet = getLimitedValue< sal_Int16, sal_Int16 >( nSheet, 0, maMaxPos.Sheet ); + aAddress.Column = ::std::min( aAddress.Column, maMaxPos.Column ); + aAddress.Row = ::std::min( aAddress.Row, maMaxPos.Row ); + } + return aAddress; +} + +void AddressConverter::convertToCellAddressUnchecked( CellAddress& orAddress, + const BinAddress& rBinAddress, sal_Int16 nSheet ) +{ + orAddress.Sheet = nSheet; + orAddress.Column = rBinAddress.mnCol; + orAddress.Row = rBinAddress.mnRow; +} + +bool AddressConverter::convertToCellAddress( CellAddress& orAddress, + const BinAddress& rBinAddress, sal_Int16 nSheet, bool bTrackOverflow ) +{ + convertToCellAddressUnchecked( orAddress, rBinAddress, nSheet ); + return checkCellAddress( orAddress, bTrackOverflow ); +} + +CellAddress AddressConverter::createValidCellAddress( + const BinAddress& rBinAddress, sal_Int16 nSheet, bool bTrackOverflow ) +{ + CellAddress aAddress; + if( !convertToCellAddress( aAddress, rBinAddress, nSheet, bTrackOverflow ) ) + { + aAddress.Sheet = getLimitedValue< sal_Int16, sal_Int16 >( nSheet, 0, maMaxPos.Sheet ); + aAddress.Column = getLimitedValue< sal_Int32, sal_Int32 >( rBinAddress.mnCol, 0, maMaxPos.Column ); + aAddress.Row = getLimitedValue< sal_Int32, sal_Int32 >( rBinAddress.mnRow, 0, maMaxPos.Row ); + } + return aAddress; +} + +// ---------------------------------------------------------------------------- + +bool AddressConverter::checkCellRange( const CellRangeAddress& rRange, bool bAllowOverflow, bool bTrackOverflow ) +{ + return + (checkCol( rRange.EndColumn, bTrackOverflow ) || bAllowOverflow) && // bAllowOverflow after checkCol to track overflow! + (checkRow( rRange.EndRow, bTrackOverflow ) || bAllowOverflow) && // bAllowOverflow after checkRow to track overflow! + checkTab( rRange.Sheet, bTrackOverflow ) && + checkCol( rRange.StartColumn, bTrackOverflow ) && + checkRow( rRange.StartRow, bTrackOverflow ); +} + +bool AddressConverter::validateCellRange( CellRangeAddress& orRange, bool bAllowOverflow, bool bTrackOverflow ) +{ + if( orRange.StartColumn > orRange.EndColumn ) + ::std::swap( orRange.StartColumn, orRange.EndColumn ); + if( orRange.StartRow > orRange.EndRow ) + ::std::swap( orRange.StartRow, orRange.EndRow ); + if( !checkCellRange( orRange, bAllowOverflow, bTrackOverflow ) ) + return false; + if( orRange.EndColumn > maMaxPos.Column ) + orRange.EndColumn = maMaxPos.Column; + if( orRange.EndRow > maMaxPos.Row ) + orRange.EndRow = maMaxPos.Row; + return true; +} + +bool AddressConverter::convertToCellRangeUnchecked( CellRangeAddress& orRange, + const OUString& rString, sal_Int16 nSheet ) +{ + orRange.Sheet = nSheet; + return parseOoxRange2d( orRange.StartColumn, orRange.StartRow, orRange.EndColumn, orRange.EndRow, rString ); +} + +bool AddressConverter::convertToCellRange( CellRangeAddress& orRange, + const OUString& rString, sal_Int16 nSheet, bool bAllowOverflow, bool bTrackOverflow ) +{ + return + convertToCellRangeUnchecked( orRange, rString, nSheet ) && + validateCellRange( orRange, bAllowOverflow, bTrackOverflow ); +} + +void AddressConverter::convertToCellRangeUnchecked( CellRangeAddress& orRange, + const BinRange& rBinRange, sal_Int16 nSheet ) +{ + orRange.Sheet = nSheet; + orRange.StartColumn = rBinRange.maFirst.mnCol; + orRange.StartRow = rBinRange.maFirst.mnRow; + orRange.EndColumn = rBinRange.maLast.mnCol; + orRange.EndRow = rBinRange.maLast.mnRow; +} + +bool AddressConverter::convertToCellRange( CellRangeAddress& orRange, + const BinRange& rBinRange, sal_Int16 nSheet, bool bAllowOverflow, bool bTrackOverflow ) +{ + convertToCellRangeUnchecked( orRange, rBinRange, nSheet ); + return validateCellRange( orRange, bAllowOverflow, bTrackOverflow ); +} + +// ---------------------------------------------------------------------------- + +bool AddressConverter::checkCellRangeList( const ApiCellRangeList& rRanges, bool bAllowOverflow, bool bTrackOverflow ) +{ + for( ApiCellRangeList::const_iterator aIt = rRanges.begin(), aEnd = rRanges.end(); aIt != aEnd; ++aIt ) + if( !checkCellRange( *aIt, bAllowOverflow, bTrackOverflow ) ) + return false; + return true; +} + +void AddressConverter::validateCellRangeList( ApiCellRangeList& orRanges, bool bTrackOverflow ) +{ + for( size_t nIndex = orRanges.size(); nIndex > 0; --nIndex ) + if( !validateCellRange( orRanges[ nIndex - 1 ], true, bTrackOverflow ) ) + orRanges.erase( orRanges.begin() + nIndex - 1 ); +} + +void AddressConverter::convertToCellRangeList( ApiCellRangeList& orRanges, + const OUString& rString, sal_Int16 nSheet, bool bTrackOverflow ) +{ + sal_Int32 nPos = 0; + sal_Int32 nLen = rString.getLength(); + CellRangeAddress aRange; + while( (0 <= nPos) && (nPos < nLen) ) + { + OUString aToken = rString.getToken( 0, ' ', nPos ); + if( (aToken.getLength() > 0) && convertToCellRange( aRange, aToken, nSheet, true, bTrackOverflow ) ) + orRanges.push_back( aRange ); + } +} + +void AddressConverter::convertToCellRangeList( ApiCellRangeList& orRanges, + const BinRangeList& rBinRanges, sal_Int16 nSheet, bool bTrackOverflow ) +{ + CellRangeAddress aRange; + for( BinRangeList::const_iterator aIt = rBinRanges.begin(), aEnd = rBinRanges.end(); aIt != aEnd; ++aIt ) + if( convertToCellRange( aRange, *aIt, nSheet, true, bTrackOverflow ) ) + orRanges.push_back( aRange ); +} + +// private -------------------------------------------------------------------- + +void AddressConverter::ControlCharacters::set( + sal_Unicode cThisWorkbook, sal_Unicode cExternal, + sal_Unicode cThisSheet, sal_Unicode cInternal, sal_Unicode cSameSheet ) +{ + mcThisWorkbook = cThisWorkbook; + mcExternal = cExternal; + mcThisSheet = cThisSheet; + mcInternal = cInternal; + mcSameSheet = cSameSheet; +} + +void AddressConverter::initializeMaxPos( + sal_Int16 nMaxXlsTab, sal_Int32 nMaxXlsCol, sal_Int32 nMaxXlsRow ) +{ + maMaxXlsPos.Sheet = nMaxXlsTab; + maMaxXlsPos.Column = nMaxXlsCol; + maMaxXlsPos.Row = nMaxXlsRow; + + // maximum cell position in Calc + try + { + Reference< XIndexAccess > xSheetsIA( getDocument()->getSheets(), UNO_QUERY_THROW ); + Reference< XCellRangeAddressable > xAddressable( xSheetsIA->getByIndex( 0 ), UNO_QUERY_THROW ); + CellRangeAddress aRange = xAddressable->getRangeAddress(); + maMaxApiPos = CellAddress( API_MAXTAB, aRange.EndColumn, aRange.EndRow ); + maMaxPos = getBaseFilter().isImportFilter() ? maMaxApiPos : maMaxXlsPos; + } + catch( Exception& ) + { + OSL_ENSURE( false, "AddressConverter::AddressConverter - cannot get sheet limits" ); + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/autofiltercontext.cxx b/oox/source/xls/autofiltercontext.cxx new file mode 100644 index 000000000000..4ae31fc94c56 --- /dev/null +++ b/oox/source/xls/autofiltercontext.cxx @@ -0,0 +1,775 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/autofiltercontext.hxx" +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/sheet/XDatabaseRange.hpp> +#include <com/sun/star/sheet/XDatabaseRanges.hpp> +#include <com/sun/star/sheet/XSheetFilterDescriptor.hpp> +#include <com/sun/star/sheet/FilterOperator.hpp> +#include <com/sun/star/sheet/FilterConnection.hpp> +#include <com/sun/star/i18n/XLocaleData.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/addressconverter.hxx" + +#define DEBUG_OOX_AUTOFILTER 0 + +#if USE_SC_MULTI_STRING_FILTER_PATCH +#include <com/sun/star/sheet/XExtendedSheetFilterDescriptor.hpp> +#include <com/sun/star/sheet/TableFilterFieldNormal.hpp> +#include <com/sun/star/sheet/TableFilterFieldMultiString.hpp> +using ::com::sun::star::sheet::TableFilterFieldNormal; +using ::com::sun::star::sheet::TableFilterFieldMultiString; +using ::com::sun::star::sheet::XExtendedSheetFilterDescriptor; +#else +#include <com/sun/star/sheet/TableFilterField.hpp> +using ::com::sun::star::sheet::TableFilterField; +#endif + +#if DEBUG_OOX_AUTOFILTER +#include <stdio.h> +#endif + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +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::container::XNameAccess; +using ::com::sun::star::container::XNamed; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::table::XCellRange; +using ::com::sun::star::sheet::XDatabaseRange; +using ::com::sun::star::sheet::XDatabaseRanges; +using ::com::sun::star::sheet::XSheetFilterDescriptor; +using ::com::sun::star::i18n::LocaleDataItem; +using ::com::sun::star::i18n::XLocaleData; +using ::com::sun::star::lang::Locale; +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +// ============================================================================ + +FilterFieldItem::FilterFieldItem() : +#if USE_SC_MULTI_STRING_FILTER_PATCH + mpField(new TableFilterFieldNormal), +#else + mpField(new TableFilterField), +#endif + meType(NORMAL) +{ +} + +FilterFieldItem::FilterFieldItem(Type eType) : + meType(eType) +{ +#if USE_SC_MULTI_STRING_FILTER_PATCH + switch ( eType ) + { + case MULTI_STRING: + mpField.reset(new TableFilterFieldMultiString); + break; + case NORMAL: + mpField.reset(new TableFilterFieldNormal); + break; + default: + mpField.reset(new TableFilterFieldNormal); + } +#else + mpField.reset(new TableFilterField); + meType = NORMAL; +#endif +} + +// ============================================================================ + +OoxAutoFilterContext::OoxAutoFilterContext( OoxWorksheetFragmentBase& rFragment ) : + OoxWorksheetContextBase( rFragment ), + mbValidAddress( false ), + mbUseRegex( false ), + mbShowBlank( false ), + mbConnectionAnd( false ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxAutoFilterContext::onCreateContext( sal_Int32 nElement, const AttributeList& ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( autoFilter ): + switch( nElement ) + { + case XLS_TOKEN( filterColumn ): return this; + } + break; + + case XLS_TOKEN( filterColumn ): + switch( nElement ) + { + case XLS_TOKEN( filters ): + case XLS_TOKEN( customFilters ): + case XLS_TOKEN( top10 ): + case XLS_TOKEN( dynamicFilter ): return this; + } + break; + + case XLS_TOKEN( filters ): + switch( nElement ) + { + case XLS_TOKEN( filter ): return this; + } + break; + + case XLS_TOKEN( customFilters ): + switch( nElement ) + { + case XLS_TOKEN( customFilter ): return this; + } + break; + } + return 0; +} + +void OoxAutoFilterContext::onStartElement( const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( autoFilter ): + importAutoFilter( rAttribs ); + break; + case XLS_TOKEN( filterColumn ): + if ( mbValidAddress ) + importFilterColumn( rAttribs ); + break; + case XLS_TOKEN( filters ): + if ( mbValidAddress ) + importFilters( rAttribs ); + break; + case XLS_TOKEN( filter ): + if ( mbValidAddress ) + importFilter( rAttribs ); + break; + case XLS_TOKEN( customFilters ): + if ( mbValidAddress ) + importCustomFilters( rAttribs ); + break; + case XLS_TOKEN( customFilter ): + if ( mbValidAddress ) + importCustomFilter( rAttribs ); + break; + case XLS_TOKEN( top10 ): + if ( mbValidAddress ) + importTop10( rAttribs ); + break; + case XLS_TOKEN( dynamicFilter ): + if ( mbValidAddress ) + importDynamicFilter( rAttribs ); + break; + } +} + +void OoxAutoFilterContext::onEndElement( const OUString& /*rChars*/ ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( autoFilter ): + maybeShowBlank(); + setAutoFilter(); + break; + case XLS_TOKEN( filters ): + setFilterNames(); + break; + } +} + +#if DEBUG_OOX_AUTOFILTER +static void lclPrintNormalField( +#if USE_SC_MULTI_STRING_FILTER_PATCH + TableFilterFieldNormal* pField +#else + TableFilterField* pField +#endif +) +{ + using namespace ::com::sun::star::sheet; + + printf(" Operator: "); + switch ( pField->Operator ) + { + case FilterOperator_EQUAL: + printf("EQUAL"); + break; + case FilterOperator_NOT_EQUAL: + printf("NOT_EQUAL"); + break; + case com::sun::star::sheet::FilterOperator_GREATER: + printf("GREATER"); + break; + case com::sun::star::sheet::FilterOperator_GREATER_EQUAL: + printf("GREATER_EQUAL"); + break; + case FilterOperator_LESS: + printf("LESS"); + break; + case FilterOperator_LESS_EQUAL: + printf("LESS_EQUAL"); + break; + case FilterOperator_NOT_EMPTY: + printf("NOT_EMPTY"); + break; + case FilterOperator_EMPTY: + printf("EMPTY"); + break; + case FilterOperator_BOTTOM_PERCENT: + printf("BOTTOM_PERCENT"); + break; + case FilterOperator_BOTTOM_VALUES: + printf("BOTTOM_VALUES"); + break; + case FilterOperator_TOP_PERCENT: + printf("TOP_PERCENT"); + break; + case FilterOperator_TOP_VALUES: + printf("TOP_VALUES"); + break; + default: + printf("other"); + } + printf("\n"); + + printf(" StringValue: %s\n", + OUStringToOString(pField->StringValue, RTL_TEXTENCODING_UTF8).getStr()); + + printf(" NumericValue: %g\n", pField->NumericValue); + + printf(" IsNumeric: "); + if (pField->IsNumeric) + printf("yes\n"); + else + printf("no\n"); +} + +static void lclPrintFieldConnection( ::com::sun::star::sheet::FilterConnection eConn ) +{ + using namespace ::com::sun::star::sheet; + + printf(" Connection: "); + switch ( eConn ) + { + case FilterConnection_AND: + printf("AND"); + break; + case FilterConnection_OR: + printf("OR"); + break; + case FilterConnection_MAKE_FIXED_SIZE: + printf("MAKE_FIXED_SIZE"); + break; + default: + printf("other"); + } + printf("\n"); +} + +static void lclPrintFilterField( const FilterFieldItem& aItem ) +{ + using namespace ::com::sun::star::sheet; + + printf("----------------------------------------\n"); +#if USE_SC_MULTI_STRING_FILTER_PATCH + { + // Print common fields first. + + TableFilterFieldBase* pField = aItem.mpField.get(); + printf(" Field: %ld\n", pField->Field); + lclPrintFieldConnection(pField->Connection); + } + switch ( aItem.meType ) + { + case FilterFieldItem::NORMAL: + { + TableFilterFieldNormal* pField = static_cast<TableFilterFieldNormal*>(aItem.mpField.get()); + lclPrintNormalField(pField); + } + break; + case FilterFieldItem::MULTI_STRING: + { + TableFilterFieldMultiString* pMultiStrField = static_cast<TableFilterFieldMultiString*>(aItem.mpField.get()); + sal_Int32 nSize = pMultiStrField->StringSet.getLength(); + printf(" StringSet:\n"); + for ( sal_Int32 i = 0; i < nSize; ++i ) + { + printf(" * %s\n", + OUStringToOString(pMultiStrField->StringSet[i], RTL_TEXTENCODING_UTF8).getStr()); + } + } + break; + } +#else + TableFilterField* pField = aItem.mpField.get(); + printf(" Field: %ld\n", pField->Field); + lclPrintFieldConnection(pField->Connection); + lclPrintNormalField(pField); + +#endif + fflush(stdout); +} +#endif + +void OoxAutoFilterContext::initialize() +{ + maFields.clear(); + maFilterNames.clear(); + mbValidAddress = mbShowBlank = mbUseRegex = mbConnectionAnd = false; +} + +void OoxAutoFilterContext::setAutoFilter() +{ + using namespace ::com::sun::star::sheet; + + // Name this built-in database. + OUStringBuffer sDataAreaNameBuf( CREATE_OUSTRING("Excel_BuiltIn__FilterDatabase_ ") ); + sDataAreaNameBuf.append( static_cast<sal_Int32>(getSheetIndex()+1) ); + + OUString sDataAreaName = sDataAreaNameBuf.makeStringAndClear(); + Reference< XCellRange > xCellRange = getCellRange( maAutoFilterRange ); + + // Create a new database range, add filters to it and refresh the database + // for that to take effect. + + Reference< XDatabaseRanges > xDBRanges = getDatabaseRanges(); + if ( !xDBRanges.is() ) + { + OSL_ENSURE( false, "OoxAutoFilterContext::setAutoFilter: DBRange empty" ); + return; + } + + Reference< XNameAccess > xNA( xDBRanges, UNO_QUERY_THROW ); + if ( !xNA->hasByName( sDataAreaName ) ) + xDBRanges->addNewByName( sDataAreaName, maAutoFilterRange ); + + Reference< XDatabaseRange > xDB( xNA->getByName( sDataAreaName ), UNO_QUERY ); + if ( xDB.is() ) + { + PropertySet aProp( xDB ); + aProp.setProperty( PROP_AutoFilter, true ); + } + + sal_Int32 nSize = maFields.size(); + sal_Int32 nMaxFieldCount = nSize; + Reference< XSheetFilterDescriptor > xDescriptor = xDB->getFilterDescriptor(); + if ( xDescriptor.is() ) + { + PropertySet aProp( xDescriptor ); + aProp.setProperty( PROP_ContainsHeader, true ); + aProp.setProperty( PROP_UseRegularExpressions, mbUseRegex ); + aProp.getProperty( nMaxFieldCount, PROP_MaxFieldCount ); + } + else + { + OSL_ENSURE(false, "OoxAutoFilterContext::setAutoFilter: descriptor is empty"); + return; + } + + // Unpack all column field items into a sequence. +#if USE_SC_MULTI_STRING_FILTER_PATCH + Reference< XExtendedSheetFilterDescriptor > xExtDescriptor( xDescriptor, UNO_QUERY ); + if ( !xExtDescriptor.is() ) + { + OSL_ENSURE(false, "OoxAutoFilterContext::setAutoFilter: extended descriptor is empty"); + return; + } + + xExtDescriptor->begin(); + + ::std::list< FilterFieldItem >::const_iterator itr = maFields.begin(), itrEnd = maFields.end(); + for (sal_Int32 i = 0; itr != itrEnd && i < nMaxFieldCount; ++itr, ++i) + { +#if DEBUG_OOX_AUTOFILTER + lclPrintFilterField(*itr); +#endif + switch ( itr->meType ) + { + case oox::xls::FilterFieldItem::MULTI_STRING: + { + // multi-string filter type + TableFilterFieldMultiString* pField = static_cast<TableFilterFieldMultiString*>( itr->mpField.get() ); + xExtDescriptor->addFilterFieldMultiString( *pField ); + } + break; + case oox::xls::FilterFieldItem::NORMAL: + default: + // normal filter type + TableFilterFieldNormal* pField = static_cast<TableFilterFieldNormal*>( itr->mpField.get() ); + xExtDescriptor->addFilterFieldNormal( *pField ); + } + } + xExtDescriptor->commit(); + +#else + Sequence< TableFilterField > aFields(nSize); + ::std::list< FilterFieldItem >::const_iterator itr = maFields.begin(), itrEnd = maFields.end(); + for (sal_Int32 i = 0; itr != itrEnd && i < nMaxFieldCount; ++itr, ++i) + { +#if DEBUG_OOX_AUTOFILTER + lclPrintFilterField( *itr ); +#endif + aFields[i] = *itr->mpField; + } + xDescriptor->setFilterFields( aFields ); +#endif + xDB->refresh(); +} + +void OoxAutoFilterContext::maybeShowBlank() +{ + using namespace ::com::sun::star::sheet; + + if ( !mbShowBlank ) + return; + +#if USE_SC_MULTI_STRING_FILTER_PATCH + FilterFieldItem aItem(FilterFieldItem::NORMAL); + TableFilterFieldNormal* pField = static_cast<TableFilterFieldNormal*>(aItem.mpField.get()); + pField->Field = mnCurColID; + pField->Operator = FilterOperator_EMPTY; + pField->Connection = FilterConnection_AND; + pField->IsNumeric = false; +#else + FilterFieldItem aItem; + aItem.mpField->Field = mnCurColID; + aItem.mpField->Operator = FilterOperator_EMPTY; + aItem.mpField->Connection = FilterConnection_AND; + aItem.mpField->IsNumeric = false; +#endif + maFields.push_back(aItem); +} + +void OoxAutoFilterContext::setFilterNames() +{ + using namespace ::com::sun::star::sheet; + + + sal_Int32 size = maFilterNames.size(); + if ( !size ) + return; + +#if USE_SC_MULTI_STRING_FILTER_PATCH + Sequence< OUString > aStrList(size); + ::std::list< OUString >::const_iterator itr = maFilterNames.begin(), itrEnd = maFilterNames.end(); + for (sal_Int32 i = 0; itr != itrEnd; ++itr, ++i) + aStrList[i] = *itr; + + FilterFieldItem aItem(FilterFieldItem::MULTI_STRING); + TableFilterFieldMultiString* pField = static_cast<TableFilterFieldMultiString*>( aItem.mpField.get() ); + pField->Field = mnCurColID; + pField->Connection = FilterConnection_AND; + pField->StringSet = aStrList; + + maFields.push_back(aItem); +#else + static const OUString sSep = CREATE_OUSTRING("|"); + + OUStringBuffer buf; + if ( size > 1 ) + { + buf.append( CREATE_OUSTRING("^(") ); + mbUseRegex = true; + } + + ::std::list< OUString >::const_iterator itr = maFilterNames.begin(), itrEnd = maFilterNames.end(); + bool bFirst = true; + for (; itr != itrEnd; ++itr) + { + if (bFirst) + bFirst = false; + else + buf.append( sSep ); + buf.append( *itr ); + } + if ( size > 1 ) + buf.append( CREATE_OUSTRING(")$") ); + + FilterFieldItem aItem; + aItem.mpField->Field = mnCurColID; + aItem.mpField->StringValue = buf.makeStringAndClear(); + aItem.mpField->Operator = FilterOperator_EQUAL; + aItem.mpField->Connection = FilterConnection_AND; + aItem.mpField->IsNumeric = false; + maFields.push_back(aItem); +#endif +} + +void OoxAutoFilterContext::importAutoFilter( const AttributeList& rAttribs ) +{ + initialize(); + + mbValidAddress = getAddressConverter().convertToCellRange( + maAutoFilterRange, rAttribs.getString( XML_ref, OUString() ), getSheetIndex(), true, true ); +} + +void OoxAutoFilterContext::importFilterColumn( const AttributeList& rAttribs ) +{ + // hiddenButton and showButton attributes are not used for now. + mnCurColID = rAttribs.getInteger( XML_colId, -1 ); +} + +void OoxAutoFilterContext::importTop10( const AttributeList& rAttribs ) +{ + using namespace ::com::sun::star::sheet; + + // filterVal attribute is not necessarily, since Calc also supports top 10 + // and top 10% filter type. + FilterFieldItem aItem; +#if USE_SC_MULTI_STRING_FILTER_PATCH + TableFilterFieldNormal* pField = static_cast<TableFilterFieldNormal*>(aItem.mpField.get()); +#else + TableFilterField* pField = aItem.mpField.get(); +#endif + pField->Field = mnCurColID; + + bool bPercent = rAttribs.getBool( XML_percent, false ); + bool bTop = rAttribs.getBool( XML_top, true ); + pField->NumericValue = rAttribs.getDouble( XML_val, 0.0 ); + pField->IsNumeric = true; + + // When top10 filter item is present, that's the only filter item for that column. + if ( bTop ) + if ( bPercent ) + pField->Operator = FilterOperator_TOP_PERCENT; + else + pField->Operator = FilterOperator_TOP_VALUES; + else + if ( bPercent ) + pField->Operator = FilterOperator_BOTTOM_PERCENT; + else + pField->Operator = FilterOperator_BOTTOM_VALUES; + + maFields.push_back(aItem); +} + +void OoxAutoFilterContext::importCustomFilters( const AttributeList& rAttribs ) +{ + // OR is default when the 'and' attribute is absent. + mbConnectionAnd = rAttribs.getBool( XML_and, false ); +} + +/** Do a best-effort guess of whether or not the given string is numerical. */ +static bool lclIsNumeric( const OUString& _str, const LocaleDataItem& aLocaleItem ) +{ + OUString str = _str.trim(); + sal_Int32 size = str.getLength(); + + if ( !size ) + // Empty string. This can't be a number. + return false; + + // Get the decimal separator for the current locale. + const OUString& sep = aLocaleItem.decimalSeparator; + + bool bDecimalSep = false; + for (sal_Int32 i = 0; i < size; ++i) + { + OUString c = str.copy(i, 1); + if ( !c.compareTo(sep) ) + { + if ( bDecimalSep ) + return false; + else + { + bDecimalSep = true; + continue; + } + } + if ( (0 > c.compareToAscii("0") || 0 < c.compareToAscii("9")) ) + return false; + } + + return true; +} + +/** Convert wildcard characters to regex equivalent. Returns true if any + wildcard character is found. */ +static bool lclWildcard2Regex( OUString& str ) +{ + bool bWCFound = false; + OUStringBuffer buf; + sal_Int32 size = str.getLength(); + buf.ensureCapacity(size + 6); // pure heuristics. + + sal_Unicode dot = '.', star = '*', hat = '^', dollar = '$'; + buf.append(hat); + for (sal_Int32 i = 0; i < size; ++i) + { + OUString c = str.copy(i, 1); + if ( !c.compareToAscii("?") ) + { + buf.append(dot); + bWCFound = true; + } + else if ( !c.compareToAscii("*") ) + { + buf.append(dot); + buf.append(star); + bWCFound = true; + } + else + buf.append(c); + } + buf.append(dollar); + + if (bWCFound) + str = buf.makeStringAndClear(); + + return bWCFound; +} + +/** Translate Excel's filter operator to Calc's. */ +static ::com::sun::star::sheet::FilterOperator lclTranslateFilterOp( sal_Int32 nToken ) +{ + using namespace ::com::sun::star::sheet; + + switch ( nToken ) + { + case XML_equal: + return FilterOperator_EQUAL; + case XML_notEqual: + return FilterOperator_NOT_EQUAL; + case XML_greaterThan: + return FilterOperator_GREATER; + case XML_greaterThanOrEqual: + return FilterOperator_GREATER_EQUAL; + case XML_lessThan: + return FilterOperator_LESS; + case XML_lessThanOrEqual: + return FilterOperator_LESS_EQUAL; + } + return FilterOperator_EQUAL; +} + +void OoxAutoFilterContext::importCustomFilter( const AttributeList& rAttribs ) +{ + using namespace ::com::sun::star::sheet; + + sal_Int32 nToken = rAttribs.getToken( XML_operator, XML_equal ); +#if USE_SC_MULTI_STRING_FILTER_PATCH + FilterFieldItem aItem(FilterFieldItem::NORMAL); + TableFilterFieldNormal* pField = static_cast<TableFilterFieldNormal*>(aItem.mpField.get()); +#else + FilterFieldItem aItem; + TableFilterField* pField = aItem.mpField.get(); +#endif + pField->Field = mnCurColID; + pField->StringValue = rAttribs.getString( XML_val, OUString() ); + pField->NumericValue = pField->StringValue.toDouble(); + pField->Operator = lclTranslateFilterOp( nToken ); + + if ( nToken == XML_notEqual && !pField->StringValue.compareToAscii(" ") ) + { + // Special case for hiding blanks. Excel translates "hide blanks" to + // (filter if notEqual " "). So, we need to translate it back. + pField->Operator = FilterOperator_NOT_EMPTY; + pField->IsNumeric = false; + maFields.push_back(aItem); + return; + } + + switch ( nToken ) + { + case XML_equal: + case XML_notEqual: + { + Reference< XLocaleData > xLocale( getGlobalFactory()->createInstance( + CREATE_OUSTRING("com.sun.star.i18n.LocaleData") ), UNO_QUERY ); + + if ( !xLocale.is() ) + return; + + LocaleDataItem aLocaleItem = xLocale->getLocaleItem( ::com::sun::star::lang::Locale() ); + pField->IsNumeric = lclIsNumeric(pField->StringValue, aLocaleItem); + + if ( !pField->IsNumeric && lclWildcard2Regex(pField->StringValue) ) + mbUseRegex = true; + + maFields.push_back(aItem); + } + break; + + case XML_greaterThan: + case XML_greaterThanOrEqual: + case XML_lessThan: + case XML_lessThanOrEqual: + { + pField->IsNumeric = true; + maFields.push_back(aItem); + } + break; + default: + OSL_ENSURE( false, "OoxAutoFilterContext::importCustomFilter: unhandled case" ); + } +} + +void OoxAutoFilterContext::importFilters( const AttributeList& rAttribs ) +{ + // blank (boolean) and calendarType attributes can be present, but not used for now. + + mbShowBlank = rAttribs.getBool( XML_blank, false ); + maFilterNames.clear(); +} + +void OoxAutoFilterContext::importFilter( const AttributeList& rAttribs ) +{ + if (mnCurColID == -1) + return; + + OUString value = rAttribs.getString( XML_val, OUString() ); + if ( value.getLength() ) + maFilterNames.push_back(value); +} + +void OoxAutoFilterContext::importDynamicFilter( const AttributeList& /*rAttribs*/ ) +{ + // not implemented yet - Calc doesn't support this. +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/biffcodec.cxx b/oox/source/xls/biffcodec.cxx new file mode 100644 index 000000000000..0872dcc654df --- /dev/null +++ b/oox/source/xls/biffcodec.cxx @@ -0,0 +1,378 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/biffcodec.hxx" +#include <osl/thread.h> +#include <string.h> +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OString; +using ::rtl::OUString; +using ::rtl::OStringToOUString; +using ::oox::core::FilterBase; + +using namespace ::com::sun::star; + +namespace oox { +namespace xls { + +// ============================================================================ + +BiffDecoderBase::BiffDecoderBase() : + mbValid( false ) +{ +} + +BiffDecoderBase::~BiffDecoderBase() +{ +} + +::comphelper::DocPasswordVerifierResult BiffDecoderBase::verifyPassword( const ::rtl::OUString& rPassword, uno::Sequence< beans::NamedValue >& o_rEncryptionData ) +{ + o_rEncryptionData = implVerifyPassword( rPassword ); + mbValid = ( o_rEncryptionData.getLength() > 0 ); + return mbValid ? ::comphelper::DocPasswordVerifierResult_OK : ::comphelper::DocPasswordVerifierResult_WRONG_PASSWORD; +} + +::comphelper::DocPasswordVerifierResult BiffDecoderBase::verifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + mbValid = implVerifyEncryptionData( rEncryptionData ); + return mbValid ? ::comphelper::DocPasswordVerifierResult_OK : ::comphelper::DocPasswordVerifierResult_WRONG_PASSWORD; +} + +void BiffDecoderBase::decode( sal_uInt8* pnDestData, const sal_uInt8* pnSrcData, sal_Int64 nStreamPos, sal_uInt16 nBytes ) +{ + if( pnDestData && pnSrcData && (nBytes > 0) ) + { + if( mbValid ) + implDecode( pnDestData, pnSrcData, nStreamPos, nBytes ); + else + memcpy( pnDestData, pnSrcData, nBytes ); + } +} + +// ============================================================================ + +BiffDecoder_XOR::BiffDecoder_XOR( sal_uInt16 nKey, sal_uInt16 nHash ) : + maCodec( ::oox::core::BinaryCodec_XOR::CODEC_EXCEL ), + mnKey( nKey ), + mnHash( nHash ) +{ +} + +BiffDecoder_XOR::BiffDecoder_XOR( const BiffDecoder_XOR& rDecoder ) : + BiffDecoderBase(), // must be called to prevent compiler warning + maCodec( ::oox::core::BinaryCodec_XOR::CODEC_EXCEL ), + maEncryptionData( rDecoder.maEncryptionData ), + mnKey( rDecoder.mnKey ), + mnHash( rDecoder.mnHash ) +{ + if( isValid() ) + maCodec.initCodec( maEncryptionData ); +} + +BiffDecoder_XOR* BiffDecoder_XOR::implClone() +{ + return new BiffDecoder_XOR( *this ); +} + +uno::Sequence< beans::NamedValue > BiffDecoder_XOR::implVerifyPassword( const ::rtl::OUString& rPassword ) +{ + maEncryptionData.realloc( 0 ); + + /* Convert password to a byte string. TODO: this needs some finetuning + according to the spec... */ + OString aBytePassword = OUStringToOString( rPassword, osl_getThreadTextEncoding() ); + sal_Int32 nLen = aBytePassword.getLength(); + if( (0 < nLen) && (nLen < 16) ) + { + // init codec + maCodec.initKey( (sal_uInt8*)aBytePassword.getStr() ); + + if ( maCodec.verifyKey( mnKey, mnHash ) ) + maEncryptionData = maCodec.getEncryptionData(); + } + + return maEncryptionData; +} + +bool BiffDecoder_XOR::implVerifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + maEncryptionData.realloc( 0 ); + + if( rEncryptionData.getLength() ) + { + // init codec + maCodec.initCodec( rEncryptionData ); + + if ( maCodec.verifyKey( mnKey, mnHash ) ) + maEncryptionData = rEncryptionData; + } + + return maEncryptionData.getLength(); +} + +void BiffDecoder_XOR::implDecode( sal_uInt8* pnDestData, const sal_uInt8* pnSrcData, sal_Int64 nStreamPos, sal_uInt16 nBytes ) +{ + maCodec.startBlock(); + maCodec.skip( static_cast< sal_Int32 >( (nStreamPos + nBytes) & 0x0F ) ); + maCodec.decode( pnDestData, pnSrcData, nBytes ); +} + +// ============================================================================ + +namespace { + +/** Returns the block index of the passed stream position for RCF decryption. */ +sal_Int32 lclGetRcfBlock( sal_Int64 nStreamPos ) +{ + return static_cast< sal_Int32 >( nStreamPos / BIFF_RCF_BLOCKSIZE ); +} + +/** Returns the offset of the passed stream position in a block for RCF decryption. */ +sal_Int32 lclGetRcfOffset( sal_Int64 nStreamPos ) +{ + return static_cast< sal_Int32 >( nStreamPos % BIFF_RCF_BLOCKSIZE ); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +BiffDecoder_RCF::BiffDecoder_RCF( sal_uInt8 pnSalt[ 16 ], sal_uInt8 pnVerifier[ 16 ], sal_uInt8 pnVerifierHash[ 16 ] ) : + maSalt( pnSalt, pnSalt + 16 ), + maVerifier( pnVerifier, pnVerifier + 16 ), + maVerifierHash( pnVerifierHash, pnVerifierHash + 16 ) +{ +} + +BiffDecoder_RCF::BiffDecoder_RCF( const BiffDecoder_RCF& rDecoder ) : + BiffDecoderBase(), // must be called to prevent compiler warning + maEncryptionData( rDecoder.maEncryptionData ), + maSalt( rDecoder.maSalt ), + maVerifier( rDecoder.maVerifier ), + maVerifierHash( rDecoder.maVerifierHash ) +{ + if( isValid() ) + maCodec.initCodec( maEncryptionData ); +} + +BiffDecoder_RCF* BiffDecoder_RCF::implClone() +{ + return new BiffDecoder_RCF( *this ); +} + +uno::Sequence< beans::NamedValue > BiffDecoder_RCF::implVerifyPassword( const ::rtl::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(); + const sal_Unicode* pcCharEnd = pcChar + nLen; + ::std::vector< sal_uInt16 >::iterator aIt = aPassVect.begin(); + for( ; pcChar < pcCharEnd; ++pcChar, ++aIt ) + *aIt = static_cast< sal_uInt16 >( *pcChar ); + + // init codec + maCodec.initKey( &aPassVect.front(), &maSalt.front() ); + if ( maCodec.verifyKey( &maVerifier.front(), &maVerifierHash.front() ) ) + maEncryptionData = maCodec.getEncryptionData(); + } + + return maEncryptionData; +} + +bool BiffDecoder_RCF::implVerifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + maEncryptionData.realloc( 0 ); + + if( rEncryptionData.getLength() ) + { + // init codec + maCodec.initCodec( rEncryptionData ); + + if ( maCodec.verifyKey( &maVerifier.front(), &maVerifierHash.front() ) ) + maEncryptionData = rEncryptionData; + } + + return maEncryptionData.getLength(); +} + + + +void BiffDecoder_RCF::implDecode( sal_uInt8* pnDestData, const sal_uInt8* pnSrcData, sal_Int64 nStreamPos, sal_uInt16 nBytes ) +{ + sal_uInt8* pnCurrDest = pnDestData; + const sal_uInt8* pnCurrSrc = pnSrcData; + sal_Int64 nCurrPos = nStreamPos; + sal_uInt16 nBytesLeft = nBytes; + while( nBytesLeft > 0 ) + { + // initialize codec for current stream position + maCodec.startBlock( lclGetRcfBlock( nCurrPos ) ); + maCodec.skip( lclGetRcfOffset( nCurrPos ) ); + + // decode the block + sal_uInt16 nBlockLeft = static_cast< sal_uInt16 >( BIFF_RCF_BLOCKSIZE - lclGetRcfOffset( nCurrPos ) ); + sal_uInt16 nDecBytes = ::std::min( nBytesLeft, nBlockLeft ); + maCodec.decode( pnCurrDest, pnCurrSrc, static_cast< sal_Int32 >( nDecBytes ) ); + + // prepare for next block + pnCurrDest += nDecBytes; + pnCurrSrc += nDecBytes; + nCurrPos += nDecBytes; + nBytesLeft = nBytesLeft - nDecBytes; + } +} + +// ============================================================================ + +namespace { + +const sal_uInt16 BIFF_FILEPASS_XOR = 0; +const sal_uInt16 BIFF_FILEPASS_RCF = 1; + +const sal_uInt16 BIFF_FILEPASS_BIFF8_RCF = 1; +const sal_uInt16 BIFF_FILEPASS_BIFF8_CRYPTOAPI_2003 = 2; +const sal_uInt16 BIFF_FILEPASS_BIFF8_CRYPTOAPI_2007 = 3; + +// ---------------------------------------------------------------------------- + +BiffDecoderRef lclReadFilePass_XOR( BiffInputStream& rStrm ) +{ + BiffDecoderRef xDecoder; + OSL_ENSURE( rStrm.getRemaining() == 4, "lclReadFilePass_XOR - wrong record size" ); + if( rStrm.getRemaining() == 4 ) + { + sal_uInt16 nBaseKey, nHash; + rStrm >> nBaseKey >> nHash; + xDecoder.reset( new BiffDecoder_XOR( nBaseKey, nHash ) ); + } + return xDecoder; +} + +BiffDecoderRef lclReadFilePass_RCF( BiffInputStream& rStrm ) +{ + BiffDecoderRef xDecoder; + OSL_ENSURE( rStrm.getRemaining() == 48, "lclReadFilePass_RCF - wrong record size" ); + if( rStrm.getRemaining() == 48 ) + { + sal_uInt8 pnSalt[ 16 ]; + sal_uInt8 pnVerifier[ 16 ]; + sal_uInt8 pnVerifierHash[ 16 ]; + rStrm.readMemory( pnSalt, 16 ); + rStrm.readMemory( pnVerifier, 16 ); + rStrm.readMemory( pnVerifierHash, 16 ); + xDecoder.reset( new BiffDecoder_RCF( pnSalt, pnVerifier, pnVerifierHash ) ); + } + return xDecoder; +} + +BiffDecoderRef lclReadFilePass_CryptoApi( BiffInputStream& /*rStrm*/ ) +{ + // not supported + return BiffDecoderRef(); +} + +BiffDecoderRef lclReadFilePassBiff8( BiffInputStream& rStrm ) +{ + BiffDecoderRef xDecoder; + switch( rStrm.readuInt16() ) + { + case BIFF_FILEPASS_XOR: + xDecoder = lclReadFilePass_XOR( rStrm ); + break; + + case BIFF_FILEPASS_RCF: + { + sal_uInt16 nMajor = rStrm.readuInt16(); + rStrm.skip( 2 ); + switch( nMajor ) + { + case BIFF_FILEPASS_BIFF8_RCF: + xDecoder = lclReadFilePass_RCF( rStrm ); + break; + case BIFF_FILEPASS_BIFF8_CRYPTOAPI_2003: + case BIFF_FILEPASS_BIFF8_CRYPTOAPI_2007: + xDecoder = lclReadFilePass_CryptoApi( rStrm ); + break; + default: + OSL_ENSURE( false, "lclReadFilePassBiff8 - unknown BIFF8 encryption sub mode" ); + } + } + break; + + default: + OSL_ENSURE( false, "lclReadFilePassBiff8 - unknown encryption mode" ); + } + return xDecoder; +} + +} // namespace + +// ---------------------------------------------------------------------------- + +BiffCodecHelper::BiffCodecHelper( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +/*static*/ BiffDecoderRef BiffCodecHelper::implReadFilePass( BiffInputStream& rStrm, BiffType eBiff ) +{ + rStrm.enableDecoder( false ); + BiffDecoderRef xDecoder = (eBiff == BIFF8) ? lclReadFilePassBiff8( rStrm ) : lclReadFilePass_XOR( rStrm ); + rStrm.setDecoder( xDecoder ); + return xDecoder; +} + +bool BiffCodecHelper::importFilePass( BiffInputStream& rStrm ) +{ + OSL_ENSURE( !mxDecoder, "BiffCodecHelper::importFilePass - multiple FILEPASS records" ); + mxDecoder = implReadFilePass( rStrm, getBiff() ); + // request and verify a password (decoder implements IDocPasswordVerifier) + if( mxDecoder.get() ) + getBaseFilter().requestEncryptionData( *mxDecoder ); + // correct password is indicated by isValid() function of decoder + return mxDecoder.get() && mxDecoder->isValid(); +} + +void BiffCodecHelper::cloneDecoder( BiffInputStream& rStrm ) +{ + if( mxDecoder.get() ) + rStrm.setDecoder( BiffDecoderRef( mxDecoder->clone() ) ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/biffdetector.cxx b/oox/source/xls/biffdetector.cxx new file mode 100644 index 000000000000..fc3e1e710bcb --- /dev/null +++ b/oox/source/xls/biffdetector.cxx @@ -0,0 +1,233 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/biffdetector.hxx" +#include <algorithm> +#include <rtl/strbuf.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <comphelper/mediadescriptor.hxx> +#include "oox/helper/binaryinputstream.hxx" +#include "oox/ole/olestorage.hxx" + +using ::rtl::OUString; +using ::rtl::OStringBuffer; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::RuntimeException; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::io::XInputStream; +using ::comphelper::MediaDescriptor; + +namespace oox { +namespace xls { + +// ============================================================================ + +Sequence< OUString > BiffDetector_getSupportedServiceNames() +{ + Sequence< OUString > aServiceNames( 1 ); + aServiceNames[ 0 ] = CREATE_OUSTRING( "com.sun.star.frame.ExtendedTypeDetection" ); + return aServiceNames; +} + +OUString BiffDetector_getImplementationName() +{ + return CREATE_OUSTRING( "com.sun.star.comp.oox.BiffDetector" ); +} + +Reference< XInterface > SAL_CALL BiffDetector_createInstance( const Reference< XMultiServiceFactory >& rxFactory ) throw( Exception ) +{ + return static_cast< ::cppu::OWeakObject* >( new BiffDetector( rxFactory ) ); +} + +// ============================================================================ + +BiffDetector::BiffDetector( const Reference< XMultiServiceFactory >& rxFactory ) : + mxFactory( rxFactory ) +{ +} + +BiffDetector::~BiffDetector() +{ +} + +BiffType BiffDetector::detectStreamBiffVersion( BinaryInputStream& rInStream ) +{ + BiffType eBiff = BIFF_UNKNOWN; + if( !rInStream.isEof() && rInStream.isSeekable() && (rInStream.getLength() > 4) ) + { + sal_Int64 nOldPos = rInStream.tell(); + rInStream.seekToStart(); + sal_uInt16 nBofId, nBofSize; + rInStream >> nBofId >> nBofSize; + + if( (4 <= nBofSize) && (nBofSize <= 16) && (rInStream.tell() + nBofSize <= rInStream.getLength()) ) + { + switch( nBofId ) + { + case BIFF2_ID_BOF: + eBiff = BIFF2; + break; + case BIFF3_ID_BOF: + eBiff = BIFF3; + break; + case BIFF4_ID_BOF: + eBiff = BIFF4; + break; + case BIFF5_ID_BOF: + { + if( 6 <= nBofSize ) + { + sal_uInt16 nVersion; + rInStream >> nVersion; + // #i23425# #i44031# #i62752# there are some *really* broken documents out there... + switch( nVersion & 0xFF00 ) + { + case 0: eBiff = BIFF5; break; // #i44031# #i62752# + case BIFF_BOF_BIFF2: eBiff = BIFF2; break; + case BIFF_BOF_BIFF3: eBiff = BIFF3; break; + case BIFF_BOF_BIFF4: eBiff = BIFF4; break; + case BIFF_BOF_BIFF5: eBiff = BIFF5; break; + case BIFF_BOF_BIFF8: eBiff = BIFF8; break; + default: OSL_ENSURE( false, + OStringBuffer( "lclDetectStreamBiffVersion - unknown BIFF version: 0x" ). + append( static_cast< sal_Int32 >( nVersion ), 16 ).getStr() ); + } + } + } + break; + // else do nothing, no BIFF stream + } + } + rInStream.seek( nOldPos ); + } + return eBiff; +} + +BiffType BiffDetector::detectStorageBiffVersion( OUString& orWorkbookStreamName, StorageRef xStorage ) +{ + static const OUString saBookName = CREATE_OUSTRING( "Book" ); + static const OUString saWorkbookName = CREATE_OUSTRING( "Workbook" ); + + BiffType eBiff = BIFF_UNKNOWN; + if( xStorage.get() ) + { + if( xStorage->isStorage() ) + { + // try to open the "Book" stream + BinaryXInputStream aBookStrm5( xStorage->openInputStream( saBookName ), true ); + BiffType eBookStrm5Biff = detectStreamBiffVersion( aBookStrm5 ); + + // try to open the "Workbook" stream + BinaryXInputStream aBookStrm8( xStorage->openInputStream( saWorkbookName ), true ); + BiffType eBookStrm8Biff = detectStreamBiffVersion( aBookStrm8 ); + + // decide which stream to use + if( (eBookStrm8Biff != BIFF_UNKNOWN) && ((eBookStrm5Biff == BIFF_UNKNOWN) || (eBookStrm8Biff > eBookStrm5Biff)) ) + { + /* Only "Workbook" stream exists; or both streams exist, + and "Workbook" has higher BIFF version than "Book" stream. */ + eBiff = eBookStrm8Biff; + orWorkbookStreamName = saWorkbookName; + } + else if( eBookStrm5Biff != BIFF_UNKNOWN ) + { + /* Only "Book" stream exists; or both streams exist, + and "Book" has higher BIFF version than "Workbook" stream. */ + eBiff = eBookStrm5Biff; + orWorkbookStreamName = saBookName; + } + } + else + { + // no storage, try plain input stream from medium (even for BIFF5+) + BinaryXInputStream aStrm( xStorage->openInputStream( OUString() ), false ); + eBiff = detectStreamBiffVersion( aStrm ); + orWorkbookStreamName = OUString(); + } + } + + return eBiff; +} + +// com.sun.star.lang.XServiceInfo interface ----------------------------------- + +OUString SAL_CALL BiffDetector::getImplementationName() throw( RuntimeException ) +{ + return BiffDetector_getImplementationName(); +} + +sal_Bool SAL_CALL BiffDetector::supportsService( const OUString& rService ) throw( RuntimeException ) +{ + const Sequence< OUString > aServices( BiffDetector_getSupportedServiceNames() ); + const OUString* pArray = aServices.getConstArray(); + const OUString* pArrayEnd = pArray + aServices.getLength(); + return ::std::find( pArray, pArrayEnd, rService ) != pArrayEnd; +} + +Sequence< OUString > SAL_CALL BiffDetector::getSupportedServiceNames() throw( RuntimeException ) +{ + return BiffDetector_getSupportedServiceNames(); +} + +// com.sun.star.document.XExtendedFilterDetect interface ---------------------- + +OUString SAL_CALL BiffDetector::detect( Sequence< PropertyValue >& rDescriptor ) throw( RuntimeException ) +{ + OUString aTypeName; + + MediaDescriptor aDescriptor( rDescriptor ); + aDescriptor.addInputStream(); + + Reference< XInputStream > xInStrm( aDescriptor[ MediaDescriptor::PROP_INPUTSTREAM() ], UNO_QUERY ); + if( xInStrm.is() ) + { + OUString aWorkbookName; + StorageRef xStorage( new ::oox::ole::OleStorage( mxFactory, xInStrm, true ) ); + switch( detectStorageBiffVersion( aWorkbookName, xStorage ) ) + { + case BIFF2: + case BIFF3: + case BIFF4: aTypeName = CREATE_OUSTRING( "calc_MS_Excel_40" ); break; + case BIFF5: aTypeName = CREATE_OUSTRING( "calc_MS_Excel_95" ); break; + case BIFF8: aTypeName = CREATE_OUSTRING( "calc_MS_Excel_97" ); break; + default:; + } + } + + return aTypeName; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/biffhelper.cxx b/oox/source/xls/biffhelper.cxx new file mode 100644 index 000000000000..afd7e04e6a08 --- /dev/null +++ b/oox/source/xls/biffhelper.cxx @@ -0,0 +1,287 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/biffhelper.hxx" +#include <rtl/math.hxx> +#include <rtl/tencinfo.h> +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/biffoutputstream.hxx" +#include "oox/xls/worksheethelper.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_Int32 BIFF_RK_100FLAG = 0x00000001; +const sal_Int32 BIFF_RK_INTFLAG = 0x00000002; +const sal_Int32 BIFF_RK_VALUEMASK = 0xFFFFFFFC; + +const sal_Int32 BITMAPFILEHEADER_SIZE = 14; +const sal_Int32 BITMAPCOREHEADER_SIZE = 12; +const sal_Int32 BITMAPINFOHEADER_SIZE = 40; + +const sal_uInt16 BIFF_IMGDATA_WMF = 2; +const sal_uInt16 BIFF_IMGDATA_DIB = 9; +const sal_uInt16 BIFF_IMGDATA_NATIVE = 14; + +// ---------------------------------------------------------------------------- + +union DecodedDouble +{ + double mfValue; + sal_math_Double maStruct; + + inline explicit DecodedDouble() {} + inline explicit DecodedDouble( double fValue ) : mfValue( fValue ) {} +}; + +bool lclCalcRkFromDouble( sal_Int32& ornRkValue, const DecodedDouble& rDecDbl ) +{ + // double + if( (rDecDbl.maStruct.w32_parts.lsw == 0) && ((rDecDbl.maStruct.w32_parts.msw & 0x3) == 0) ) + { + ornRkValue = static_cast< sal_Int32 >( rDecDbl.maStruct.w32_parts.msw ); + return true; + } + + // integer + double fInt = 0.0; + double fFrac = modf( rDecDbl.mfValue, &fInt ); + if( (fFrac == 0.0) && (-536870912.0 <= fInt) && (fInt <= 536870911.0) ) // 2^29 + { + ornRkValue = static_cast< sal_Int32 >( fInt ); + ornRkValue <<= 2; + ornRkValue |= BIFF_RK_INTFLAG; + return true; + } + + return false; +} + +bool lclCalcRkFromDouble( sal_Int32& ornRkValue, double fValue ) +{ + DecodedDouble aDecDbl( fValue ); + if( lclCalcRkFromDouble( ornRkValue, aDecDbl ) ) + return true; + + aDecDbl.mfValue *= 100.0; + if( lclCalcRkFromDouble( ornRkValue, aDecDbl ) ) + { + ornRkValue |= BIFF_RK_100FLAG; + return true; + } + + return false; +} + +// ---------------------------------------------------------------------------- + +void lclImportImgDataDib( StreamDataSequence& orDataSeq, BiffInputStream& rStrm, sal_Int32 nBytes, BiffType eBiff ) +{ + /* The IMGDATA record for bitmap format contains a Windows DIB (a bitmap + file without the 'BITMAPFILEHEADER' header structure). Usually, the DIB + header consists of 12 bytes (called 'OS/2 V1 header' or + 'BITMAPCOREHEADER', see http://en.wikipedia.org/wiki/BMP_file_format) + followed by the remaining pixel data, but the 'Windows V3' or + 'BITMAPINFOHEADER' is also supported here. This function creates a + complete 'BMP file' that can be read by the OOo graphic provider used + to import graphic objects. For that, the BITMAPFILEHEADER has to be + inserted before the DIB data, and it has to contain the correct offset + to the pixel data. Currently, in real life there are only 24-bit and + 32-bit DIBs (without color palette) in use. This code relies on this + fact and calculates the offset to the pixel data according to the size + of the DIB header. + Remaining tasks are (if really used somewhere): + - Support of DIBs with color palette, + - Support of 'Windows V4' and 'Windows V5' DIB header. */ + + // read and check validity of DIB header + sal_Int64 nInStrmPos = rStrm.tell(); + sal_Int32 nDibHdrSize = rStrm.readInt32(); + sal_uInt16 nPlanes = 0, nDepth = 0; + switch( nDibHdrSize ) + { + case BITMAPCOREHEADER_SIZE: + rStrm.skip( 4 ); // width/height as 16-bit integer + rStrm >> nPlanes >> nDepth; + break; + case BITMAPINFOHEADER_SIZE: + rStrm.skip( 8 ); // width/height as 32-bit integer + rStrm >> nPlanes >> nDepth; + break; + } + rStrm.seek( nInStrmPos ); + + if( (nPlanes == 1) && ((nDepth == 24) || (nDepth == 32)) ) + { + // allocate enough space for the BITMAPFILEHEADER and the DIB data + orDataSeq.realloc( BITMAPFILEHEADER_SIZE + nBytes ); + SequenceOutputStream aOutStrm( orDataSeq ); + + // write the BITMAPFILEHEADER of a regular BMP file + sal_Int32 nBmpSize = BITMAPFILEHEADER_SIZE + nBytes; + sal_Int32 nOffset = BITMAPFILEHEADER_SIZE + nDibHdrSize; + aOutStrm << sal_uInt16( 0x4D42 ) << nBmpSize << sal_Int32( 0 ) << nOffset; + + // copy the DIB header + rStrm.copyToStream( aOutStrm, nDibHdrSize ); + nBytes -= nDibHdrSize; + + /* Excel 3.x and Excel 4.x seem to write broken or out-dated DIB data. + Usually they write a BITMAPCOREHEADER containing width, height, + planes as usual. The pixel depth field is set to 32 bit (though + this is not allowed according to documentation). Between that + header and the actual pixel data, 3 unused bytes are inserted. This + does even confuse Excel 5.x and later, which cannot read the image + data correctly. */ + if( (eBiff <= BIFF4) && (nDibHdrSize == BITMAPCOREHEADER_SIZE) && (nDepth == 32) ) + { + // skip the dummy bytes in input stream + rStrm.skip( 3 ); + nBytes -= 3; + // correct the total BMP file size in output stream + sal_Int64 nOutStrmPos = aOutStrm.tell(); + aOutStrm.seek( 2 ); + aOutStrm << sal_Int32( nBmpSize - 3 ); + aOutStrm.seek( nOutStrmPos ); + } + + // copy remaining pixel data to output stream + rStrm.copyToStream( aOutStrm, nBytes ); + } + rStrm.seek( nInStrmPos + nBytes ); +} + +} // namespace + +// ============================================================================ + +/*static*/ double BiffHelper::calcDoubleFromRk( sal_Int32 nRkValue ) +{ + DecodedDouble aDecDbl( 0.0 ); + if( getFlag( nRkValue, BIFF_RK_INTFLAG ) ) + { + sal_Int32 nTemp = nRkValue >> 2; + setFlag< sal_Int32 >( nTemp, 0xE0000000, nRkValue < 0 ); + aDecDbl.mfValue = nTemp; + } + else + { + aDecDbl.maStruct.w32_parts.msw = static_cast< sal_uInt32 >( nRkValue & BIFF_RK_VALUEMASK ); + } + + if( getFlag( nRkValue, BIFF_RK_100FLAG ) ) + aDecDbl.mfValue /= 100.0; + + return aDecDbl.mfValue; +} + +/*static*/ bool BiffHelper::calcRkFromDouble( sal_Int32& ornRkValue, double fValue ) +{ + if( lclCalcRkFromDouble( ornRkValue, fValue ) ) + return true; + + if( lclCalcRkFromDouble( ornRkValue, fValue * 100 ) ) + { + ornRkValue |= BIFF_RK_100FLAG; + return true; + } + + return false; +} + +/*static*/ double BiffHelper::calcDoubleFromError( sal_uInt8 nErrorCode ) +{ + sal_uInt16 nApiError = 0x7FFF; + switch( nErrorCode ) + { + case BIFF_ERR_NULL: nApiError = 521; break; + case BIFF_ERR_DIV0: nApiError = 532; break; + case BIFF_ERR_VALUE: nApiError = 519; break; + case BIFF_ERR_REF: nApiError = 524; break; + case BIFF_ERR_NAME: nApiError = 525; break; + case BIFF_ERR_NUM: nApiError = 503; break; + case BIFF_ERR_NA: nApiError = 0x7FFF; break; + default: OSL_ENSURE( false, "BiffHelper::calcDoubleFromError - unknown error code" ); + } + DecodedDouble aDecDbl; + ::rtl::math::setNan( &aDecDbl.mfValue ); + aDecDbl.maStruct.nan_parts.fraction_lo = nApiError; + return aDecDbl.mfValue; +} + +/*static*/ rtl_TextEncoding BiffHelper::calcTextEncodingFromCodePage( sal_uInt16 nCodePage ) +{ + // some specials for BIFF + switch( nCodePage ) + { + case 1200: return RTL_TEXTENCODING_DONTKNOW; // BIFF8 Unicode + case 32768: return RTL_TEXTENCODING_APPLE_ROMAN; + case 32769: return RTL_TEXTENCODING_MS_1252; // BIFF2-BIFF3 + } + + rtl_TextEncoding eTextEnc = rtl_getTextEncodingFromWindowsCodePage( nCodePage ); + OSL_ENSURE( eTextEnc != RTL_TEXTENCODING_DONTKNOW, "BiffHelper::calcTextEncodingFromCodePage - unknown code page" ); + return eTextEnc; +} + +/*static*/ sal_uInt16 BiffHelper::calcCodePageFromTextEncoding( rtl_TextEncoding eTextEnc ) +{ + sal_uInt32 nCodePage = rtl_getWindowsCodePageFromTextEncoding( eTextEnc ); + OSL_ENSURE( (0 < nCodePage) && (nCodePage <= SAL_MAX_UINT16), "BiffHelper::calcCodePageFromTextEncoding - unknown text encoding" ); + return static_cast< sal_uInt16 >( (nCodePage == 0) ? 1252 : nCodePage ); +} + +/*static*/ void BiffHelper::importImgData( StreamDataSequence& orDataSeq, BiffInputStream& rStrm, BiffType eBiff ) +{ + sal_uInt16 nFormat, nEnv; + sal_Int32 nBytes; + rStrm >> nFormat >> nEnv >> nBytes; + OSL_ENSURE( nBytes > 0, "BiffHelper::importImgData - invalid data size" ); + if( (0 < nBytes) && (nBytes <= rStrm.getRemaining()) ) + { + switch( nFormat ) + { +// case BIFF_IMGDATA_WMF: /* TODO */ break; + case BIFF_IMGDATA_DIB: lclImportImgDataDib( orDataSeq, rStrm, nBytes, eBiff ); break; +// case BIFF_IMGDATA_NATIVE: /* TODO */ break; + default: OSL_ENSURE( false, "BiffHelper::importImgData - unknown image format" ); + } + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/biffinputstream.cxx b/oox/source/xls/biffinputstream.cxx new file mode 100644 index 000000000000..fec36d4fa54d --- /dev/null +++ b/oox/source/xls/biffinputstream.cxx @@ -0,0 +1,631 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/biffinputstream.hxx" +#include <algorithm> +#include <rtl/ustrbuf.hxx> + +using ::rtl::OString; +using ::rtl::OStringToOUString; +using ::rtl::OUString; +using ::rtl::OUStringBuffer; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace prv { + +BiffInputRecordBuffer::BiffInputRecordBuffer( BinaryInputStream& rInStrm ) : + mrInStrm( rInStrm ), + mpCurrentData( 0 ), + mnHeaderPos( -1 ), + mnBodyPos( 0 ), + mnBufferBodyPos( 0 ), + mnNextHeaderPos( 0 ), + mnRecId( BIFF_ID_UNKNOWN ), + mnRecSize( 0 ), + mnRecPos( 0 ), + mbValidHeader( false ) +{ + OSL_ENSURE( mrInStrm.isSeekable(), "BiffInputRecordBuffer::BiffInputRecordBuffer - stream must be seekable" ); + mrInStrm.seekToStart(); + maOriginalData.reserve( SAL_MAX_UINT16 ); + maDecodedData.reserve( SAL_MAX_UINT16 ); + enableDecoder( false ); // updates mpCurrentData +} + +void BiffInputRecordBuffer::restartAt( sal_Int64 nPos ) +{ + mnHeaderPos = -1; + mnBodyPos = mnBufferBodyPos = 0; + mnNextHeaderPos = nPos; + mnRecId = BIFF_ID_UNKNOWN; + mnRecSize = mnRecPos = 0; + mbValidHeader = false; +} + +void BiffInputRecordBuffer::setDecoder( const BiffDecoderRef& rxDecoder ) +{ + mxDecoder = rxDecoder; + enableDecoder( true ); + updateDecoded(); +} + +void BiffInputRecordBuffer::enableDecoder( bool bEnable ) +{ + mpCurrentData = (bEnable && mxDecoder.get() && mxDecoder->isValid()) ? &maDecodedData : &maOriginalData; +} + +bool BiffInputRecordBuffer::startRecord( sal_Int64 nHeaderPos ) +{ + mbValidHeader = (0 <= nHeaderPos) && (nHeaderPos + 4 <= mrInStrm.getLength()); + if( mbValidHeader ) + { + mnHeaderPos = nHeaderPos; + mrInStrm.seek( nHeaderPos ); + mrInStrm >> mnRecId >> mnRecSize; + mnBodyPos = mrInStrm.tell(); + mnNextHeaderPos = mnBodyPos + mnRecSize; + mbValidHeader = !mrInStrm.isEof() && (mnNextHeaderPos <= mrInStrm.getLength()); + } + if( !mbValidHeader ) + { + mnHeaderPos = mnBodyPos = -1; + mnNextHeaderPos = 0; + mnRecId = BIFF_ID_UNKNOWN; + mnRecSize = 0; + } + mnRecPos = 0; + return mbValidHeader; +} + +bool BiffInputRecordBuffer::startNextRecord() +{ + return startRecord( mnNextHeaderPos ); +} + +sal_uInt16 BiffInputRecordBuffer::getNextRecId() +{ + sal_uInt16 nRecId = BIFF_ID_UNKNOWN; + if( mbValidHeader && (mnNextHeaderPos + 4 <= mrInStrm.getLength()) ) + { + mrInStrm.seek( mnNextHeaderPos ); + mrInStrm >> nRecId; + } + return nRecId; +} + +void BiffInputRecordBuffer::read( void* opData, sal_uInt16 nBytes ) +{ + updateBuffer(); + OSL_ENSURE( nBytes > 0, "BiffInputRecordBuffer::read - nothing to read" ); + OSL_ENSURE( nBytes <= getRecLeft(), "BiffInputRecordBuffer::read - buffer overflow" ); + memcpy( opData, &(*mpCurrentData)[ mnRecPos ], nBytes ); + mnRecPos = mnRecPos + nBytes; +} + +void BiffInputRecordBuffer::skip( sal_uInt16 nBytes ) +{ + OSL_ENSURE( nBytes > 0, "BiffInputRecordBuffer::skip - nothing to skip" ); + OSL_ENSURE( nBytes <= getRecLeft(), "BiffInputRecordBuffer::skip - buffer overflow" ); + mnRecPos = mnRecPos + nBytes; +} + +void BiffInputRecordBuffer::updateBuffer() +{ + OSL_ENSURE( mbValidHeader, "BiffInputRecordBuffer::updateBuffer - invalid access" ); + if( mnBodyPos != mnBufferBodyPos ) + { + mrInStrm.seek( mnBodyPos ); + maOriginalData.resize( mnRecSize ); + if( mnRecSize > 0 ) + mrInStrm.readMemory( &maOriginalData.front(), static_cast< sal_Int32 >( mnRecSize ) ); + mnBufferBodyPos = mnBodyPos; + updateDecoded(); + } +} + +void BiffInputRecordBuffer::updateDecoded() +{ + if( mxDecoder.get() && mxDecoder->isValid() ) + { + maDecodedData.resize( mnRecSize ); + if( mnRecSize > 0 ) + mxDecoder->decode( &maDecodedData.front(), &maOriginalData.front(), mnBodyPos, mnRecSize ); + } +} + +} // namespace prv + +// ============================================================================ + +BiffInputStream::BiffInputStream( BinaryInputStream& rInStream, bool bContLookup ) : + maRecBuffer( rInStream ), + mnRecHandle( -1 ), + mnRecId( BIFF_ID_UNKNOWN ), + mnAltContId( BIFF_ID_UNKNOWN ), + mnCurrRecSize( 0 ), + mnComplRecSize( 0 ), + mbHasComplRec( false ), + mbCont( bContLookup ) +{ + mbEof = true; // EOF will be true if stream is not inside a record +} + +// record control ------------------------------------------------------------- + +bool BiffInputStream::startNextRecord() +{ + bool bValidRec = false; + /* #i4266# ignore zero records (id==len==0) (e.g. the application + "Crystal Report" writes zero records between other records) */ + bool bIsZeroRec = false; + do + { + // record header is never encrypted + maRecBuffer.enableDecoder( false ); + // read header of next raw record, returns false at end of stream + bValidRec = maRecBuffer.startNextRecord(); + // ignore record, if identifier and size are zero + bIsZeroRec = (maRecBuffer.getRecId() == 0) && (maRecBuffer.getRecSize() == 0); + } + while( bValidRec && ((mbCont && isContinueId( maRecBuffer.getRecId() )) || bIsZeroRec) ); + + // setup other class members + setupRecord(); + return isInRecord(); +} + +bool BiffInputStream::startRecordByHandle( sal_Int64 nRecHandle ) +{ + rewindToRecord( nRecHandle ); + return startNextRecord(); +} + +void BiffInputStream::resetRecord( bool bContLookup, sal_uInt16 nAltContId ) +{ + if( isInRecord() ) + { + mbCont = bContLookup; + mnAltContId = nAltContId; + restartRecord( true ); + maRecBuffer.enableDecoder( true ); + } +} + +void BiffInputStream::rewindRecord() +{ + rewindToRecord( mnRecHandle ); +} + +// decoder -------------------------------------------------------------------- + +void BiffInputStream::setDecoder( const BiffDecoderRef& rxDecoder ) +{ + maRecBuffer.setDecoder( rxDecoder ); +} + +void BiffInputStream::enableDecoder( bool bEnable ) +{ + maRecBuffer.enableDecoder( bEnable ); +} + +// stream/record state and info ----------------------------------------------- + +sal_uInt16 BiffInputStream::getNextRecId() +{ + sal_uInt16 nRecId = BIFF_ID_UNKNOWN; + if( isInRecord() ) + { + sal_Int64 nCurrPos = tell(); // save current position in record + while( jumpToNextContinue() ) {} // skip following CONTINUE records + if( maRecBuffer.startNextRecord() ) // read header of next record + nRecId = maRecBuffer.getRecId(); + seek( nCurrPos ); // restore position, seek() resets old mbValid state + } + return nRecId; +} + +// BinaryStreamBase interface (seeking) --------------------------------------- + +bool BiffInputStream::isSeekable() const +{ + return true; +} + +sal_Int64 BiffInputStream::tell() const +{ + return mbEof ? -1 : (mnCurrRecSize - maRecBuffer.getRecLeft()); +} + +sal_Int64 BiffInputStream::getLength() const +{ + if( !mbHasComplRec ) + const_cast< BiffInputStream* >( this )->calcRecordLength(); + return mnComplRecSize; +} + +void BiffInputStream::seek( sal_Int64 nRecPos ) +{ + if( isInRecord() ) + { + if( mbEof || (nRecPos < tell()) ) + restartRecord( false ); + if( !mbEof && (nRecPos > tell()) ) + skip( static_cast< sal_Int32 >( nRecPos - tell() ) ); + } +} + +sal_Int64 BiffInputStream::tellBase() const +{ + return maRecBuffer.getBaseStream().tell(); +} + +sal_Int64 BiffInputStream::getBaseLength() const +{ + return maRecBuffer.getBaseStream().getLength(); +} + +// BinaryInputStream interface (stream read access) --------------------------- + +sal_Int32 BiffInputStream::readData( StreamDataSequence& orData, sal_Int32 nBytes ) +{ + sal_Int32 nRet = 0; + if( !mbEof ) + { + orData.realloc( ::std::max< sal_Int32 >( nBytes, 0 ) ); + if( nBytes > 0 ) + { + nRet = readMemory( orData.getArray(), nBytes ); + if( nRet < nBytes ) + orData.realloc( nRet ); + } + } + return nRet; +} + +sal_Int32 BiffInputStream::readMemory( void* opMem, sal_Int32 nBytes ) +{ + sal_Int32 nRet = 0; + if( !mbEof && opMem && (nBytes > 0) ) + { + sal_uInt8* pnBuffer = reinterpret_cast< sal_uInt8* >( opMem ); + sal_Int32 nBytesLeft = nBytes; + + while( !mbEof && (nBytesLeft > 0) ) + { + sal_uInt16 nReadSize = getMaxRawReadSize( nBytesLeft ); + // check nReadSize, stream may already be located at end of a raw record + if( nReadSize > 0 ) + { + maRecBuffer.read( pnBuffer, nReadSize ); + nRet += nReadSize; + pnBuffer += nReadSize; + nBytesLeft -= nReadSize; + } + if( nBytesLeft > 0 ) + jumpToNextContinue(); + OSL_ENSURE( !mbEof, "BiffInputStream::readMemory - record overread" ); + } + } + return nRet; +} + +void BiffInputStream::skip( sal_Int32 nBytes ) +{ + sal_Int32 nBytesLeft = nBytes; + while( !mbEof && (nBytesLeft > 0) ) + { + sal_uInt16 nSkipSize = getMaxRawReadSize( nBytesLeft ); + // check nSkipSize, stream may already be located at end of a raw record + if( nSkipSize > 0 ) + { + maRecBuffer.skip( nSkipSize ); + nBytesLeft -= nSkipSize; + } + if( nBytesLeft > 0 ) + jumpToNextContinue(); + OSL_ENSURE( !mbEof, "BiffInputStream::skip - record overread" ); + } +} + +// byte strings --------------------------------------------------------------- + +OString BiffInputStream::readByteString( bool b16BitLen, bool bAllowNulChars ) +{ + sal_Int32 nStrLen = b16BitLen ? readuInt16() : readuInt8(); + return readCharArray( nStrLen, bAllowNulChars ); +} + +OUString BiffInputStream::readByteStringUC( bool b16BitLen, rtl_TextEncoding eTextEnc, bool bAllowNulChars ) +{ + return OStringToOUString( readByteString( b16BitLen, bAllowNulChars ), eTextEnc ); +} + +void BiffInputStream::skipByteString( bool b16BitLen ) +{ + skip( b16BitLen ? readuInt16() : readuInt8() ); +} + +// Unicode strings ------------------------------------------------------------ + +OUString BiffInputStream::readUniStringChars( sal_uInt16 nChars, bool b16BitChars, bool bAllowNulChars ) +{ + OUStringBuffer aBuffer; + aBuffer.ensureCapacity( nChars ); + + /* This function has to react on CONTINUE records to read the repeated + flags field, so readUnicodeArray() cannot be used here. */ + sal_uInt16 nCharsLeft = nChars; + while( !mbEof && (nCharsLeft > 0) ) + { + sal_uInt16 nPortionCount = 0; + if( b16BitChars ) + { + nPortionCount = ::std::min< sal_uInt16 >( nCharsLeft, maRecBuffer.getRecLeft() / 2 ); + OSL_ENSURE( (nPortionCount <= nCharsLeft) || ((maRecBuffer.getRecLeft() & 1) == 0), + "BiffInputStream::readUniStringChars - missing a byte" ); + } + else + { + nPortionCount = getMaxRawReadSize( nCharsLeft ); + } + + // read the character array + appendUnicodeArray( aBuffer, nPortionCount, b16BitChars, bAllowNulChars ); + + // prepare for next CONTINUE record + nCharsLeft = nCharsLeft - nPortionCount; + if( nCharsLeft > 0 ) + jumpToNextStringContinue( b16BitChars ); + } + + return aBuffer.makeStringAndClear(); +} + +OUString BiffInputStream::readUniStringBody( sal_uInt16 nChars, bool bAllowNulChars ) +{ + bool b16BitChars; + sal_Int32 nAddSize; + readUniStringHeader( b16BitChars, nAddSize ); + OUString aString = readUniStringChars( nChars, b16BitChars, bAllowNulChars ); + skip( nAddSize ); + return aString; +} + +OUString BiffInputStream::readUniString( bool bAllowNulChars ) +{ + return readUniStringBody( readuInt16(), bAllowNulChars ); +} + +void BiffInputStream::skipUniStringChars( sal_uInt16 nChars, bool b16BitChars ) +{ + sal_uInt16 nCharsLeft = nChars; + while( !mbEof && (nCharsLeft > 0) ) + { + sal_uInt16 nPortionCount; + if( b16BitChars ) + { + nPortionCount = ::std::min< sal_uInt16 >( nCharsLeft, maRecBuffer.getRecLeft() / 2 ); + OSL_ENSURE( (nPortionCount <= nCharsLeft) || ((maRecBuffer.getRecLeft() & 1) == 0), + "BiffInputStream::skipUniStringChars - missing a byte" ); + skip( 2 * nPortionCount ); + } + else + { + nPortionCount = getMaxRawReadSize( nCharsLeft ); + skip( nPortionCount ); + } + + // prepare for next CONTINUE record + nCharsLeft = nCharsLeft - nPortionCount; + if( nCharsLeft > 0 ) + jumpToNextStringContinue( b16BitChars ); + } +} + +void BiffInputStream::skipUniStringBody( sal_uInt16 nChars ) +{ + bool b16BitChars; + sal_Int32 nAddSize; + readUniStringHeader( b16BitChars, nAddSize ); + skipUniStringChars( nChars, b16BitChars ); + skip( nAddSize ); +} + +void BiffInputStream::skipUniString() +{ + skipUniStringBody( readuInt16() ); +} + +// private -------------------------------------------------------------------- + +void BiffInputStream::readAtom( void* opMem, sal_uInt8 nSize ) +{ + // byte swapping is done in calling BinaryInputStream::readValue() template function + if( ensureRawReadSize( nSize ) ) + maRecBuffer.read( opMem, nSize ); +} + +void BiffInputStream::setupRecord() +{ + // initialize class members + mnRecHandle = maRecBuffer.getRecHeaderPos(); + mnRecId = maRecBuffer.getRecId(); + mnAltContId = BIFF_ID_UNKNOWN; + mnCurrRecSize = mnComplRecSize = maRecBuffer.getRecSize(); + mbHasComplRec = !mbCont; + mbEof = !isInRecord(); + // enable decoder in new record + enableDecoder( true ); +} + +void BiffInputStream::restartRecord( bool bInvalidateRecSize ) +{ + if( isInRecord() ) + { + maRecBuffer.startRecord( getRecHandle() ); + mnCurrRecSize = maRecBuffer.getRecSize(); + if( bInvalidateRecSize ) + { + mnComplRecSize = mnCurrRecSize; + mbHasComplRec = !mbCont; + } + mbEof = false; + } +} + +void BiffInputStream::rewindToRecord( sal_Int64 nRecHandle ) +{ + if( nRecHandle >= 0 ) + { + maRecBuffer.restartAt( nRecHandle ); + mnRecHandle = -1; + mbEof = true; // as long as the record is not started + } +} + +bool BiffInputStream::isContinueId( sal_uInt16 nRecId ) const +{ + return (nRecId == BIFF_ID_CONT) || (nRecId == mnAltContId); +} + +bool BiffInputStream::jumpToNextContinue() +{ + mbEof = mbEof || !mbCont || !isContinueId( maRecBuffer.getNextRecId() ) || !maRecBuffer.startNextRecord(); + if( !mbEof ) + mnCurrRecSize += maRecBuffer.getRecSize(); + return !mbEof; +} + +bool BiffInputStream::jumpToNextStringContinue( bool& rb16BitChars ) +{ + OSL_ENSURE( maRecBuffer.getRecLeft() == 0, "BiffInputStream::jumpToNextStringContinue - unexpected garbage" ); + + if( mbCont && (getRemaining() > 0) ) + { + jumpToNextContinue(); + } + else if( mnRecId == BIFF_ID_CONT ) + { + /* CONTINUE handling is off, but we have started reading in a CONTINUE + record -> start next CONTINUE for TXO import. We really start a new + record here - no chance to return to string origin. */ + mbEof = mbEof || (maRecBuffer.getNextRecId() != BIFF_ID_CONT) || !maRecBuffer.startNextRecord(); + if( !mbEof ) + setupRecord(); + } + + // trying to read the flags invalidates stream, if no CONTINUE record has been found + sal_uInt8 nFlags; + readValue( nFlags ); + rb16BitChars = getFlag( nFlags, BIFF_STRF_16BIT ); + return !mbEof; +} + +void BiffInputStream::calcRecordLength() +{ + sal_Int64 nCurrPos = tell(); // save current position in record + while( jumpToNextContinue() ) {} // jumpToNextContinue() adds up mnCurrRecSize + mnComplRecSize = mnCurrRecSize; + mbHasComplRec = true; + seek( nCurrPos ); // restore position, seek() resets old mbValid state +} + +bool BiffInputStream::ensureRawReadSize( sal_uInt16 nBytes ) +{ + if( !mbEof && (nBytes > 0) ) + { + while( !mbEof && (maRecBuffer.getRecLeft() == 0) ) jumpToNextContinue(); + mbEof = mbEof || (nBytes > maRecBuffer.getRecLeft()); + OSL_ENSURE( !mbEof, "BiffInputStream::ensureRawReadSize - record overread" ); + } + return !mbEof; +} + +sal_uInt16 BiffInputStream::getMaxRawReadSize( sal_Int32 nBytes ) const +{ + return getLimitedValue< sal_uInt16, sal_Int32 >( nBytes, 0, maRecBuffer.getRecLeft() ); +} + +void BiffInputStream::appendUnicodeArray( OUStringBuffer& orBuffer, sal_uInt16 nChars, bool b16BitChars, bool bAllowNulChars ) +{ + orBuffer.ensureCapacity( orBuffer.getLength() + nChars ); + sal_uInt16 nChar; + for( sal_uInt16 nCharIdx = 0; !mbEof && (nCharIdx < nChars); ++nCharIdx ) + { + if( b16BitChars ) readValue( nChar ); else nChar = readuInt8(); + orBuffer.append( static_cast< sal_Unicode >( (!bAllowNulChars && (nChar == 0)) ? '?' : nChar ) ); + } +} + +void BiffInputStream::readUniStringHeader( bool& orb16BitChars, sal_Int32& ornAddSize ) +{ + sal_uInt8 nFlags = readuInt8(); + OSL_ENSURE( !getFlag( nFlags, BIFF_STRF_UNKNOWN ), "BiffInputStream::readUniStringHeader - unknown flags" ); + orb16BitChars = getFlag( nFlags, BIFF_STRF_16BIT ); + sal_uInt16 nFontCount = getFlag( nFlags, BIFF_STRF_RICH ) ? readuInt16() : 0; + sal_Int32 nPhoneticSize = getFlag( nFlags, BIFF_STRF_PHONETIC ) ? readInt32() : 0; + ornAddSize = 4 * nFontCount + ::std::max< sal_Int32 >( 0, nPhoneticSize ); +} + +// ============================================================================ + +BiffInputStreamPos::BiffInputStreamPos( BiffInputStream& rStrm ) : + mrStrm( rStrm ), + mnRecHandle( rStrm.getRecHandle() ), + mnRecPos( rStrm.tell() ) +{ +} + +bool BiffInputStreamPos::restorePosition() +{ + bool bValidRec = mrStrm.startRecordByHandle( mnRecHandle ); + if( bValidRec ) + mrStrm.seek( mnRecPos ); + return bValidRec && !mrStrm.isEof(); +} + +// ============================================================================ + +BiffInputStreamPosGuard::BiffInputStreamPosGuard( BiffInputStream& rStrm ) : + BiffInputStreamPos( rStrm ) +{ +} + +BiffInputStreamPosGuard::~BiffInputStreamPosGuard() +{ + restorePosition(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/biffoutputstream.cxx b/oox/source/xls/biffoutputstream.cxx new file mode 100644 index 000000000000..5ec8834c5336 --- /dev/null +++ b/oox/source/xls/biffoutputstream.cxx @@ -0,0 +1,208 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/biffoutputstream.hxx" + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace prv { + +BiffOutputRecordBuffer::BiffOutputRecordBuffer( BinaryOutputStream& rOutStrm, sal_uInt16 nMaxRecSize ) : + mrOutStrm( rOutStrm ), + mnMaxRecSize( nMaxRecSize ), + mnRecId( BIFF_ID_UNKNOWN ), + mbInRec( false ) +{ + OSL_ENSURE( mrOutStrm.isSeekable(), "BiffOutputRecordBuffer::BiffOutputRecordBuffer - stream must be seekable" ); + maData.reserve( SAL_MAX_UINT16 ); +} + +void BiffOutputRecordBuffer::startRecord( sal_uInt16 nRecId ) +{ + OSL_ENSURE( !mbInRec, "BiffOutputRecordBuffer::startRecord - another record still open" ); + mnRecId = nRecId; + maData.clear(); + mbInRec = true; +} + +void BiffOutputRecordBuffer::endRecord() +{ + OSL_ENSURE( mbInRec, "BiffOutputRecordBuffer::endRecord - no record open" ); + sal_uInt16 nRecSize = getLimitedValue< sal_uInt16, size_t >( maData.size(), 0, SAL_MAX_UINT16 ); + mrOutStrm.seekToEnd(); + mrOutStrm << mnRecId << nRecSize; + if( nRecSize > 0 ) + mrOutStrm.writeMemory( &maData.front(), nRecSize ); + mbInRec = false; +} + +void BiffOutputRecordBuffer::write( const void* pData, sal_uInt16 nBytes ) +{ + OSL_ENSURE( mbInRec, "BiffOutputRecordBuffer::write - no record open" ); + OSL_ENSURE( nBytes > 0, "BiffOutputRecordBuffer::write - nothing to write" ); + OSL_ENSURE( nBytes <= getRecLeft(), "BiffOutputRecordBuffer::write - buffer overflow" ); + maData.resize( maData.size() + nBytes ); + memcpy( &*(maData.end() - nBytes), pData, nBytes ); +} + +void BiffOutputRecordBuffer::fill( sal_uInt8 nValue, sal_uInt16 nBytes ) +{ + OSL_ENSURE( mbInRec, "BiffOutputRecordBuffer::write - no record open" ); + OSL_ENSURE( nBytes > 0, "BiffOutputRecordBuffer::write - nothing to write" ); + OSL_ENSURE( nBytes <= getRecLeft(), "BiffOutputRecordBuffer::write - buffer overflow" ); + maData.resize( maData.size() + nBytes, nValue ); +} + +} // namespace prv + +// ============================================================================ + +BiffOutputStream::BiffOutputStream( BinaryOutputStream& rOutStream, sal_uInt16 nMaxRecSize ) : + maRecBuffer( rOutStream, nMaxRecSize ), + mnPortionSize( 0 ), + mnPortionPos( 0 ) +{ +} + +// record control ------------------------------------------------------------- + +void BiffOutputStream::startRecord( sal_uInt16 nRecId ) +{ + maRecBuffer.startRecord( nRecId ); + setPortionSize( 0 ); +} + +void BiffOutputStream::endRecord() +{ + setPortionSize( 0 ); + maRecBuffer.endRecord(); +} + +void BiffOutputStream::setPortionSize( sal_uInt16 nSize ) +{ + OSL_ENSURE( mnPortionPos == 0, "BiffOutputStream::setPortionSize - block operation inside portion" ); + mnPortionSize = nSize; + mnPortionPos = 0; +} + +// BinaryStreamBase interface (seeking) --------------------------------------- + +sal_Int64 BiffOutputStream::tellBase() const +{ + return maRecBuffer.getBaseStream().tell(); +} + +sal_Int64 BiffOutputStream::getBaseLength() const +{ + return maRecBuffer.getBaseStream().getLength(); +} + +// BinaryOutputStream interface (stream write access) ------------------------- + +void BiffOutputStream::writeData( const StreamDataSequence& rData ) +{ + if( rData.hasElements() ) + writeMemory( rData.getConstArray(), rData.getLength() ); +} + +void BiffOutputStream::writeMemory( const void* pMem, sal_Int32 nBytes ) +{ + if( pMem && (nBytes > 0) ) + { + const sal_uInt8* pnBuffer = reinterpret_cast< const sal_uInt8* >( pMem ); + sal_Int32 nBytesLeft = nBytes; + while( nBytesLeft > 0 ) + { + sal_uInt16 nBlockSize = prepareRawBlock( nBytesLeft ); + maRecBuffer.write( pnBuffer, nBlockSize ); + pnBuffer += nBlockSize; + nBytesLeft -= nBlockSize; + } + } +} + +void BiffOutputStream::fill( sal_uInt8 nValue, sal_Int32 nBytes ) +{ + sal_Int32 nBytesLeft = nBytes; + while( nBytesLeft > 0 ) + { + sal_uInt16 nBlockSize = prepareRawBlock( nBytesLeft ); + maRecBuffer.fill( nValue, nBlockSize ); + nBytesLeft -= nBlockSize; + } +} + +void BiffOutputStream::writeBlock( const void* pMem, sal_uInt16 nBytes ) +{ + ensureRawBlock( nBytes ); + maRecBuffer.write( pMem, nBytes ); +} + +// private -------------------------------------------------------------------- + +void BiffOutputStream::writeAtom( const void* pMem, sal_uInt8 nSize ) +{ + // byte swapping is done in calling BinaryOutputStream::writeValue() template function + writeBlock( pMem, nSize ); +} + +void BiffOutputStream::ensureRawBlock( sal_uInt16 nSize ) +{ + if( (maRecBuffer.getRecLeft() < nSize) || + ((mnPortionSize > 0) && (mnPortionPos == 0) && (maRecBuffer.getRecLeft() < mnPortionSize)) ) + { + maRecBuffer.endRecord(); + maRecBuffer.startRecord( BIFF_ID_CONT ); + } + if( mnPortionSize > 0 ) + { + OSL_ENSURE( mnPortionPos + nSize <= mnPortionSize, "BiffOutputStream::ensureRawBlock - portion overflow" ); + mnPortionPos = (mnPortionPos + nSize) % mnPortionSize; // prevent compiler warning, do not use operator+=, operator%= + } +} + +sal_uInt16 BiffOutputStream::prepareRawBlock( sal_Int32 nTotalSize ) +{ + sal_uInt16 nRecLeft = maRecBuffer.getRecLeft(); + if( mnPortionSize > 0 ) + { + OSL_ENSURE( mnPortionPos == 0, "BiffOutputStream::prepareRawBlock - block operation inside portion" ); + OSL_ENSURE( nTotalSize % mnPortionSize == 0, "BiffOutputStream::prepareRawBlock - portion size does not match block size" ); + nRecLeft = (nRecLeft / mnPortionSize) * mnPortionSize; + } + sal_uInt16 nSize = getLimitedValue< sal_uInt16, sal_Int32 >( nTotalSize, 0, nRecLeft ); + ensureRawBlock( nSize ); + return nSize; +} +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/chartsheetfragment.cxx b/oox/source/xls/chartsheetfragment.cxx new file mode 100644 index 000000000000..287518584cc3 --- /dev/null +++ b/oox/source/xls/chartsheetfragment.cxx @@ -0,0 +1,292 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/chartsheetfragment.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/pagesettings.hxx" +#include "oox/xls/viewsettings.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/xls/worksheetsettings.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxChartsheetFragment::OoxChartsheetFragment( const WorkbookHelper& rHelper, + const OUString& rFragmentPath, ISegmentProgressBarRef xProgressBar, sal_Int16 nSheet ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath, xProgressBar, SHEETTYPE_CHARTSHEET, nSheet ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxChartsheetFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( chartsheet ) ) return this; + break; + + case XLS_TOKEN( chartsheet ): + switch( nElement ) + { + case XLS_TOKEN( sheetViews ): return this; + + case XLS_TOKEN( sheetPr ): getWorksheetSettings().importChartSheetPr( rAttribs ); break; + case XLS_TOKEN( sheetProtection ): getWorksheetSettings().importChartProtection( rAttribs ); break; + case XLS_TOKEN( pageMargins ): getPageSettings().importPageMargins( rAttribs ); break; + case XLS_TOKEN( pageSetup ): getPageSettings().importChartPageSetup( getRelations(), rAttribs ); break; + case XLS_TOKEN( headerFooter ): getPageSettings().importHeaderFooter( rAttribs ); return this; + case XLS_TOKEN( picture ): getPageSettings().importPicture( getRelations(), rAttribs ); break; + case XLS_TOKEN( drawing ): importDrawing( rAttribs ); break; + } + break; + + case XLS_TOKEN( sheetViews ): + if( nElement == XLS_TOKEN( sheetView ) ) getSheetViewSettings().importChartSheetView( rAttribs ); + break; + + case XLS_TOKEN( headerFooter ): + switch( nElement ) + { + case XLS_TOKEN( firstHeader ): + case XLS_TOKEN( firstFooter ): + case XLS_TOKEN( oddHeader ): + case XLS_TOKEN( oddFooter ): + case XLS_TOKEN( evenHeader ): + case XLS_TOKEN( evenFooter ): return this; // collect contents in onEndElement() + } + break; + } + return 0; +} + +void OoxChartsheetFragment::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( firstHeader ): + case XLS_TOKEN( firstFooter ): + case XLS_TOKEN( oddHeader ): + case XLS_TOKEN( oddFooter ): + case XLS_TOKEN( evenHeader ): + case XLS_TOKEN( evenFooter ): + getPageSettings().importHeaderFooterCharacters( rChars, getCurrentElement() ); + break; + } +} + +ContextHandlerRef OoxChartsheetFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_WORKSHEET ) return this; + break; + + case OOBIN_ID_WORKSHEET: + switch( nRecId ) + { + case OOBIN_ID_CHARTSHEETVIEWS: return this; + + case OOBIN_ID_CHARTSHEETPR: getWorksheetSettings().importChartSheetPr( rStrm ); break; + case OOBIN_ID_CHARTPROTECTION: getWorksheetSettings().importChartProtection( rStrm ); break; + case OOBIN_ID_PAGEMARGINS: getPageSettings().importPageMargins( rStrm ); break; + case OOBIN_ID_CHARTPAGESETUP: getPageSettings().importChartPageSetup( getRelations(), rStrm ); break; + case OOBIN_ID_HEADERFOOTER: getPageSettings().importHeaderFooter( rStrm ); break; + case OOBIN_ID_PICTURE: getPageSettings().importPicture( getRelations(), rStrm ); break; + case OOBIN_ID_DRAWING: importDrawing( rStrm ); break; + } + break; + + case OOBIN_ID_CHARTSHEETVIEWS: + if( nRecId == OOBIN_ID_CHARTSHEETVIEW ) getSheetViewSettings().importChartSheetView( rStrm ); + break; + } + return 0; +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxChartsheetFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_CHARTSHEETVIEW, OOBIN_ID_CHARTSHEETVIEW + 1 }, + { OOBIN_ID_CHARTSHEETVIEWS, OOBIN_ID_CHARTSHEETVIEWS + 1 }, + { OOBIN_ID_CUSTOMCHARTVIEW, OOBIN_ID_CUSTOMCHARTVIEW + 1 }, + { OOBIN_ID_CUSTOMCHARTVIEWS, OOBIN_ID_CUSTOMCHARTVIEWS + 1 }, + { OOBIN_ID_HEADERFOOTER, OOBIN_ID_HEADERFOOTER + 1 }, + { OOBIN_ID_WORKSHEET, OOBIN_ID_WORKSHEET + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +void OoxChartsheetFragment::initializeImport() +{ + // initial processing in base class WorksheetHelper + initializeWorksheetImport(); +} + +void OoxChartsheetFragment::finalizeImport() +{ + // final processing in base class WorksheetHelper + finalizeWorksheetImport(); +} + +// private -------------------------------------------------------------------- + +void OoxChartsheetFragment::importDrawing( const AttributeList& rAttribs ) +{ + setDrawingPath( getFragmentPathFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ) ); +} + +void OoxChartsheetFragment::importDrawing( RecordInputStream& rStrm ) +{ + setDrawingPath( getFragmentPathFromRelId( rStrm.readString() ) ); +} + +// ============================================================================ + +BiffChartsheetFragment::BiffChartsheetFragment( const BiffWorkbookFragmentBase& rParent, + ISegmentProgressBarRef xProgressBar, sal_Int16 nSheet ) : + BiffWorksheetFragmentBase( rParent, xProgressBar, SHEETTYPE_CHARTSHEET, nSheet ) +{ +} + +bool BiffChartsheetFragment::importFragment() +{ + // initial processing in base class WorksheetHelper + initializeWorksheetImport(); + + WorksheetSettings& rWorksheetSett = getWorksheetSettings(); + SheetViewSettings& rSheetViewSett = getSheetViewSettings(); + PageSettings& rPageSett = getPageSettings(); + + // process all record in this sheet fragment + while( mrStrm.startNextRecord() && (mrStrm.getRecId() != BIFF_ID_EOF) ) + { + if( isBofRecord() ) + { + // skip unknown embedded fragments (BOF/EOF blocks) + skipFragment(); + } + else + { + sal_uInt16 nRecId = mrStrm.getRecId(); + switch( nRecId ) + { + // records in all BIFF versions + case BIFF_ID_BOTTOMMARGIN: rPageSett.importBottomMargin( mrStrm ); break; + case BIFF_ID_CHBEGIN: skipRecordBlock( BIFF_ID_CHEND ); break; + case BIFF_ID_FOOTER: rPageSett.importFooter( mrStrm ); break; + case BIFF_ID_HEADER: rPageSett.importHeader( mrStrm ); break; + case BIFF_ID_LEFTMARGIN: rPageSett.importLeftMargin( mrStrm ); break; + case BIFF_ID_PASSWORD: rWorksheetSett.importPassword( mrStrm ); break; + case BIFF_ID_PROTECT: rWorksheetSett.importProtect( mrStrm ); break; + case BIFF_ID_RIGHTMARGIN: rPageSett.importRightMargin( mrStrm ); break; + case BIFF_ID_TOPMARGIN: rPageSett.importTopMargin( mrStrm ); break; + + // BIFF specific records + default: switch( getBiff() ) + { + case BIFF2: switch( nRecId ) + { + case BIFF2_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF3: switch( nRecId ) + { + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + + } + break; + + case BIFF4: switch( nRecId ) + { + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_PAGESETUP: rPageSett.importPageSetup( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF5: switch( nRecId ) + { + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_PAGESETUP: rPageSett.importPageSetup( mrStrm ); break; + case BIFF_ID_SCENPROTECT: rWorksheetSett.importScenProtect( mrStrm ); break; + case BIFF_ID_SCL: rSheetViewSett.importScl( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF8: switch( nRecId ) + { + case BIFF_ID_CODENAME: rWorksheetSett.importCodeName( mrStrm ); break; + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_PICTURE: rPageSett.importPicture( mrStrm ); break; + case BIFF_ID_PAGESETUP: rPageSett.importPageSetup( mrStrm ); break; + case BIFF_ID_SCL: rSheetViewSett.importScl( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF_UNKNOWN: break; + } + } + } + } + + // final processing in base class WorksheetHelper + finalizeWorksheetImport(); + return mrStrm.getRecId() == BIFF_ID_EOF; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/commentsbuffer.cxx b/oox/source/xls/commentsbuffer.cxx new file mode 100644 index 000000000000..190309649789 --- /dev/null +++ b/oox/source/xls/commentsbuffer.cxx @@ -0,0 +1,157 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/commentsbuffer.hxx" +#include <com/sun/star/sheet/XSheetAnnotationAnchor.hpp> +#include <com/sun/star/sheet/XSheetAnnotationShapeSupplier.hpp> +#include <com/sun/star/sheet/XSheetAnnotations.hpp> +#include <com/sun/star/sheet/XSheetAnnotationsSupplier.hpp> +#include "oox/helper/attributelist.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/vml/vmlshape.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/drawingfragment.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::sheet::XSheetAnnotation; +using ::com::sun::star::sheet::XSheetAnnotationAnchor; +using ::com::sun::star::sheet::XSheetAnnotationShapeSupplier; +using ::com::sun::star::sheet::XSheetAnnotations; +using ::com::sun::star::sheet::XSheetAnnotationsSupplier; + +namespace oox { +namespace xls { + +// ============================================================================ + +CommentModel::CommentModel() : + mnAuthorId( -1 ) +{ +} + +// ---------------------------------------------------------------------------- + +Comment::Comment( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +void Comment::importComment( const AttributeList& rAttribs ) +{ + maModel.mnAuthorId = rAttribs.getInteger( XML_authorId, -1 ); + // cell range will be checked while inserting the comment into the document + getAddressConverter().convertToCellRangeUnchecked( maModel.maRange, rAttribs.getString( XML_ref, OUString() ), getSheetIndex() ); +} + +void Comment::importComment( RecordInputStream& rStrm ) +{ + BinRange aBinRange; + rStrm >> maModel.mnAuthorId >> aBinRange; + // cell range will be checked while inserting the comment into the document + getAddressConverter().convertToCellRangeUnchecked( maModel.maRange, aBinRange, getSheetIndex() ); +} + +RichStringRef Comment::createText() +{ + maModel.mxText.reset( new RichString( *this ) ); + return maModel.mxText; +} + +void Comment::finalizeImport() +{ + // OOBIN format stores cell range instead of cell address, use first cell of this range + OSL_ENSURE( (maModel.maRange.StartColumn == maModel.maRange.EndColumn) && + (maModel.maRange.StartRow == maModel.maRange.EndRow), + "Comment::finalizeImport - comment anchor should be a single cell" ); + CellAddress aNotePos( maModel.maRange.Sheet, maModel.maRange.StartColumn, maModel.maRange.StartRow ); + if( getAddressConverter().checkCellAddress( aNotePos, true ) && maModel.mxText.get() ) try + { + maModel.mxText->finalizeImport(); + OUString aNoteText = maModel.mxText->getPlainText(); + // non-empty string required by note implementation + if( aNoteText.getLength() > 0 ) + { + Reference< XSheetAnnotationsSupplier > xAnnosSupp( getSheet(), UNO_QUERY_THROW ); + Reference< XSheetAnnotations > xAnnos( xAnnosSupp->getAnnotations(), UNO_SET_THROW ); + xAnnos->insertNew( aNotePos, aNoteText ); + // receive craeted note from cell (insertNew does not return the note) + Reference< XSheetAnnotationAnchor > xAnnoAnchor( getCell( aNotePos ), UNO_QUERY_THROW ); + Reference< XSheetAnnotation > xAnno( xAnnoAnchor->getAnnotation(), UNO_SET_THROW ); + Reference< XSheetAnnotationShapeSupplier > xAnnoShapeSupp( xAnno, UNO_QUERY_THROW ); + Reference< XShape > xAnnoShape( xAnnoShapeSupp->getAnnotationShape(), UNO_SET_THROW ); + // convert shape formatting + if( const ::oox::vml::ShapeBase* pNoteShape = getVmlDrawing().getNoteShape( aNotePos ) ) + { + // position and formatting + pNoteShape->convertFormatting( xAnnoShape ); + // visibility + const ::oox::vml::ShapeModel::ShapeClientDataPtr& rxClientData = pNoteShape->getShapeModel().mxClientData; + bool bVisible = rxClientData.get() && rxClientData->mbVisible; + xAnno->setIsVisible( bVisible ); + } + } + } + catch( Exception& ) + { + } +} + +// ============================================================================ + +CommentsBuffer::CommentsBuffer( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +void CommentsBuffer::appendAuthor( const OUString& rAuthor ) +{ + maAuthors.push_back( rAuthor ); +} + +CommentRef CommentsBuffer::createComment() +{ + CommentRef xComment( new Comment( *this ) ); + maComments.push_back( xComment ); + return xComment; +} + +void CommentsBuffer::finalizeImport() +{ + maComments.forEachMem( &Comment::finalizeImport ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/commentsfragment.cxx b/oox/source/xls/commentsfragment.cxx new file mode 100644 index 000000000000..c87d3028b7cb --- /dev/null +++ b/oox/source/xls/commentsfragment.cxx @@ -0,0 +1,153 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/commentsfragment.hxx" +#include "oox/xls/richstringcontext.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxCommentsFragment::OoxCommentsFragment( const WorksheetHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxCommentsFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( comments ) ) return this; + break; + case XLS_TOKEN( comments ): + if( nElement == XLS_TOKEN( authors ) ) return this; + if( nElement == XLS_TOKEN( commentList ) ) return this; + break; + case XLS_TOKEN( authors ): + if( nElement == XLS_TOKEN( author ) ) return this; // collect author in onEndElement() + break; + case XLS_TOKEN( commentList ): + if( nElement == XLS_TOKEN( comment ) ) { importComment( rAttribs ); return this; } + break; + case XLS_TOKEN( comment ): + if( (nElement == XLS_TOKEN( text )) && mxComment.get() ) + return new OoxRichStringContext( *this, mxComment->createText() ); + break; + } + return 0; +} + +void OoxCommentsFragment::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( author ): + getComments().appendAuthor( rChars ); + break; + case XLS_TOKEN( comment ): + mxComment.reset(); + break; + } +} + +ContextHandlerRef OoxCommentsFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_COMMENTS ) return this; + break; + case OOBIN_ID_COMMENTS: + if( nRecId == OOBIN_ID_COMMENTAUTHORS ) return this; + if( nRecId == OOBIN_ID_COMMENTLIST ) return this; + break; + case OOBIN_ID_COMMENTAUTHORS: + if( nRecId == OOBIN_ID_COMMENTAUTHOR ) getComments().appendAuthor( rStrm.readString() ); + break; + case OOBIN_ID_COMMENTLIST: + if( nRecId == OOBIN_ID_COMMENT ) { importComment( rStrm ); return this; } + break; + case OOBIN_ID_COMMENT: + if( (nRecId == OOBIN_ID_COMMENTTEXT) && mxComment.get() ) + mxComment->createText()->importString( rStrm, true ); + break; + } + return 0; +} + +void OoxCommentsFragment::onEndRecord() +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_COMMENT: + mxComment.reset(); + break; + } +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxCommentsFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_COMMENT, OOBIN_ID_COMMENT + 1 }, + { OOBIN_ID_COMMENTAUTHORS, OOBIN_ID_COMMENTAUTHORS + 1 }, + { OOBIN_ID_COMMENTLIST, OOBIN_ID_COMMENTLIST + 1 }, + { OOBIN_ID_COMMENTS, OOBIN_ID_COMMENTS + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +// private -------------------------------------------------------------------- + +void OoxCommentsFragment::importComment( const AttributeList& rAttribs ) +{ + mxComment = getComments().createComment(); + mxComment->importComment( rAttribs ); +} + +void OoxCommentsFragment::importComment( RecordInputStream& rStrm ) +{ + mxComment = getComments().createComment(); + mxComment->importComment( rStrm ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/condformatbuffer.cxx b/oox/source/xls/condformatbuffer.cxx new file mode 100644 index 000000000000..317476f25635 --- /dev/null +++ b/oox/source/xls/condformatbuffer.cxx @@ -0,0 +1,787 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/condformatbuffer.hxx" +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/table/CellRangeAddress.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/sheet/ConditionOperator.hpp> +#include <com/sun/star/sheet/XSheetConditionalEntries.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/sheet/XSpreadsheets.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/stylesbuffer.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::style::XStyleFamiliesSupplier; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::container::XNameContainer; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::ConditionOperator; +using ::com::sun::star::table::XCellRange; +using ::com::sun::star::sheet::XSheetCellRanges; +using ::com::sun::star::sheet::XSheetConditionalEntries; +using ::com::sun::star::sheet::XSpreadsheetDocument; +using ::com::sun::star::sheet::XSpreadsheets; +using ::com::sun::star::sheet::XSpreadsheet; +using ::com::sun::star::style::XStyle; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_Int32 OOBIN_CFRULE_TYPE_CELLIS = 1; +const sal_Int32 OOBIN_CFRULE_TYPE_EXPRESSION = 2; +const sal_Int32 OOBIN_CFRULE_TYPE_COLORSCALE = 3; +const sal_Int32 OOBIN_CFRULE_TYPE_DATABAR = 4; +const sal_Int32 OOBIN_CFRULE_TYPE_TOPTEN = 5; +const sal_Int32 OOBIN_CFRULE_TYPE_ICONSET = 6; + +const sal_Int32 OOBIN_CFRULE_SUB_CELLIS = 0; +const sal_Int32 OOBIN_CFRULE_SUB_EXPRESSION = 1; +const sal_Int32 OOBIN_CFRULE_SUB_COLORSCALE = 2; +const sal_Int32 OOBIN_CFRULE_SUB_DATABAR = 3; +const sal_Int32 OOBIN_CFRULE_SUB_ICONSET = 4; +const sal_Int32 OOBIN_CFRULE_SUB_TOPTEN = 5; +const sal_Int32 OOBIN_CFRULE_SUB_UNIQUE = 7; +const sal_Int32 OOBIN_CFRULE_SUB_TEXT = 8; +const sal_Int32 OOBIN_CFRULE_SUB_BLANK = 9; +const sal_Int32 OOBIN_CFRULE_SUB_NOTBLANK = 10; +const sal_Int32 OOBIN_CFRULE_SUB_ERROR = 11; +const sal_Int32 OOBIN_CFRULE_SUB_NOTERROR = 12; +const sal_Int32 OOBIN_CFRULE_SUB_TODAY = 15; +const sal_Int32 OOBIN_CFRULE_SUB_TOMORROW = 16; +const sal_Int32 OOBIN_CFRULE_SUB_YESTERDAY = 17; +const sal_Int32 OOBIN_CFRULE_SUB_LAST7DAYS = 18; +const sal_Int32 OOBIN_CFRULE_SUB_LASTMONTH = 19; +const sal_Int32 OOBIN_CFRULE_SUB_NEXTMONTH = 20; +const sal_Int32 OOBIN_CFRULE_SUB_THISWEEK = 21; +const sal_Int32 OOBIN_CFRULE_SUB_NEXTWEEK = 22; +const sal_Int32 OOBIN_CFRULE_SUB_LASTWEEK = 23; +const sal_Int32 OOBIN_CFRULE_SUB_THISMONTH = 24; +const sal_Int32 OOBIN_CFRULE_SUB_ABOVEAVERAGE = 25; +const sal_Int32 OOBIN_CFRULE_SUB_BELOWAVERAGE = 26; +const sal_Int32 OOBIN_CFRULE_SUB_DUPLICATE = 27; +const sal_Int32 OOBIN_CFRULE_SUB_EQABOVEAVERAGE = 29; +const sal_Int32 OOBIN_CFRULE_SUB_EQBELOWAVERAGE = 30; + +const sal_Int32 OOBIN_CFRULE_TIMEOP_TODAY = 0; +const sal_Int32 OOBIN_CFRULE_TIMEOP_YESTERDAY = 1; +const sal_Int32 OOBIN_CFRULE_TIMEOP_LAST7DAYS = 2; +const sal_Int32 OOBIN_CFRULE_TIMEOP_THISWEEK = 3; +const sal_Int32 OOBIN_CFRULE_TIMEOP_LASTWEEK = 4; +const sal_Int32 OOBIN_CFRULE_TIMEOP_LASTMONTH = 5; +const sal_Int32 OOBIN_CFRULE_TIMEOP_TOMORROW = 6; +const sal_Int32 OOBIN_CFRULE_TIMEOP_NEXTWEEK = 7; +const sal_Int32 OOBIN_CFRULE_TIMEOP_NEXTMONTH = 8; +const sal_Int32 OOBIN_CFRULE_TIMEOP_THISMONTH = 9; + +const sal_uInt16 OOBIN_CFRULE_STOPIFTRUE = 0x0002; +const sal_uInt16 OOBIN_CFRULE_ABOVEAVERAGE = 0x0004; +const sal_uInt16 OOBIN_CFRULE_BOTTOM = 0x0008; +const sal_uInt16 OOBIN_CFRULE_PERCENT = 0x0010; + +// ---------------------------------------------------------------------------- + +template< typename Type > +void lclAppendProperty( ::std::vector< PropertyValue >& orProps, const OUString& rPropName, const Type& rValue ) +{ + orProps.push_back( PropertyValue() ); + orProps.back().Name = rPropName; + orProps.back().Value <<= rValue; +} + +} // namespace + +// ============================================================================ + +CondFormatRuleModel::CondFormatRuleModel() : + mnPriority( -1 ), + mnType( XML_TOKEN_INVALID ), + mnOperator( XML_TOKEN_INVALID ), + mnTimePeriod( XML_TOKEN_INVALID ), + mnRank( 0 ), + mnStdDev( 0 ), + mnDxfId( -1 ), + mbStopIfTrue( false ), + mbBottom( false ), + mbPercent( false ), + mbAboveAverage( true ), + mbEqualAverage( false ) +{ +} + +void CondFormatRuleModel::setBinOperator( sal_Int32 nOperator ) +{ + static const sal_Int32 spnOperators[] = { + XML_TOKEN_INVALID, XML_between, XML_notBetween, XML_equal, XML_notEqual, + XML_greaterThan, XML_lessThan, XML_greaterThanOrEqual, XML_lessThanOrEqual }; + mnOperator = STATIC_ARRAY_SELECT( spnOperators, nOperator, XML_TOKEN_INVALID ); +} + +void CondFormatRuleModel::setOobTextType( sal_Int32 nOperator ) +{ + // note: type XML_notContainsText vs. operator XML_notContains + static const sal_Int32 spnTypes[] = { XML_containsText, XML_notContainsText, XML_beginsWith, XML_endsWith }; + mnType = STATIC_ARRAY_SELECT( spnTypes, nOperator, XML_TOKEN_INVALID ); + static const sal_Int32 spnOperators[] = { XML_containsText, XML_notContains, XML_beginsWith, XML_endsWith }; + mnOperator = STATIC_ARRAY_SELECT( spnOperators, nOperator, XML_TOKEN_INVALID ); +} + +// ============================================================================ + +CondFormatRule::CondFormatRule( const CondFormat& rCondFormat ) : + WorksheetHelper( rCondFormat ), + mrCondFormat( rCondFormat ) +{ +} + +void CondFormatRule::importCfRule( const AttributeList& rAttribs ) +{ + maModel.maText = rAttribs.getString( XML_text, OUString() ); + maModel.mnPriority = rAttribs.getInteger( XML_priority, -1 ); + maModel.mnType = rAttribs.getToken( XML_type, XML_TOKEN_INVALID ); + maModel.mnOperator = rAttribs.getToken( XML_operator, XML_TOKEN_INVALID ); + maModel.mnTimePeriod = rAttribs.getToken( XML_timePeriod, XML_TOKEN_INVALID ); + maModel.mnRank = rAttribs.getInteger( XML_rank, 0 ); + maModel.mnStdDev = rAttribs.getInteger( XML_stdDev, 0 ); + maModel.mnDxfId = rAttribs.getInteger( XML_dxfId, -1 ); + maModel.mbStopIfTrue = rAttribs.getBool( XML_stopIfTrue, false ); + maModel.mbBottom = rAttribs.getBool( XML_bottom, false ); + maModel.mbPercent = rAttribs.getBool( XML_percent, false ); + maModel.mbAboveAverage = rAttribs.getBool( XML_aboveAverage, true ); + maModel.mbEqualAverage = rAttribs.getBool( XML_equalAverage, false ); +} + +void CondFormatRule::appendFormula( const OUString& rFormula ) +{ + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( mrCondFormat.getRanges().getBaseAddress() ); + getFormulaParser().importFormula( aContext, rFormula ); + maModel.maFormulas.push_back( aContext ); +} + +void CondFormatRule::importCfRule( RecordInputStream& rStrm ) +{ + sal_Int32 nType, nSubType, nOperator, nFmla1Size, nFmla2Size, nFmla3Size; + sal_uInt16 nFlags; + rStrm >> nType >> nSubType >> maModel.mnDxfId >> maModel.mnPriority >> nOperator; + rStrm.skip( 8 ); + rStrm >> nFlags >> nFmla1Size >> nFmla2Size >> nFmla3Size >> maModel.maText; + + /* Import the formulas. For no obvious reason, the sizes of the formulas + are already stored before. Nevertheless the following formulas contain + their own sizes. */ + + // first formula + OSL_ENSURE( (nFmla1Size >= 0) || ((nFmla2Size == 0) && (nFmla3Size == 0)), "CondFormatRule::importCfRule - missing first formula" ); + OSL_ENSURE( (nFmla1Size > 0) == (rStrm.getRemaining() >= 8), "CondFormatRule::importCfRule - formula size mismatch" ); + if( rStrm.getRemaining() >= 8 ) + { + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( mrCondFormat.getRanges().getBaseAddress() ); + getFormulaParser().importFormula( aContext, rStrm ); + maModel.maFormulas.push_back( aContext ); + + // second formula + OSL_ENSURE( (nFmla2Size >= 0) || (nFmla3Size == 0), "CondFormatRule::importCfRule - missing second formula" ); + OSL_ENSURE( (nFmla2Size > 0) == (rStrm.getRemaining() >= 8), "CondFormatRule::importCfRule - formula size mismatch" ); + if( rStrm.getRemaining() >= 8 ) + { + getFormulaParser().importFormula( aContext, rStrm ); + maModel.maFormulas.push_back( aContext ); + + // third formula + OSL_ENSURE( (nFmla3Size > 0) == (rStrm.getRemaining() >= 8), "CondFormatRule::importCfRule - formula size mismatch" ); + if( rStrm.getRemaining() >= 8 ) + { + getFormulaParser().importFormula( aContext, rStrm ); + maModel.maFormulas.push_back( aContext ); + } + } + } + + // flags + maModel.mbStopIfTrue = getFlag( nFlags, OOBIN_CFRULE_STOPIFTRUE ); + maModel.mbBottom = getFlag( nFlags, OOBIN_CFRULE_BOTTOM ); + maModel.mbPercent = getFlag( nFlags, OOBIN_CFRULE_PERCENT ); + maModel.mbAboveAverage = getFlag( nFlags, OOBIN_CFRULE_ABOVEAVERAGE ); + // no flag for equalAverage, must be determined from subtype below... + + // Convert the type/operator settings. This is a real mess... + switch( nType ) + { + case OOBIN_CFRULE_TYPE_CELLIS: + OSL_ENSURE( nSubType == OOBIN_CFRULE_SUB_CELLIS, "CondFormatRule::importCfRule - rule type/subtype mismatch" ); + maModel.mnType = XML_cellIs; + maModel.setBinOperator( nOperator ); + OSL_ENSURE( maModel.mnOperator != XML_TOKEN_INVALID, "CondFormatRule::importCfRule - unknown operator" ); + break; + case OOBIN_CFRULE_TYPE_EXPRESSION: + // here we have to look at the subtype to find the real type... + switch( nSubType ) + { + case OOBIN_CFRULE_SUB_EXPRESSION: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_expression; + break; + case OOBIN_CFRULE_SUB_UNIQUE: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_uniqueValues; + break; + case OOBIN_CFRULE_SUB_TEXT: + maModel.setOobTextType( nOperator ); + OSL_ENSURE( maModel.mnType != XML_TOKEN_INVALID, "CondFormatRule::importCfRule - unexpected operator value" ); + break; + case OOBIN_CFRULE_SUB_BLANK: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_containsBlanks; + break; + case OOBIN_CFRULE_SUB_NOTBLANK: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_notContainsBlanks; + break; + case OOBIN_CFRULE_SUB_ERROR: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_containsErrors; + break; + case OOBIN_CFRULE_SUB_NOTERROR: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_notContainsErrors; + break; + case OOBIN_CFRULE_SUB_TODAY: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_TODAY, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_today; + break; + case OOBIN_CFRULE_SUB_TOMORROW: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_TOMORROW, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_tomorrow; + break; + case OOBIN_CFRULE_SUB_YESTERDAY: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_YESTERDAY, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_yesterday; + break; + case OOBIN_CFRULE_SUB_LAST7DAYS: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_LAST7DAYS, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_last7Days; + break; + case OOBIN_CFRULE_SUB_LASTMONTH: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_LASTMONTH, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_lastMonth; + break; + case OOBIN_CFRULE_SUB_NEXTMONTH: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_NEXTMONTH, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_nextMonth; + break; + case OOBIN_CFRULE_SUB_THISWEEK: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_THISWEEK, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_thisWeek; + break; + case OOBIN_CFRULE_SUB_NEXTWEEK: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_NEXTWEEK, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_nextWeek; + break; + case OOBIN_CFRULE_SUB_LASTWEEK: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_LASTWEEK, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_lastWeek; + break; + case OOBIN_CFRULE_SUB_THISMONTH: + OSL_ENSURE( nOperator == OOBIN_CFRULE_TIMEOP_THISMONTH, "CondFormatRule::importCfRule - unexpected time operator value" ); + maModel.mnType = XML_timePeriod; + maModel.mnTimePeriod = XML_thisMonth; + break; + case OOBIN_CFRULE_SUB_ABOVEAVERAGE: + OSL_ENSURE( maModel.mbAboveAverage, "CondFormatRule::importCfRule - wrong above-average flag" ); + maModel.mnType = XML_aboveAverage; + maModel.mnStdDev = nOperator; // operator field used for standard deviation + maModel.mbAboveAverage = true; + maModel.mbEqualAverage = false; // does not exist as real flag... + break; + case OOBIN_CFRULE_SUB_BELOWAVERAGE: + OSL_ENSURE( !maModel.mbAboveAverage, "CondFormatRule::importCfRule - wrong above-average flag" ); + maModel.mnType = XML_aboveAverage; + maModel.mnStdDev = nOperator; // operator field used for standard deviation + maModel.mbAboveAverage = false; + maModel.mbEqualAverage = false; // does not exist as real flag... + break; + case OOBIN_CFRULE_SUB_DUPLICATE: + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_duplicateValues; + break; + case OOBIN_CFRULE_SUB_EQABOVEAVERAGE: + OSL_ENSURE( maModel.mbAboveAverage, "CondFormatRule::importCfRule - wrong above-average flag" ); + maModel.mnType = XML_aboveAverage; + maModel.mnStdDev = nOperator; // operator field used for standard deviation + maModel.mbAboveAverage = true; + maModel.mbEqualAverage = true; // does not exist as real flag... + break; + case OOBIN_CFRULE_SUB_EQBELOWAVERAGE: + OSL_ENSURE( !maModel.mbAboveAverage, "CondFormatRule::importCfRule - wrong above-average flag" ); + maModel.mnType = XML_aboveAverage; + maModel.mnStdDev = nOperator; // operator field used for standard deviation + maModel.mbAboveAverage = false; + maModel.mbEqualAverage = true; // does not exist as real flag... + break; + } + break; + case OOBIN_CFRULE_TYPE_COLORSCALE: + OSL_ENSURE( nSubType == OOBIN_CFRULE_SUB_COLORSCALE, "CondFormatRule::importCfRule - rule type/subtype mismatch" ); + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_colorScale; + break; + case OOBIN_CFRULE_TYPE_DATABAR: + OSL_ENSURE( nSubType == OOBIN_CFRULE_SUB_DATABAR, "CondFormatRule::importCfRule - rule type/subtype mismatch" ); + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_dataBar; + break; + case OOBIN_CFRULE_TYPE_TOPTEN: + OSL_ENSURE( nSubType == OOBIN_CFRULE_SUB_TOPTEN, "CondFormatRule::importCfRule - rule type/subtype mismatch" ); + maModel.mnType = XML_top10; + maModel.mnRank = nOperator; // operator field used for rank value + break; + case OOBIN_CFRULE_TYPE_ICONSET: + OSL_ENSURE( nSubType == OOBIN_CFRULE_SUB_ICONSET, "CondFormatRule::importCfRule - rule type/subtype mismatch" ); + OSL_ENSURE( nOperator == 0, "CondFormatRule::importCfRule - unexpected operator value" ); + maModel.mnType = XML_iconSet; + break; + default: + OSL_ENSURE( false, "CondFormatRule::importCfRule - unknown rule type" ); + } +} + +void CondFormatRule::importCfRule( BiffInputStream& rStrm, sal_Int32 nPriority ) +{ + sal_uInt8 nType, nOperator; + sal_uInt16 nFmla1Size, nFmla2Size; + sal_uInt32 nFlags; + rStrm >> nType >> nOperator >> nFmla1Size >> nFmla2Size >> nFlags; + rStrm.skip( 2 ); + + static const sal_Int32 spnTypeIds[] = { XML_TOKEN_INVALID, XML_cellIs, XML_expression }; + maModel.mnType = STATIC_ARRAY_SELECT( spnTypeIds, nType, XML_TOKEN_INVALID ); + + maModel.setBinOperator( nOperator ); + maModel.mnPriority = nPriority; + maModel.mbStopIfTrue = true; + + DxfRef xDxf = getStyles().createDxf( &maModel.mnDxfId ); + xDxf->importCfRule( rStrm, nFlags ); + xDxf->finalizeImport(); + + // import the formulas + OSL_ENSURE( (nFmla1Size > 0) || (nFmla2Size == 0), "CondFormatRule::importCfRule - missing first formula" ); + if( nFmla1Size > 0 ) + { + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( mrCondFormat.getRanges().getBaseAddress() ); + getFormulaParser().importFormula( aContext, rStrm, &nFmla1Size ); + maModel.maFormulas.push_back( aContext ); + if( nFmla2Size > 0 ) + { + getFormulaParser().importFormula( aContext, rStrm, &nFmla2Size ); + maModel.maFormulas.push_back( aContext ); + } + } +} + +void CondFormatRule::finalizeImport( const Reference< XSheetConditionalEntries >& rxEntries ) +{ + ConditionOperator eOperator = ::com::sun::star::sheet::ConditionOperator_NONE; + + /* Replacement formula for unsupported rule types (text comparison rules, + time period rules, cell type rules). The replacement formulas below may + contain several placeholders: + - '#B' will be replaced by the current relative base address (may occur + several times). + - '#R' will be replaced by the entire range list of the conditional + formatting (absolute addresses). + - '#T' will be replaced by the quoted comparison text. + - '#L' will be replaced by the length of the comparison text (from + the 'text' attribute) used in text comparison rules. + - '#K' will be replaced by the rank (from the 'rank' attribute) used in + top-10 rules. + - '#M' will be replaced by the top/bottom flag (from the 'bottom' + attribute) used in the RANK function in top-10 rules. + - '#C' will be replaced by one of the comparison operators <, >, <=, or + >=, according to the 'aboveAverage' and 'equalAverage' flags. + */ + OUString aReplaceFormula; + + switch( maModel.mnType ) + { + case XML_cellIs: + eOperator = CondFormatBuffer::convertToApiOperator( maModel.mnOperator ); + break; + case XML_expression: + eOperator = ::com::sun::star::sheet::ConditionOperator_FORMULA; + break; + case XML_containsText: + OSL_ENSURE( maModel.mnOperator == XML_containsText, "CondFormatRule::finalizeImport - unexpected operator" ); + aReplaceFormula = CREATE_OUSTRING( "NOT(ISERROR(SEARCH(#T,#B)))" ); + break; + case XML_notContainsText: + // note: type XML_notContainsText vs. operator XML_notContains + OSL_ENSURE( maModel.mnOperator == XML_notContains, "CondFormatRule::finalizeImport - unexpected operator" ); + aReplaceFormula = CREATE_OUSTRING( "ISERROR(SEARCH(#T,#B))" ); + break; + case XML_beginsWith: + OSL_ENSURE( maModel.mnOperator == XML_beginsWith, "CondFormatRule::finalizeImport - unexpected operator" ); + aReplaceFormula = CREATE_OUSTRING( "LEFT(#B,#L)=#T" ); + break; + case XML_endsWith: + OSL_ENSURE( maModel.mnOperator == XML_endsWith, "CondFormatRule::finalizeImport - unexpected operator" ); + aReplaceFormula = CREATE_OUSTRING( "RIGHT(#B,#L)=#T" ); + break; + case XML_timePeriod: + switch( maModel.mnTimePeriod ) + { + case XML_yesterday: + aReplaceFormula = CREATE_OUSTRING( "FLOOR(#B,1)=TODAY()-1" ); + break; + case XML_today: + aReplaceFormula = CREATE_OUSTRING( "FLOOR(#B,1)=TODAY()" ); + break; + case XML_tomorrow: + aReplaceFormula = CREATE_OUSTRING( "FLOOR(#B,1)=TODAY()+1" ); + break; + case XML_last7Days: + aReplaceFormula = CREATE_OUSTRING( "AND(TODAY()-7<FLOOR(#B,1),FLOOR(#B,1)<=TODAY())" ); + break; + case XML_lastWeek: + aReplaceFormula = CREATE_OUSTRING( "AND(TODAY()-WEEKDAY(TODAY())-7<FLOOR(#B,1),FLOOR(#B,1)<=TODAY()-WEEKDAY(TODAY()))" ); + break; + case XML_thisWeek: + aReplaceFormula = CREATE_OUSTRING( "AND(TODAY()-WEEKDAY(TODAY())<FLOOR(#B,1),FLOOR(#B,1)<=TODAY()-WEEKDAY(TODAY())+7)" ); + break; + case XML_nextWeek: + aReplaceFormula = CREATE_OUSTRING( "AND(TODAY()-WEEKDAY(TODAY())+7<FLOOR(#B,1),FLOOR(#B,1)<=TODAY()-WEEKDAY(TODAY())+14)" ); + break; + case XML_lastMonth: + aReplaceFormula = CREATE_OUSTRING( "OR(AND(MONTH(#B)=MONTH(TODAY())-1,YEAR(#B)=YEAR(TODAY())),AND(MONTH(#B)=12,MONTH(TODAY())=1,YEAR(#B)=YEAR(TODAY())-1))" ); + break; + case XML_thisMonth: + aReplaceFormula = CREATE_OUSTRING( "AND(MONTH(#B)=MONTH(TODAY()),YEAR(#B)=YEAR(TODAY()))" ); + break; + case XML_nextMonth: + aReplaceFormula = CREATE_OUSTRING( "OR(AND(MONTH(#B)=MONTH(TODAY())+1,YEAR(#B)=YEAR(TODAY())),AND(MONTH(#B)=1,MONTH(TODAY())=12,YEAR(#B)=YEAR(TODAY())+1))" ); + break; + default: + OSL_ENSURE( false, "CondFormatRule::finalizeImport - unknown time period type" ); + } + break; + case XML_containsBlanks: + aReplaceFormula = CREATE_OUSTRING( "LEN(TRIM(#B))=0" ); + break; + case XML_notContainsBlanks: + aReplaceFormula = CREATE_OUSTRING( "LEN(TRIM(#B))>0" ); + break; + case XML_containsErrors: + aReplaceFormula = CREATE_OUSTRING( "ISERROR(#B)" ); + break; + case XML_notContainsErrors: + aReplaceFormula = CREATE_OUSTRING( "NOT(ISERROR(#B))" ); + break; + case XML_top10: + if( maModel.mbPercent ) + aReplaceFormula = CREATE_OUSTRING( "RANK(#B,#R,#M)/COUNT(#R)<=#K%" ); + else + aReplaceFormula = CREATE_OUSTRING( "RANK(#B,#R,#M)<=#K" ); + break; + case XML_aboveAverage: + if( maModel.mnStdDev == 0 ) + aReplaceFormula = CREATE_OUSTRING( "#B#CAVERAGE(#R)" ); + break; + } + + if( aReplaceFormula.getLength() > 0 ) + { + OUString aAddress, aRanges, aText, aComp; + sal_Int32 nStrPos = aReplaceFormula.getLength(); + while( (nStrPos = aReplaceFormula.lastIndexOf( '#', nStrPos )) >= 0 ) + { + switch( aReplaceFormula[ nStrPos + 1 ] ) + { + case 'B': // current base address + if( aAddress.getLength() == 0 ) + aAddress = FormulaProcessorBase::generateAddress2dString( mrCondFormat.getRanges().getBaseAddress(), false ); + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, aAddress ); + break; + case 'R': // range list of conditional formatting + if( aRanges.getLength() == 0 ) + aRanges = FormulaProcessorBase::generateRangeList2dString( mrCondFormat.getRanges(), true, ',', true ); + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, aRanges ); + break; + case 'T': // comparison text + if( aText.getLength() == 0 ) + // quote the comparison text, and handle embedded quote characters + aText = FormulaProcessorBase::generateApiString( maModel.maText ); + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, aText ); + break; + case 'L': // length of comparison text + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, + OUString::valueOf( maModel.maText.getLength() ) ); + break; + case 'K': // top-10 rank + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, + OUString::valueOf( maModel.mnRank ) ); + break; + case 'M': // top-10 top/bottom flag + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, + OUString::valueOf( static_cast< sal_Int32 >( maModel.mbBottom ? 1 : 0 ) ) ); + break; + case 'C': // average comparison operator + if( aComp.getLength() == 0 ) + aComp = maModel.mbAboveAverage ? + (maModel.mbEqualAverage ? CREATE_OUSTRING( ">=" ) : CREATE_OUSTRING( ">" )) : + (maModel.mbEqualAverage ? CREATE_OUSTRING( "<=" ) : CREATE_OUSTRING( "<" )); + aReplaceFormula = aReplaceFormula.replaceAt( nStrPos, 2, aComp ); + break; + default: + OSL_ENSURE( false, "CondFormatRule::finalizeImport - unknown placeholder" ); + } + } + + // set the replacement formula + maModel.maFormulas.clear(); + appendFormula( aReplaceFormula ); + eOperator = ::com::sun::star::sheet::ConditionOperator_FORMULA; + } + + if( rxEntries.is() && (eOperator != ::com::sun::star::sheet::ConditionOperator_NONE) && !maModel.maFormulas.empty() ) + { + ::std::vector< PropertyValue > aProps; + // create condition properties + lclAppendProperty( aProps, CREATE_OUSTRING( "Operator" ), eOperator ); + lclAppendProperty( aProps, CREATE_OUSTRING( "Formula1" ), maModel.maFormulas[ 0 ].getTokens() ); + if( maModel.maFormulas.size() >= 2 ) + lclAppendProperty( aProps, CREATE_OUSTRING( "Formula2" ), maModel.maFormulas[ 1 ].getTokens() ); + + // style name for the formatting attributes + OUString aStyleName = getStyles().createDxfStyle( maModel.mnDxfId ); + if( aStyleName.getLength() > 0 ) + lclAppendProperty( aProps, CREATE_OUSTRING( "StyleName" ), aStyleName ); + + // append the new rule + try + { + rxEntries->addNew( ContainerHelper::vectorToSequence( aProps ) ); + } + catch( Exception& ) + { + } + } +} + +// ============================================================================ + +CondFormatModel::CondFormatModel() : + mbPivot( false ) +{ +} + +// ============================================================================ + +CondFormat::CondFormat( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +void CondFormat::importConditionalFormatting( const AttributeList& rAttribs ) +{ + getAddressConverter().convertToCellRangeList( maModel.maRanges, rAttribs.getString( XML_sqref, OUString() ), getSheetIndex(), true ); + maModel.mbPivot = rAttribs.getBool( XML_pivot, false ); +} + +CondFormatRuleRef CondFormat::importCfRule( const AttributeList& rAttribs ) +{ + CondFormatRuleRef xRule = createRule(); + xRule->importCfRule( rAttribs ); + insertRule( xRule ); + return xRule; +} + +void CondFormat::importCondFormatting( RecordInputStream& rStrm ) +{ + BinRangeList aRanges; + rStrm.skip( 8 ); + rStrm >> aRanges; + getAddressConverter().convertToCellRangeList( maModel.maRanges, aRanges, getSheetIndex(), true ); +} + +void CondFormat::importCfRule( RecordInputStream& rStrm ) +{ + CondFormatRuleRef xRule = createRule(); + xRule->importCfRule( rStrm ); + insertRule( xRule ); +} + +void CondFormat::importCfHeader( BiffInputStream& rStrm ) +{ + // import the CFHEADER record + sal_uInt16 nRuleCount; + BinRangeList aRanges; + rStrm >> nRuleCount; + rStrm.skip( 10 ); + rStrm >> aRanges; + getAddressConverter().convertToCellRangeList( maModel.maRanges, aRanges, getSheetIndex(), true ); + + // import following list of CFRULE records + for( sal_uInt16 nRule = 0; (nRule < nRuleCount) && (rStrm.getNextRecId() == BIFF_ID_CFRULE) && rStrm.startNextRecord(); ++nRule ) + { + CondFormatRuleRef xRule = createRule(); + xRule->importCfRule( rStrm, nRule + 1 ); + insertRule( xRule ); + } +} + +void CondFormat::finalizeImport() +{ + Reference< XSheetCellRanges > xRanges = getCellRangeList( maModel.maRanges ); + if( xRanges.is() ) + { + PropertySet aPropSet( xRanges ); + Reference< XSheetConditionalEntries > xEntries; + aPropSet.getProperty( xEntries, PROP_ConditionalFormat ); + if( xEntries.is() ) + { + // maRules is sorted by rule priority + maRules.forEachMem( &CondFormatRule::finalizeImport, ::boost::cref( xEntries ) ); + aPropSet.setProperty( PROP_ConditionalFormat, xEntries ); + } + } +} + +CondFormatRuleRef CondFormat::createRule() +{ + return CondFormatRuleRef( new CondFormatRule( *this ) ); +} + +void CondFormat::insertRule( CondFormatRuleRef xRule ) +{ + if( xRule.get() && (xRule->getPriority() > 0) ) + { + OSL_ENSURE( maRules.find( xRule->getPriority() ) == maRules.end(), "CondFormat::insertRule - multiple rules with equal priority" ); + maRules[ xRule->getPriority() ] = xRule; + } +} + +// ============================================================================ + +CondFormatBuffer::CondFormatBuffer( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +CondFormatRef CondFormatBuffer::importConditionalFormatting( const AttributeList& rAttribs ) +{ + CondFormatRef xCondFmt = createCondFormat(); + xCondFmt->importConditionalFormatting( rAttribs ); + return xCondFmt; +} + +CondFormatRef CondFormatBuffer::importCondFormatting( RecordInputStream& rStrm ) +{ + CondFormatRef xCondFmt = createCondFormat(); + xCondFmt->importCondFormatting( rStrm ); + return xCondFmt; +} + +void CondFormatBuffer::importCfHeader( BiffInputStream& rStrm ) +{ + createCondFormat()->importCfHeader( rStrm ); +} + +void CondFormatBuffer::finalizeImport() +{ + maCondFormats.forEachMem( &CondFormat::finalizeImport ); +} + +ConditionOperator CondFormatBuffer::convertToApiOperator( sal_Int32 nToken ) +{ + using namespace ::com::sun::star::sheet; + switch( nToken ) + { + case XML_between: return ConditionOperator_BETWEEN; + case XML_equal: return ConditionOperator_EQUAL; + case XML_greaterThan: return ConditionOperator_GREATER; + case XML_greaterThanOrEqual: return ConditionOperator_GREATER_EQUAL; + case XML_lessThan: return ConditionOperator_LESS; + case XML_lessThanOrEqual: return ConditionOperator_LESS_EQUAL; + case XML_notBetween: return ConditionOperator_NOT_BETWEEN; + case XML_notEqual: return ConditionOperator_NOT_EQUAL; + } + return ConditionOperator_NONE; +} + +// private -------------------------------------------------------------------- + +CondFormatRef CondFormatBuffer::createCondFormat() +{ + CondFormatRef xCondFmt( new CondFormat( *this ) ); + maCondFormats.push_back( xCondFmt ); + return xCondFmt; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/condformatcontext.cxx b/oox/source/xls/condformatcontext.cxx new file mode 100644 index 000000000000..8cd3a33615fc --- /dev/null +++ b/oox/source/xls/condformatcontext.cxx @@ -0,0 +1,107 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/condformatcontext.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxCondFormatContext::OoxCondFormatContext( OoxWorksheetFragmentBase& rFragment ) : + OoxWorksheetContextBase( rFragment ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxCondFormatContext::onCreateContext( sal_Int32 nElement, const AttributeList& ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( conditionalFormatting ): + return (nElement == XLS_TOKEN( cfRule )) ? this : 0; + case XLS_TOKEN( cfRule ): + return (nElement == XLS_TOKEN( formula )) ? this : 0; + } + return 0; +} + +void OoxCondFormatContext::onStartElement( const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( conditionalFormatting ): + mxCondFmt = getCondFormats().importConditionalFormatting( rAttribs ); + break; + case XLS_TOKEN( cfRule ): + if( mxCondFmt.get() ) mxRule = mxCondFmt->importCfRule( rAttribs ); + break; + } +} + +void OoxCondFormatContext::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( formula ): + if( mxCondFmt.get() && mxRule.get() ) mxRule->appendFormula( rChars ); + break; + } +} + +ContextHandlerRef OoxCondFormatContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_CONDFORMATTING: + return (nRecId == OOBIN_ID_CFRULE) ? this : 0; + } + return 0; +} + +void OoxCondFormatContext::onStartRecord( RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_CONDFORMATTING: + mxCondFmt = getCondFormats().importCondFormatting( rStrm ); + break; + case OOBIN_ID_CFRULE: + if( mxCondFmt.get() ) mxCondFmt->importCfRule( rStrm ); + break; + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/connectionsfragment.cxx b/oox/source/xls/connectionsfragment.cxx new file mode 100644 index 000000000000..f19204306d1b --- /dev/null +++ b/oox/source/xls/connectionsfragment.cxx @@ -0,0 +1,113 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/connectionsfragment.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/xls/webquerybuffer.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +OoxConnectionsFragment::OoxConnectionsFragment( const WorkbookHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ) +{ +} + +ContextHandlerRef OoxConnectionsFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( connections ) ) return this; + break; + + case XLS_TOKEN( connections ): + switch( nElement ) + { + case XLS_TOKEN( connection ): importConnection( rAttribs ); return this; + } + break; + + case XLS_TOKEN( connection ): + switch( nElement ) + { + case XLS_TOKEN( webPr ): importWebPr( rAttribs ); return this; + } + break; + + case XLS_TOKEN( webPr ): + switch( nElement ) + { + case XLS_TOKEN( tables ): importTables( rAttribs ); return this; + } + break; + + case XLS_TOKEN( tables ): + switch( nElement ) + { + case XLS_TOKEN( s ): importS( rAttribs ); break; + case XLS_TOKEN( x ): importX( rAttribs ); break; + } + break; + } + return 0; +} + +void OoxConnectionsFragment::importConnection( const AttributeList& rAttribs ) +{ + if ( rAttribs.getInteger( XML_type, 0 ) == Connection::CONNECTION_WEBQUERY ) + { + getWebQueries().importConnection( rAttribs ); + } +} + +void OoxConnectionsFragment::importWebPr( const AttributeList& rAttribs ) +{ + getWebQueries().importWebPr( rAttribs ); +} + +void OoxConnectionsFragment::importTables( const AttributeList& /*rAttribs*/ ) +{ +// sal_Int32 nCount = rAttribs.getInteger( XML_count, 0 ); +} + +void OoxConnectionsFragment::importS( const AttributeList& /*rAttribs*/ ) +{ +// OUString aName = rAttribs.getString( XML_v ); +} + +void OoxConnectionsFragment::importX( const AttributeList& /*rAttribs*/ ) +{ +// sal_Int32 nSharedId = rAttribs.getInteger( XML_v, 0 ); +} + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/defnamesbuffer.cxx b/oox/source/xls/defnamesbuffer.cxx new file mode 100644 index 000000000000..a02375e2b869 --- /dev/null +++ b/oox/source/xls/defnamesbuffer.cxx @@ -0,0 +1,712 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/defnamesbuffer.hxx" +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/sheet/ComplexReference.hpp> +#include <com/sun/star/sheet/ExternalReference.hpp> +#include <com/sun/star/sheet/NamedRangeFlag.hpp> +#include <com/sun/star/sheet/ReferenceFlags.hpp> +#include <com/sun/star/sheet/SingleReference.hpp> +#include <com/sun/star/sheet/XFormulaTokens.hpp> +#include <com/sun/star/sheet/XPrintAreas.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/externallinkbuffer.hxx" +#include "oox/xls/formulaparser.hxx" +#include "oox/xls/worksheetbuffer.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::ComplexReference; +using ::com::sun::star::sheet::ExternalReference; +using ::com::sun::star::sheet::SingleReference; +using ::com::sun::star::sheet::XFormulaTokens; +using ::com::sun::star::sheet::XPrintAreas; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt32 OOBIN_DEFNAME_HIDDEN = 0x00000001; +const sal_uInt32 OOBIN_DEFNAME_FUNC = 0x00000002; +const sal_uInt32 OOBIN_DEFNAME_VBNAME = 0x00000004; +const sal_uInt32 OOBIN_DEFNAME_MACRO = 0x00000008; +const sal_uInt32 OOBIN_DEFNAME_CALCEXP = 0x00000010; +const sal_uInt32 OOBIN_DEFNAME_BUILTIN = 0x00000020; +const sal_uInt32 OOBIN_DEFNAME_PUBLISHED = 0x00008000; +const sal_uInt32 OOBIN_DEFNAME_WBPARAM = 0x00010000; + +const sal_uInt16 BIFF_DEFNAME_HIDDEN = 0x0001; +const sal_uInt16 BIFF_DEFNAME_FUNC = 0x0002; +const sal_uInt16 BIFF_DEFNAME_VBNAME = 0x0004; +const sal_uInt16 BIFF_DEFNAME_MACRO = 0x0008; +const sal_uInt16 BIFF_DEFNAME_CALCEXP = 0x0010; +const sal_uInt16 BIFF_DEFNAME_BUILTIN = 0x0020; +const sal_uInt16 BIFF_DEFNAME_BIG = 0x1000; + +const sal_uInt8 BIFF2_DEFNAME_FUNC = 0x02; /// BIFF2 function/command flag. + +const sal_uInt16 BIFF_DEFNAME_GLOBAL = 0; /// 0 = Globally defined name. + +// ---------------------------------------------------------------------------- + +const sal_Char* const spcLegacyPrefix = "Excel_BuiltIn_"; +const sal_Char* const spcOoxPrefix = "_xlnm."; + +const sal_Char* const sppcBaseNames[] = +{ + "Consolidate_Area", /* OOX */ + "Auto_Open", + "Auto_Close", + "Extract", /* OOX */ + "Database", /* OOX */ + "Criteria", /* OOX */ + "Print_Area", /* OOX */ + "Print_Titles", /* OOX */ + "Recorder", + "Data_Form", + "Auto_Activate", + "Auto_Deactivate", + "Sheet_Title", /* OOX */ + "_FilterDatabase" /* OOX */ +}; + +/** Localized names for _xlnm._FilterDatabase as used in BIFF5. */ +const sal_Char* const sppcFilterDbNames[] = +{ + "_FilterDatabase", // English + "_FilterDatenbank" // German +}; + +OUString lclGetBaseName( sal_Unicode cBuiltinId ) +{ + OSL_ENSURE( cBuiltinId < STATIC_ARRAY_SIZE( sppcBaseNames ), "lclGetBaseName - unknown builtin name" ); + OUStringBuffer aBuffer; + if( cBuiltinId < STATIC_ARRAY_SIZE( sppcBaseNames ) ) + aBuffer.appendAscii( sppcBaseNames[ cBuiltinId ] ); + else + aBuffer.append( static_cast< sal_Int32 >( cBuiltinId ) ); + return aBuffer.makeStringAndClear(); +} + +OUString lclGetBuiltinName( sal_Unicode cBuiltinId ) +{ + return OUStringBuffer().appendAscii( spcOoxPrefix ).append( lclGetBaseName( cBuiltinId ) ).makeStringAndClear(); +} + +sal_Unicode lclGetBuiltinIdFromOox( const OUString& rOoxName ) +{ + OUString aPrefix = OUString::createFromAscii( spcOoxPrefix ); + sal_Int32 nPrefixLen = aPrefix.getLength(); + if( rOoxName.matchIgnoreAsciiCase( aPrefix ) ) + { + for( sal_Unicode cBuiltinId = 0; cBuiltinId < STATIC_ARRAY_SIZE( sppcBaseNames ); ++cBuiltinId ) + { + OUString aBaseName = lclGetBaseName( cBuiltinId ); + sal_Int32 nBaseNameLen = aBaseName.getLength(); + if( (rOoxName.getLength() == nPrefixLen + nBaseNameLen) && rOoxName.matchIgnoreAsciiCase( aBaseName, nPrefixLen ) ) + return cBuiltinId; + } + } + return OOX_DEFNAME_UNKNOWN; +} + +sal_Unicode lclGetBuiltinIdFromOob( const OUString& rOobName ) +{ + for( sal_Unicode cBuiltinId = 0; cBuiltinId < STATIC_ARRAY_SIZE( sppcBaseNames ); ++cBuiltinId ) + if( rOobName.equalsIgnoreAsciiCaseAscii( sppcBaseNames[ cBuiltinId ] ) ) + return cBuiltinId; + return OOX_DEFNAME_UNKNOWN; +} + +bool lclIsFilterDatabaseName( const OUString& rName ) +{ + for( const sal_Char* const* ppcName = sppcFilterDbNames; ppcName < STATIC_ARRAY_END( sppcFilterDbNames ); ++ppcName ) + if( rName.equalsIgnoreAsciiCaseAscii( *ppcName ) ) + return true; + return false; +} + +} // namespace + +// ============================================================================ + +DefinedNameModel::DefinedNameModel() : + mnSheet( -1 ), + mnFuncGroupId( -1 ), + mbMacro( false ), + mbFunction( false ), + mbVBName( false ), + mbHidden( false ) +{ +} + +// ============================================================================ + +namespace { + +const sal_uInt16 BIFF_REFFLAG_COL1REL = 0x0001; +const sal_uInt16 BIFF_REFFLAG_ROW1REL = 0x0002; +const sal_uInt16 BIFF_REFFLAG_COL2REL = 0x0004; +const sal_uInt16 BIFF_REFFLAG_ROW2REL = 0x0008; + +void lclConvertRefFlags( sal_Int32& ornFlags, sal_Int32& ornAbsPos, sal_Int32& ornRelPos, sal_Int32 nBasePos, sal_Int32 nApiRelFlag, bool bRel ) +{ + if( getFlag( ornFlags, nApiRelFlag ) && !bRel ) + { + // convert relative to absolute + setFlag( ornFlags, nApiRelFlag, false ); + ornAbsPos = nBasePos + ornRelPos; + } + else if( !getFlag( ornFlags, nApiRelFlag ) && bRel ) + { + // convert absolute to relative + setFlag( ornFlags, nApiRelFlag, true ); + ornRelPos = ornAbsPos - nBasePos; + } +} + +void lclConvertSingleRefFlags( SingleReference& orApiRef, const CellAddress& rBaseAddress, bool bColRel, bool bRowRel ) +{ + using namespace ::com::sun::star::sheet::ReferenceFlags; + lclConvertRefFlags( + orApiRef.Flags, orApiRef.Column, orApiRef.RelativeColumn, + rBaseAddress.Column, COLUMN_RELATIVE, bColRel ); + lclConvertRefFlags( + orApiRef.Flags, orApiRef.Row, orApiRef.RelativeRow, + rBaseAddress.Row, ROW_RELATIVE, bRowRel ); +} + +Any lclConvertReference( const Any& rRefAny, const CellAddress& rBaseAddress, sal_uInt16 nRelFlags ) +{ + if( rRefAny.has< SingleReference >() && !getFlag( nRelFlags, BIFF_REFFLAG_COL2REL ) && !getFlag( nRelFlags, BIFF_REFFLAG_ROW2REL ) ) + { + SingleReference aApiRef; + rRefAny >>= aApiRef; + lclConvertSingleRefFlags( aApiRef, rBaseAddress, getFlag( nRelFlags, BIFF_REFFLAG_COL1REL ), getFlag( nRelFlags, BIFF_REFFLAG_ROW1REL ) ); + return Any( aApiRef ); + } + if( rRefAny.has< ComplexReference >() ) + { + ComplexReference aApiRef; + rRefAny >>= aApiRef; + lclConvertSingleRefFlags( aApiRef.Reference1, rBaseAddress, getFlag( nRelFlags, BIFF_REFFLAG_COL1REL ), getFlag( nRelFlags, BIFF_REFFLAG_ROW1REL ) ); + lclConvertSingleRefFlags( aApiRef.Reference2, rBaseAddress, getFlag( nRelFlags, BIFF_REFFLAG_COL2REL ), getFlag( nRelFlags, BIFF_REFFLAG_ROW2REL ) ); + return Any( aApiRef ); + } + return Any(); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +DefinedNameBase::DefinedNameBase( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +const OUString& DefinedNameBase::getUpcaseModelName() const +{ + if( maUpModelName.getLength() == 0 ) + maUpModelName = maModel.maName.toAsciiUpperCase(); + return maUpModelName; +} + +Any DefinedNameBase::getReference( const CellAddress& rBaseAddress ) const +{ + if( maRefAny.hasValue() && (maModel.maName.getLength() >= 2) && (maModel.maName[ 0 ] == '\x01') ) + { + sal_Unicode cFlagsChar = getUpcaseModelName()[ 1 ]; + if( ('A' <= cFlagsChar) && (cFlagsChar <= 'P') ) + { + sal_uInt16 nRelFlags = static_cast< sal_uInt16 >( cFlagsChar - 'A' ); + if( maRefAny.has< ExternalReference >() ) + { + ExternalReference aApiExtRef; + maRefAny >>= aApiExtRef; + Any aRefAny = lclConvertReference( aApiExtRef.Reference, rBaseAddress, nRelFlags ); + if( aRefAny.hasValue() ) + { + aApiExtRef.Reference <<= aRefAny; + return Any( aApiExtRef ); + } + } + else + { + return lclConvertReference( maRefAny, rBaseAddress, nRelFlags ); + } + } + } + return Any(); +} + +void DefinedNameBase::importOoxFormula( FormulaContext& rContext, sal_Int16 nBaseSheet ) +{ + if( maModel.maFormula.getLength() > 0 ) + { + rContext.setBaseAddress( CellAddress( nBaseSheet, 0, 0 ) ); + getFormulaParser().importFormula( rContext, maModel.maFormula ); + } + else + getFormulaParser().convertErrorToFormula( rContext, BIFF_ERR_NAME ); +} + +void DefinedNameBase::importOobFormula( FormulaContext& rContext, sal_Int16 nBaseSheet, RecordInputStream& rStrm ) +{ + rContext.setBaseAddress( CellAddress( nBaseSheet, 0, 0 ) ); + getFormulaParser().importFormula( rContext, rStrm ); +} + +void DefinedNameBase::importBiffFormula( FormulaContext& rContext, sal_Int16 nBaseSheet, BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize ) +{ + rContext.setBaseAddress( CellAddress( nBaseSheet, 0, 0 ) ); + if( !pnFmlaSize || (*pnFmlaSize > 0) ) + getFormulaParser().importFormula( rContext, rStrm, pnFmlaSize ); + else + getFormulaParser().convertErrorToFormula( rContext, BIFF_ERR_NAME ); +} + +void DefinedNameBase::extractReference( const ApiTokenSequence& rTokens ) +{ + OSL_ENSURE( (getFilterType() == FILTER_BIFF) && (getBiff() <= BIFF4), "DefinedNameBase::extractReference - unexpected call" ); + maRefAny = getFormulaParser().extractReference( rTokens ); +} + +// ============================================================================ + +DefinedName::DefinedName( const WorkbookHelper& rHelper ) : + DefinedNameBase( rHelper ), + mnTokenIndex( -1 ), + mcBuiltinId( OOX_DEFNAME_UNKNOWN ), + mnFmlaSize( 0 ) +{ +} + +void DefinedName::importDefinedName( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + maModel.mnSheet = rAttribs.getInteger( XML_localSheetId, -1 ); + maModel.mnFuncGroupId = rAttribs.getInteger( XML_functionGroupId, -1 ); + maModel.mbMacro = rAttribs.getBool( XML_xlm, false ); + maModel.mbFunction = rAttribs.getBool( XML_function, false ); + maModel.mbVBName = rAttribs.getBool( XML_vbProcedure, false ); + maModel.mbHidden = rAttribs.getBool( XML_hidden, false ); + mcBuiltinId = lclGetBuiltinIdFromOox( maModel.maName ); + mnCalcSheet = (maModel.mnSheet >= 0) ? getWorksheets().getCalcSheetIndex( maModel.mnSheet ) : -1; +} + +void DefinedName::setFormula( const OUString& rFormula ) +{ + maModel.maFormula = rFormula; +} + +void DefinedName::importDefinedName( RecordInputStream& rStrm ) +{ + sal_uInt32 nFlags; + rStrm >> nFlags; + rStrm.skip( 1 ); // keyboard shortcut + rStrm >> maModel.mnSheet >> maModel.maName; + mnCalcSheet = (maModel.mnSheet >= 0) ? getWorksheets().getCalcSheetIndex( maModel.mnSheet ) : -1; + + // macro function/command, hidden flag + maModel.mnFuncGroupId = extractValue< sal_Int32 >( nFlags, 6, 9 ); + maModel.mbMacro = getFlag( nFlags, OOBIN_DEFNAME_MACRO ); + maModel.mbFunction = getFlag( nFlags, OOBIN_DEFNAME_FUNC ); + maModel.mbVBName = getFlag( nFlags, OOBIN_DEFNAME_VBNAME ); + maModel.mbHidden = getFlag( nFlags, OOBIN_DEFNAME_HIDDEN ); + + // get builtin name index from name + if( getFlag( nFlags, OOBIN_DEFNAME_BUILTIN ) ) + mcBuiltinId = lclGetBuiltinIdFromOob( maModel.maName ); + // unhide built-in names (_xlnm._FilterDatabase is always hidden) + if( isBuiltinName() ) + maModel.mbHidden = false; + + // store token array data + sal_Int64 nRecPos = rStrm.tell(); + sal_Int32 nFmlaSize = rStrm.readInt32(); + rStrm.skip( nFmlaSize ); + sal_Int32 nAddDataSize = rStrm.readInt32(); + if( !rStrm.isEof() && (nFmlaSize > 0) && (nAddDataSize >= 0) && (rStrm.getRemaining() >= nAddDataSize) ) + { + sal_Int32 nTotalSize = 8 + nFmlaSize + nAddDataSize; + mxFormula.reset( new StreamDataSequence ); + rStrm.seek( nRecPos ); + rStrm.readData( *mxFormula, nTotalSize ); + } +} + +void DefinedName::importDefinedName( BiffInputStream& rStrm, sal_Int16 nCalcSheet ) +{ + BiffType eBiff = getBiff(); + sal_uInt16 nFlags = 0; + sal_Int16 nRefId = BIFF_DEFNAME_GLOBAL; + sal_Int16 nTabId = BIFF_DEFNAME_GLOBAL; + sal_uInt8 nNameLen = 0, nShortCut = 0; + + switch( eBiff ) + { + case BIFF2: + { + sal_uInt8 nFlagsBiff2; + rStrm >> nFlagsBiff2; + rStrm.skip( 1 ); + rStrm >> nShortCut >> nNameLen; + mnFmlaSize = rStrm.readuInt8(); + setFlag( nFlags, BIFF_DEFNAME_FUNC, getFlag( nFlagsBiff2, BIFF2_DEFNAME_FUNC ) ); + maModel.maName = rStrm.readCharArrayUC( nNameLen, getTextEncoding(), true ); + } + break; + case BIFF3: + case BIFF4: + rStrm >> nFlags >> nShortCut >> nNameLen >> mnFmlaSize; + maModel.maName = rStrm.readCharArrayUC( nNameLen, getTextEncoding(), true ); + break; + case BIFF5: + rStrm >> nFlags >> nShortCut >> nNameLen >> mnFmlaSize >> nRefId >> nTabId; + rStrm.skip( 4 ); + maModel.maName = rStrm.readCharArrayUC( nNameLen, getTextEncoding(), true ); + break; + case BIFF8: + rStrm >> nFlags >> nShortCut >> nNameLen >> mnFmlaSize >> nRefId >> nTabId; + rStrm.skip( 4 ); + maModel.maName = rStrm.readUniStringBody( nNameLen, true ); + break; + case BIFF_UNKNOWN: break; + } + + // macro function/command, hidden flag + maModel.mnFuncGroupId = extractValue< sal_Int32 >( nFlags, 6, 6 ); + maModel.mbMacro = getFlag( nFlags, BIFF_DEFNAME_MACRO ); + maModel.mbFunction = getFlag( nFlags, BIFF_DEFNAME_FUNC ); + maModel.mbVBName = getFlag( nFlags, BIFF_DEFNAME_VBNAME ); + maModel.mbHidden = getFlag( nFlags, BIFF_DEFNAME_HIDDEN ); + + // get builtin name index from name + if( getFlag( nFlags, BIFF_DEFNAME_BUILTIN ) ) + { + OSL_ENSURE( maModel.maName.getLength() == 1, "DefinedName::importDefinedName - wrong builtin name" ); + if( maModel.maName.getLength() > 0 ) + mcBuiltinId = maModel.maName[ 0 ]; + } + /* In BIFF5, _xlnm._FilterDatabase appears as hidden user name without + built-in flag, and even worse, localized. */ + else if( (eBiff == BIFF5) && lclIsFilterDatabaseName( maModel.maName ) ) + { + mcBuiltinId = OOX_DEFNAME_FILTERDATABASE; + } + + // unhide built-in names (_xlnm._FilterDatabase is always hidden) + if( isBuiltinName() ) + maModel.mbHidden = false; + + // get sheet index for sheet-local names in BIFF5-BIFF8 + switch( getBiff() ) + { + case BIFF2: + case BIFF3: + case BIFF4: + // BIFF2-BIFF4: all defined names are sheet-local + mnCalcSheet = nCalcSheet; + break; + case BIFF5: + // #i44019# nTabId may be invalid, resolve nRefId to sheet index + if( nRefId != BIFF_DEFNAME_GLOBAL ) + if( const ExternalLink* pExtLink = getExternalLinks().getExternalLink( nRefId ).get() ) + if( pExtLink->getLinkType() == LINKTYPE_INTERNAL ) + mnCalcSheet = pExtLink->getCalcSheetIndex(); + break; + case BIFF8: + // convert one-based worksheet index to zero-based Calc sheet index + OSL_ENSURE( nTabId >= 0, "DefinedName::importDefinedName - invalid local sheet index" ); + if( nTabId != BIFF_DEFNAME_GLOBAL ) + mnCalcSheet = getWorksheets().getCalcSheetIndex( nTabId - 1 ); + break; + case BIFF_UNKNOWN: + break; + } + + if( (getBiff() <= BIFF4) && maModel.mbHidden && (maModel.maName.getLength() > 1) && (maModel.maName[ 0 ] == '\x01') ) + { + /* Read the token array of special internal names containing addresses + for BIFF3-BIFF4 3D references immediately. It is expected that + these names contain a simple cell reference or range reference. + Other regular defined names and external names rely on existence of + this reference. */ + TokensFormulaContext aContext( true, true ); + importBiffFormula( aContext, mnCalcSheet, rStrm, &mnFmlaSize ); + extractReference( aContext.getTokens() ); + } + else + { + /* Store record position of other defined names to be able to import + token array later. This is needed to correctly resolve references + to names that are stored later in the defined names list following + this name. */ + mxBiffStrm.reset( new BiffInputStreamPos( rStrm ) ); + } +} + +void DefinedName::createNameObject() +{ + // do not create names for (macro) functions + // #163146# do not ignore hidden names (may be regular names created by VBA scripts) + if( /*maModel.mbHidden ||*/ maModel.mbFunction ) + return; + + // convert original name to final Calc name + if( maModel.mbVBName ) + maCalcName = maModel.maName; + else if( isBuiltinName() ) + maCalcName = lclGetBuiltinName( mcBuiltinId ); + else + maCalcName = maModel.maName; //! TODO convert to valid name + + // #163146# do not rename sheet-local names by default, this breaks VBA scripts +#if 0 + // append sheet index for local names in multi-sheet documents + if( isWorkbookFile() && !isGlobalName() ) + maCalcName = OUStringBuffer( maCalcName ).append( sal_Unicode( '_' ) ). + append( static_cast< sal_Int32 >( mnCalcSheet + 1 ) ).makeStringAndClear(); +#endif + + // special flags for this name + sal_Int32 nNameFlags = 0; + using namespace ::com::sun::star::sheet::NamedRangeFlag; + if( !isGlobalName() ) switch( mcBuiltinId ) + { + case OOX_DEFNAME_CRITERIA: nNameFlags = FILTER_CRITERIA; break; + case OOX_DEFNAME_PRINTAREA: nNameFlags = PRINT_AREA; break; + case OOX_DEFNAME_PRINTTITLES: nNameFlags = COLUMN_HEADER | ROW_HEADER; break; + } + + // create the name and insert it into the document, maCalcName will be changed to the resulting name + mxNamedRange = createNamedRangeObject( maCalcName, nNameFlags ); + // index of this defined name used in formula token arrays + PropertySet aPropSet( mxNamedRange ); + aPropSet.getProperty( mnTokenIndex, PROP_TokenIndex ); +} + +void DefinedName::convertFormula() +{ + Reference< XFormulaTokens > xTokens( mxNamedRange, UNO_QUERY ); + if( xTokens.is() ) + { + // convert and set formula of the defined name + switch( getFilterType() ) + { + case FILTER_OOX: + { + SimpleFormulaContext aContext( xTokens, true, false ); + implImportOoxFormula( aContext ); + } + break; + case FILTER_BIFF: + { + SimpleFormulaContext aContext( xTokens, true, getBiff() <= BIFF4 ); + implImportBiffFormula( aContext ); + } + break; + case FILTER_UNKNOWN: break; + } + + // set builtin names (print ranges, repeated titles, filter ranges) + if( !isGlobalName() ) switch( mcBuiltinId ) + { + case OOX_DEFNAME_PRINTAREA: + { + Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY ); + ApiCellRangeList aPrintRanges; + getFormulaParser().extractCellRangeList( aPrintRanges, xTokens->getTokens(), false, mnCalcSheet ); + if( xPrintAreas.is() && !aPrintRanges.empty() ) + xPrintAreas->setPrintAreas( ContainerHelper::vectorToSequence( aPrintRanges ) ); + } + break; + case OOX_DEFNAME_PRINTTITLES: + { + Reference< XPrintAreas > xPrintAreas( getSheetFromDoc( mnCalcSheet ), UNO_QUERY ); + ApiCellRangeList aTitleRanges; + getFormulaParser().extractCellRangeList( aTitleRanges, xTokens->getTokens(), false, mnCalcSheet ); + if( xPrintAreas.is() && !aTitleRanges.empty() ) + { + bool bHasRowTitles = false; + bool bHasColTitles = false; + const CellAddress& rMaxPos = getAddressConverter().getMaxAddress(); + for( ApiCellRangeList::const_iterator aIt = aTitleRanges.begin(), aEnd = aTitleRanges.end(); (aIt != aEnd) && (!bHasRowTitles || !bHasColTitles); ++aIt ) + { + bool bFullRow = (aIt->StartColumn == 0) && (aIt->EndColumn >= rMaxPos.Column); + bool bFullCol = (aIt->StartRow == 0) && (aIt->EndRow >= rMaxPos.Row); + if( !bHasRowTitles && bFullRow && !bFullCol ) + { + xPrintAreas->setTitleRows( *aIt ); + xPrintAreas->setPrintTitleRows( sal_True ); + bHasRowTitles = true; + } + else if( !bHasColTitles && bFullCol && !bFullRow ) + { + xPrintAreas->setTitleColumns( *aIt ); + xPrintAreas->setPrintTitleColumns( sal_True ); + bHasColTitles = true; + } + } + } + } + break; + } + } +} + +bool DefinedName::getAbsoluteRange( CellRangeAddress& orRange ) const +{ + /* ScNamedRangeObj::XCellRangeReferrer::getReferredCells is buggy with + relative references, so we extract an absolute reference by hand. */ + Reference< XFormulaTokens > xTokens( mxNamedRange, UNO_QUERY ); + return xTokens.is() && getFormulaParser().extractCellRange( orRange, xTokens->getTokens(), false ); +} + +void DefinedName::implImportOoxFormula( FormulaContext& rContext ) +{ + if( mxFormula.get() ) + { + RecordInputStream aStrm( *mxFormula ); + importOobFormula( rContext, mnCalcSheet, aStrm ); + } + else + importOoxFormula( rContext, mnCalcSheet ); +} + +void DefinedName::implImportBiffFormula( FormulaContext& rContext ) +{ + OSL_ENSURE( mxBiffStrm.get(), "DefinedName::implImportBiffFormula - missing BIFF stream" ); + BiffInputStream& rStrm = mxBiffStrm->getStream(); + BiffInputStreamPosGuard aStrmGuard( rStrm ); + if( mxBiffStrm->restorePosition() ) + importBiffFormula( rContext, mnCalcSheet, rStrm, &mnFmlaSize ); +} + +// ============================================================================ + +DefinedNamesBuffer::DefinedNamesBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mnCalcSheet( -1 ) +{ +} + +void DefinedNamesBuffer::setLocalCalcSheet( sal_Int16 nCalcSheet ) +{ + OSL_ENSURE( (getFilterType() == FILTER_BIFF) && (getBiff() <= BIFF4), + "DefinedNamesBuffer::setLocalCalcSheet - invalid call" ); + mnCalcSheet = nCalcSheet; +} + +DefinedNameRef DefinedNamesBuffer::importDefinedName( const AttributeList& rAttribs ) +{ + DefinedNameRef xDefName = createDefinedName(); + xDefName->importDefinedName( rAttribs ); + return xDefName; +} + +void DefinedNamesBuffer::importDefinedName( RecordInputStream& rStrm ) +{ + createDefinedName()->importDefinedName( rStrm ); +} + +void DefinedNamesBuffer::importDefinedName( BiffInputStream& rStrm ) +{ + createDefinedName()->importDefinedName( rStrm, mnCalcSheet ); +} + +void DefinedNamesBuffer::finalizeImport() +{ + // first insert all names without formula definition into the document + for( DefNameVector::iterator aIt = maDefNames.begin(), aEnd = maDefNames.end(); aIt != aEnd; ++aIt ) + { + DefinedNameRef xDefName = *aIt; + xDefName->createNameObject(); + sal_Int32 nTokenIndex = xDefName->getTokenIndex(); + if( nTokenIndex >= 0 ) + maDefNameMap[ nTokenIndex ] = xDefName; + } + + /* Now convert all name formulas, so that the formula parser can find all + names in case of circular dependencies. */ + maDefNames.forEachMem( &DefinedName::convertFormula ); +} + +DefinedNameRef DefinedNamesBuffer::getByIndex( sal_Int32 nIndex ) const +{ + return maDefNames.get( nIndex ); +} + +DefinedNameRef DefinedNamesBuffer::getByTokenIndex( sal_Int32 nIndex ) const +{ + return maDefNameMap.get( nIndex ); +} + +DefinedNameRef DefinedNamesBuffer::getByModelName( const OUString& rModelName, sal_Int16 nCalcSheet ) const +{ + DefinedNameRef xGlobalName; // a found global name + DefinedNameRef xLocalName; // a found local name + for( DefNameVector::const_iterator aIt = maDefNames.begin(), aEnd = maDefNames.end(); (aIt != aEnd) && !xLocalName; ++aIt ) + { + DefinedNameRef xCurrName = *aIt; + if( xCurrName->getModelName() == rModelName ) + { + if( xCurrName->getLocalCalcSheet() == nCalcSheet ) + xLocalName = xCurrName; + else if( xCurrName->isGlobalName() ) + xGlobalName = xCurrName; + } + } + return xLocalName.get() ? xLocalName : xGlobalName; +} + +DefinedNameRef DefinedNamesBuffer::createDefinedName() +{ + DefinedNameRef xDefName( new DefinedName( *this ) ); + maDefNames.push_back( xDefName ); + return xDefName; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/drawingfragment.cxx b/oox/source/xls/drawingfragment.cxx new file mode 100644 index 000000000000..172530692923 --- /dev/null +++ b/oox/source/xls/drawingfragment.cxx @@ -0,0 +1,680 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/drawingfragment.hxx" +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/form/binding/XListEntrySource.hpp> +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/drawingml/connectorshapecontext.hxx" +#include "oox/drawingml/graphicshapecontext.hxx" +#include "oox/drawingml/shapecontext.hxx" +#include "oox/drawingml/shapegroupcontext.hxx" +#include "oox/vml/vmlshape.hxx" +#include "oox/vml/vmlshapecontainer.hxx" +#include "oox/xls/formulaparser.hxx" +#include "oox/xls/stylesbuffer.hxx" +#include "oox/xls/themebuffer.hxx" +#include "oox/xls/unitconverter.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +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::beans::NamedValue; +using ::com::sun::star::awt::Point; +using ::com::sun::star::awt::Rectangle; +using ::com::sun::star::awt::Size; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::form::binding::XBindableValue; +using ::com::sun::star::form::binding::XListEntrySink; +using ::com::sun::star::form::binding::XListEntrySource; +using ::com::sun::star::form::binding::XValueBinding; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::oox::core::ContextHandlerRef; +using ::oox::drawingml::ConnectorShapeContext; +using ::oox::drawingml::GraphicalObjectFrameContext; +using ::oox::drawingml::GraphicShapeContext; +using ::oox::drawingml::Shape; +using ::oox::drawingml::ShapePtr; +using ::oox::drawingml::ShapeContext; +using ::oox::drawingml::ShapeGroupContext; +// no using's for ::oox::vml, that may clash with ::oox::drawingml types + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +/** Converts the passed 64-bit integer value from the passed unit to EMUs. */ +sal_Int64 lclCalcEmu( const UnitConverter& rUnitConv, sal_Int64 nValue, Unit eFromUnit ) +{ + return (eFromUnit == UNIT_EMU) ? nValue : + static_cast< sal_Int64 >( rUnitConv.scaleValue( static_cast< double >( nValue ), eFromUnit, UNIT_EMU ) + 0.5 ); +} + +} // namespace + +// ============================================================================ + +AnchorCellModel::AnchorCellModel() : + mnCol( -1 ), + mnRow( -1 ), + mnColOffset( 0 ), + mnRowOffset( 0 ) +{ +} + +// ---------------------------------------------------------------------------- + +AnchorClientDataModel::AnchorClientDataModel() : + mbLocksWithSheet( true ), + mbPrintsWithSheet( true ) +{ +} + +// ============================================================================ + +ShapeAnchor::ShapeAnchor( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ), + meType( ANCHOR_INVALID ), + mnEditAs( XML_twoCell ) +{ +} + +void ShapeAnchor::importAnchor( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( nElement ) + { + case XDR_TOKEN( absoluteAnchor ): + meType = ANCHOR_ABSOLUTE; + break; + case XDR_TOKEN( oneCellAnchor ): + meType = ANCHOR_ONECELL; + break; + case XDR_TOKEN( twoCellAnchor ): + meType = ANCHOR_TWOCELL; + mnEditAs = rAttribs.getToken( XML_editAs, XML_twoCell ); + break; + default: + OSL_ENSURE( false, "ShapeAnchor::importAnchor - unexpected element" ); + } +} + +void ShapeAnchor::importPos( const AttributeList& rAttribs ) +{ + OSL_ENSURE( meType == ANCHOR_ABSOLUTE, "ShapeAnchor::importPos - unexpected 'xdr:pos' element" ); + maPos.X = rAttribs.getHyper( XML_x, 0 ); + maPos.Y = rAttribs.getHyper( XML_y, 0 ); +} + +void ShapeAnchor::importExt( const AttributeList& rAttribs ) +{ + OSL_ENSURE( (meType == ANCHOR_ABSOLUTE) || (meType == ANCHOR_ONECELL), "ShapeAnchor::importExt - unexpected 'xdr:ext' element" ); + maSize.Width = rAttribs.getHyper( XML_cx, 0 ); + maSize.Height = rAttribs.getHyper( XML_cy, 0 ); +} + +void ShapeAnchor::importClientData( const AttributeList& rAttribs ) +{ + maClientData.mbLocksWithSheet = rAttribs.getBool( XML_fLocksWithSheet, true ); + maClientData.mbPrintsWithSheet = rAttribs.getBool( XML_fPrintsWithSheet, true ); +} + +void ShapeAnchor::setCellPos( sal_Int32 nElement, sal_Int32 nParentContext, const OUString& rValue ) +{ + AnchorCellModel* pAnchorCell = 0; + switch( nParentContext ) + { + case XDR_TOKEN( from ): + OSL_ENSURE( (meType == ANCHOR_ONECELL) || (meType == ANCHOR_TWOCELL), "ShapeAnchor::setCellPos - unexpected 'xdr:from' element" ); + pAnchorCell = &maFrom; + break; + case XDR_TOKEN( to ): + OSL_ENSURE( meType == ANCHOR_TWOCELL, "ShapeAnchor::setCellPos - unexpected 'xdr:to' element" ); + pAnchorCell = &maTo; + break; + default: + OSL_ENSURE( false, "ShapeAnchor::setCellPos - unexpected parent element" ); + } + if( pAnchorCell ) switch( nElement ) + { + case XDR_TOKEN( col ): pAnchorCell->mnCol = rValue.toInt32(); break; + case XDR_TOKEN( row ): pAnchorCell->mnRow = rValue.toInt32(); break; + case XDR_TOKEN( colOff ): pAnchorCell->mnColOffset = rValue.toInt64(); break; + case XDR_TOKEN( rowOff ): pAnchorCell->mnRowOffset = rValue.toInt64(); break; + default: OSL_ENSURE( false, "ShapeAnchor::setCellPos - unexpected element" ); + } +} + +void ShapeAnchor::importVmlAnchor( const OUString& rAnchor ) +{ + meType = ANCHOR_VML; + + ::std::vector< OUString > aTokens; + sal_Int32 nIndex = 0; + while( nIndex >= 0 ) + aTokens.push_back( rAnchor.getToken( 0, ',', nIndex ).trim() ); + + OSL_ENSURE( aTokens.size() >= 8, "ShapeAnchor::importVmlAnchor - missing anchor tokens" ); + if( aTokens.size() >= 8 ) + { + maFrom.mnCol = aTokens[ 0 ].toInt32(); + maFrom.mnColOffset = aTokens[ 1 ].toInt32(); + maFrom.mnRow = aTokens[ 2 ].toInt32(); + maFrom.mnRowOffset = aTokens[ 3 ].toInt32(); + maTo.mnCol = aTokens[ 4 ].toInt32(); + maTo.mnColOffset = aTokens[ 5 ].toInt32(); + maTo.mnRow = aTokens[ 6 ].toInt32(); + maTo.mnRowOffset = aTokens[ 7 ].toInt32(); + } +} + +bool ShapeAnchor::isValidAnchor() const +{ + bool bValid = false; + switch( meType ) + { + case ANCHOR_ABSOLUTE: + OSL_ENSURE( maPos.isValid(), "ShapeAnchor::isValidAnchor - invalid position" ); + OSL_ENSURE( maSize.isValid(), "ShapeAnchor::isValidAnchor - invalid size" ); + bValid = maPos.isValid() && maSize.isValid() && (maSize.Width > 0) && (maSize.Height > 0); + break; + case ANCHOR_ONECELL: + OSL_ENSURE( maFrom.isValid(), "ShapeAnchor::isValidAnchor - invalid from position" ); + OSL_ENSURE( maSize.isValid(), "ShapeAnchor::isValidAnchor - invalid size" ); + bValid = maFrom.isValid() && maSize.isValid() && (maSize.Width > 0) && (maSize.Height > 0); + break; + case ANCHOR_TWOCELL: + case ANCHOR_VML: + OSL_ENSURE( maFrom.isValid(), "ShapeAnchor::isValidAnchor - invalid from position" ); + OSL_ENSURE( maTo.isValid(), "ShapeAnchor::isValidAnchor - invalid to position" ); + bValid = maFrom.isValid() && maTo.isValid() && + ((maFrom.mnCol < maTo.mnCol) || ((maFrom.mnCol == maTo.mnCol) && (maFrom.mnColOffset < maTo.mnColOffset))) && + ((maFrom.mnRow < maTo.mnRow) || ((maFrom.mnRow == maTo.mnRow) && (maFrom.mnRowOffset < maTo.mnRowOffset))); + break; + case ANCHOR_INVALID: + OSL_ENSURE( false, "ShapeAnchor::isValidAnchor - invalid anchor" ); + break; + } + return bValid; +} + +Rectangle ShapeAnchor::calcApiLocation( const Size& rApiSheetSize, const AnchorSizeModel& rEmuSheetSize ) const +{ + AddressConverter& rAddrConv = getAddressConverter(); + UnitConverter& rUnitConv = getUnitConverter(); + Rectangle aApiLoc( -1, -1, -1, -1 ); + Unit eUnitX = (meType == ANCHOR_VML) ? UNIT_SCREENX : UNIT_EMU; + Unit eUnitY = (meType == ANCHOR_VML) ? UNIT_SCREENY : UNIT_EMU; + + // calculate shape position + switch( meType ) + { + case ANCHOR_ABSOLUTE: + OSL_ENSURE( maPos.isValid(), "ShapeAnchor::calcApiLocation - invalid position" ); + if( maPos.isValid() && (maPos.X < rEmuSheetSize.Width) && (maPos.Y < rEmuSheetSize.Height) ) + { + aApiLoc.X = rUnitConv.scaleToMm100( static_cast< double >( maPos.X ), UNIT_EMU ); + aApiLoc.Y = rUnitConv.scaleToMm100( static_cast< double >( maPos.Y ), UNIT_EMU ); + } + break; + case ANCHOR_ONECELL: + case ANCHOR_TWOCELL: + case ANCHOR_VML: + OSL_ENSURE( maFrom.isValid(), "ShapeAnchor::calcApiLocation - invalid position" ); + if( maFrom.isValid() && rAddrConv.checkCol( maFrom.mnCol, true ) && rAddrConv.checkRow( maFrom.mnRow, true ) ) + { + Point aPoint = getCellPosition( maFrom.mnCol, maFrom.mnRow ); + aApiLoc.X = aPoint.X + rUnitConv.scaleToMm100( static_cast< double >( maFrom.mnColOffset ), eUnitX ); + aApiLoc.Y = aPoint.Y + rUnitConv.scaleToMm100( static_cast< double >( maFrom.mnRowOffset ), eUnitY ); + } + break; + case ANCHOR_INVALID: + OSL_ENSURE( false, "ShapeAnchor::calcApiLocation - invalid anchor" ); + break; + } + + // calculate shape size + if( (aApiLoc.X >= 0) && (aApiLoc.Y >= 0) ) switch( meType ) + { + case ANCHOR_ABSOLUTE: + case ANCHOR_ONECELL: + OSL_ENSURE( maSize.isValid(), "ShapeAnchor::calcApiLocation - invalid size" ); + if( maSize.isValid() ) + { + aApiLoc.Width = ::std::min< sal_Int32 >( + rUnitConv.scaleToMm100( static_cast< double >( maSize.Width ), UNIT_EMU ), + rApiSheetSize.Width - aApiLoc.X ); + aApiLoc.Height = ::std::min< sal_Int32 >( + rUnitConv.scaleToMm100( static_cast< double >( maSize.Height ), UNIT_EMU ), + rApiSheetSize.Height - aApiLoc.Y ); + } + break; + case ANCHOR_TWOCELL: + case ANCHOR_VML: + OSL_ENSURE( maTo.isValid(), "ShapeAnchor::calcApiLocation - invalid position" ); + if( maTo.isValid() ) + { + /* Pass a valid cell address to getCellPosition(), otherwise + nothing is returned, even if either row or column is valid. */ + CellAddress aToCell = rAddrConv.createValidCellAddress( BinAddress( maTo.mnCol, maTo.mnRow ), getSheetIndex(), true ); + Point aPoint = getCellPosition( aToCell.Column, aToCell.Row ); + // width + aApiLoc.Width = rApiSheetSize.Width - aApiLoc.X; + if( aToCell.Column == maTo.mnCol ) + { + aPoint.X += rUnitConv.scaleToMm100( static_cast< double >( maTo.mnColOffset ), eUnitX ); + aApiLoc.Width = ::std::min< sal_Int32 >( aPoint.X - aApiLoc.X + 1, aApiLoc.Width ); + } + // height + aApiLoc.Height = rApiSheetSize.Height - aApiLoc.Y; + if( aToCell.Row == maTo.mnRow ) + { + aPoint.Y += rUnitConv.scaleToMm100( static_cast< double >( maTo.mnRowOffset ), eUnitY ); + aApiLoc.Height = ::std::min< sal_Int32 >( aPoint.Y - aApiLoc.Y + 1, aApiLoc.Height ); + } + } + break; + case ANCHOR_INVALID: + break; + } + + return aApiLoc; +} + +Rectangle ShapeAnchor::calcEmuLocation( const AnchorSizeModel& rEmuSheetSize ) const +{ + AddressConverter& rAddrConv = getAddressConverter(); + UnitConverter& rUnitConv = getUnitConverter(); + + Size aSheetSize( + getLimitedValue< sal_Int32, sal_Int64 >( rEmuSheetSize.Width, 0, SAL_MAX_INT32 ), + getLimitedValue< sal_Int32, sal_Int64 >( rEmuSheetSize.Height, 0, SAL_MAX_INT32 ) ); + Rectangle aLoc( -1, -1, -1, -1 ); + Unit eUnitX = (meType == ANCHOR_VML) ? UNIT_SCREENX : UNIT_EMU; + Unit eUnitY = (meType == ANCHOR_VML) ? UNIT_SCREENY : UNIT_EMU; + + // calculate shape position + switch( meType ) + { + case ANCHOR_ABSOLUTE: + OSL_ENSURE( maPos.isValid(), "ShapeAnchor::calcEmuLocation - invalid position" ); + if( maPos.isValid() && (maPos.X < aSheetSize.Width) && (maPos.Y < aSheetSize.Height) ) + { + aLoc.X = static_cast< sal_Int32 >( maPos.X ); + aLoc.Y = static_cast< sal_Int32 >( maPos.Y ); + } + break; + case ANCHOR_ONECELL: + case ANCHOR_TWOCELL: + case ANCHOR_VML: + OSL_ENSURE( maFrom.isValid(), "ShapeAnchor::calcEmuLocation - invalid position" ); + if( maFrom.isValid() && rAddrConv.checkCol( maFrom.mnCol, true ) && rAddrConv.checkRow( maFrom.mnRow, true ) ) + { + Point aPoint = getCellPosition( maFrom.mnCol, maFrom.mnRow ); + sal_Int64 nX = static_cast< sal_Int64 >( rUnitConv.scaleFromMm100( aPoint.X, UNIT_EMU ) ) + lclCalcEmu( rUnitConv, maFrom.mnColOffset, eUnitX ); + sal_Int64 nY = static_cast< sal_Int64 >( rUnitConv.scaleFromMm100( aPoint.Y, UNIT_EMU ) ) + lclCalcEmu( rUnitConv, maFrom.mnRowOffset, eUnitY ); + if( (nX < aSheetSize.Width) && (nY < aSheetSize.Height) ) + { + aLoc.X = static_cast< sal_Int32 >( nX ); + aLoc.Y = static_cast< sal_Int32 >( nY ); + } + } + break; + case ANCHOR_INVALID: + OSL_ENSURE( false, "ShapeAnchor::calcEmuLocation - invalid anchor" ); + break; + } + + // calculate shape size + if( (aLoc.X >= 0) && (aLoc.Y >= 0) ) switch( meType ) + { + case ANCHOR_ABSOLUTE: + case ANCHOR_ONECELL: + OSL_ENSURE( maSize.isValid(), "ShapeAnchor::calcEmuLocation - invalid size" ); + if( maSize.isValid() ) + { + aLoc.Width = static_cast< sal_Int32 >( ::std::min< sal_Int64 >( maSize.Width, aSheetSize.Width - aLoc.X ) ); + aLoc.Height = static_cast< sal_Int32 >( ::std::min< sal_Int64 >( maSize.Height, aSheetSize.Height - aLoc.Y ) ); + } + break; + case ANCHOR_TWOCELL: + case ANCHOR_VML: + OSL_ENSURE( maTo.isValid(), "ShapeAnchor::calcEmuLocation - invalid position" ); + if( maTo.isValid() ) + { + /* Pass a valid cell address to getCellPosition(), otherwise + nothing is returned, even if either row or column is valid. */ + CellAddress aToCell = rAddrConv.createValidCellAddress( BinAddress( maTo.mnCol, maTo.mnRow ), getSheetIndex(), true ); + Point aPoint = getCellPosition( aToCell.Column, aToCell.Row ); + sal_Int64 nX = static_cast< sal_Int64 >( rUnitConv.scaleFromMm100( aPoint.X, UNIT_EMU ) ); + sal_Int64 nY = static_cast< sal_Int64 >( rUnitConv.scaleFromMm100( aPoint.Y, UNIT_EMU ) ); + // width + aLoc.Width = aSheetSize.Width - aLoc.X; + if( aToCell.Column == maTo.mnCol ) + { + nX += lclCalcEmu( rUnitConv, maTo.mnColOffset, eUnitX ); + aLoc.Width = static_cast< sal_Int32 >( ::std::min< sal_Int64 >( nX - aLoc.X + 1, aLoc.Width ) ); + } + // height + aLoc.Height = aSheetSize.Height - aLoc.Y; + if( aToCell.Row == maTo.mnRow ) + { + nY += lclCalcEmu( rUnitConv, maTo.mnRowOffset, eUnitY ); + aLoc.Height = static_cast< sal_Int32 >( ::std::min< sal_Int64 >( nY - aLoc.Y + 1, aLoc.Height ) ); + } + } + break; + case ANCHOR_INVALID: + break; + } + + // add 0.75 mm (27,000 EMUs) in X direction to correct display error + if( aLoc.X >= 0 ) + aLoc.X += 27000; + // remove 0.25 mm (9,000 EMUs) in Y direction to correct display error + if( aLoc.Y >= 9000 ) + aLoc.Y -= 9000; + + return aLoc; +} + +// ============================================================================ + +OoxDrawingFragment::OoxDrawingFragment( const WorksheetHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath ), + mxDrawPage( rHelper.getDrawPage(), UNO_QUERY ) +{ + OSL_ENSURE( mxDrawPage.is(), "OoxDrawingFragment::OoxDrawingFragment - missing drawing page" ); + maApiSheetSize = getDrawPageSize(); + maEmuSheetSize.Width = static_cast< sal_Int64 >( getUnitConverter().scaleFromMm100( maApiSheetSize.Width, UNIT_EMU ) ); + maEmuSheetSize.Height = static_cast< sal_Int64 >( getUnitConverter().scaleFromMm100( maApiSheetSize.Height, UNIT_EMU ) ); +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxDrawingFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XDR_TOKEN( wsDr ) ) return this; + break; + + case XDR_TOKEN( wsDr ): + switch( nElement ) + { + case XDR_TOKEN( absoluteAnchor ): + case XDR_TOKEN( oneCellAnchor ): + case XDR_TOKEN( twoCellAnchor ): + mxAnchor.reset( new ShapeAnchor( *this ) ); + mxAnchor->importAnchor( nElement, rAttribs ); + return this; + } + break; + + case XDR_TOKEN( absoluteAnchor ): + case XDR_TOKEN( oneCellAnchor ): + case XDR_TOKEN( twoCellAnchor ): + switch( nElement ) + { + case XDR_TOKEN( sp ): + mxShape.reset( new Shape( "com.sun.star.drawing.CustomShape" ) ); + return new ShapeContext( *this, ShapePtr(), mxShape ); + case XDR_TOKEN( cxnSp ): + mxShape.reset( new Shape( "com.sun.star.drawing.ConnectorShape" ) ); + return new ConnectorShapeContext( *this, ShapePtr(), mxShape ); + case XDR_TOKEN( pic ): + mxShape.reset( new Shape( "com.sun.star.drawing.GraphicObjectShape" ) ); + return new GraphicShapeContext( *this, ShapePtr(), mxShape ); + case XDR_TOKEN( graphicFrame ): + mxShape.reset( new Shape( "com.sun.star.drawing.OLE2Shape" ) ); + return new GraphicalObjectFrameContext( *this, ShapePtr(), mxShape, getSheetType() != SHEETTYPE_CHARTSHEET ); + case XDR_TOKEN( grpSp ): + mxShape.reset( new Shape( "com.sun.star.drawing.GroupShape" ) ); + return new ShapeGroupContext( *this, ShapePtr(), mxShape ); + + case XDR_TOKEN( from ): + case XDR_TOKEN( to ): return this; + + case XDR_TOKEN( pos ): if( mxAnchor.get() ) mxAnchor->importPos( rAttribs ); break; + case XDR_TOKEN( ext ): if( mxAnchor.get() ) mxAnchor->importExt( rAttribs ); break; + case XDR_TOKEN( clientData ): if( mxAnchor.get() ) mxAnchor->importClientData( rAttribs ); break; + } + break; + + case XDR_TOKEN( from ): + case XDR_TOKEN( to ): + switch( nElement ) + { + case XDR_TOKEN( col ): + case XDR_TOKEN( row ): + case XDR_TOKEN( colOff ): + case XDR_TOKEN( rowOff ): return this; // collect index in onEndElement() + } + break; + } + return 0; +} + +void OoxDrawingFragment::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XDR_TOKEN( col ): + case XDR_TOKEN( row ): + case XDR_TOKEN( colOff ): + case XDR_TOKEN( rowOff ): + if( mxAnchor.get() ) mxAnchor->setCellPos( getCurrentElement(), getPreviousElement(), rChars ); + break; + case XDR_TOKEN( absoluteAnchor ): + case XDR_TOKEN( oneCellAnchor ): + case XDR_TOKEN( twoCellAnchor ): + if( mxDrawPage.is() && mxShape.get() && mxAnchor.get() && mxAnchor->isValidAnchor() ) + { + Rectangle aShapeRect = mxAnchor->calcEmuLocation( maEmuSheetSize ); + if( (aShapeRect.X >= 0) && (aShapeRect.Y >= 0) && (aShapeRect.Width >= 0) && (aShapeRect.Height >= 0) ) + { + mxShape->addShape( getOoxFilter(), &getTheme(), mxDrawPage, &aShapeRect ); + // collect all shape positions in the WorksheetHelper base class + extendShapeBoundingBox( aShapeRect ); + } + } + mxShape.reset(); + mxAnchor.reset(); + break; + } +} + +// ============================================================================ + +namespace { + +class VmlFindNoteFunc +{ +public: + explicit VmlFindNoteFunc( const CellAddress& rPos ); + bool operator()( const ::oox::vml::ShapeBase& rShape ) const; + +private: + sal_Int32 mnCol; + sal_Int32 mnRow; +}; + +VmlFindNoteFunc::VmlFindNoteFunc( const CellAddress& rPos ) : + mnCol( rPos.Column ), + mnRow( rPos.Row ) +{ +} + +bool VmlFindNoteFunc::operator()( const ::oox::vml::ShapeBase& rShape ) const +{ + const ::oox::vml::ShapeModel::ShapeClientDataPtr& rxClientData = rShape.getShapeModel().mxClientData; + return rxClientData.get() && (rxClientData->mnCol == mnCol) && (rxClientData->mnRow == mnRow); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +VmlDrawing::VmlDrawing( const WorksheetHelper& rHelper ) : + ::oox::vml::Drawing( rHelper.getOoxFilter(), rHelper.getDrawPage(), ::oox::vml::VMLDRAWING_EXCEL ), + WorksheetHelper( rHelper ) +{ +} + +const ::oox::vml::ShapeBase* VmlDrawing::getNoteShape( const CellAddress& rPos ) const +{ + return getShapes().findShape( VmlFindNoteFunc( rPos ) ); +} + +bool VmlDrawing::isShapeSupported( const ::oox::vml::ShapeBase& rShape ) const +{ + const ::oox::vml::ShapeModel::ShapeClientDataPtr& rxClientData = rShape.getShapeModel().mxClientData; + return !rxClientData.get() || (rxClientData->mnObjType != XML_Note); +} + +bool VmlDrawing::convertShapeClientAnchor( Rectangle& orShapeRect, const OUString& rShapeAnchor ) const +{ + if( rShapeAnchor.getLength() == 0 ) + return false; + ShapeAnchor aAnchor( *this ); + aAnchor.importVmlAnchor( rShapeAnchor ); + orShapeRect = aAnchor.calcApiLocation( getDrawPageSize(), AnchorSizeModel() ); + return (orShapeRect.Width >= 0) && (orShapeRect.Height >= 0); +} + +void VmlDrawing::convertControlClientData( const Reference< XControlModel >& rxCtrlModel, + const ::oox::vml::ShapeClientData& rClientData ) const +{ + if( rxCtrlModel.is() ) + { + PropertySet aPropSet( rxCtrlModel ); + + // printable + aPropSet.setProperty( PROP_Printable, rClientData.mbPrintObject ); + + // linked cell + if( rClientData.maLinkedCell.getLength() > 0 ) try + { + Reference< XBindableValue > xBindable( rxCtrlModel, UNO_QUERY_THROW ); + + // convert formula string to cell address + FormulaParser& rParser = getFormulaParser(); + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( CellAddress( getSheetIndex(), 0, 0 ) ); + rParser.importFormula( aContext, rClientData.maLinkedCell ); + CellAddress aAddress; + if( rParser.extractCellAddress( aAddress, aContext.getTokens(), true ) ) + { + // create argument sequence for createInstanceWithArguments() + NamedValue aValue; + aValue.Name = CREATE_OUSTRING( "BoundCell" ); + aValue.Value <<= aAddress; + Sequence< Any > aArgs( 1 ); + aArgs[ 0 ] <<= aValue; + + // create the CellValueBinding instance and set at the control model + Reference< XValueBinding > xBinding( getDocumentFactory()->createInstanceWithArguments( + CREATE_OUSTRING( "com.sun.star.table.CellValueBinding" ), aArgs ), UNO_QUERY_THROW ); + xBindable->setValueBinding( xBinding ); + } + } + catch( Exception& ) + { + } + + // source range + if( rClientData.maSourceRange.getLength() > 0 ) try + { + Reference< XListEntrySink > xEntrySink( rxCtrlModel, UNO_QUERY_THROW ); + + // convert formula string to cell range + FormulaParser& rParser = getFormulaParser(); + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( CellAddress( getSheetIndex(), 0, 0 ) ); + rParser.importFormula( aContext, rClientData.maSourceRange ); + CellRangeAddress aRange; + if( rParser.extractCellRange( aRange, aContext.getTokens(), true ) ) + { + // create argument sequence for createInstanceWithArguments() + NamedValue aValue; + aValue.Name = CREATE_OUSTRING( "CellRange" ); + aValue.Value <<= aRange; + Sequence< Any > aArgs( 1 ); + aArgs[ 0 ] <<= aValue; + + // create the EntrySource instance and set at the control model + Reference< XListEntrySource > xEntrySource( getDocumentFactory()->createInstanceWithArguments( + CREATE_OUSTRING( "com.sun.star.table.CellRangeListSource" ), aArgs ), UNO_QUERY_THROW ); + xEntrySink->setListEntrySource( xEntrySource ); + } + } + catch( Exception& ) + { + } + } +} + +void VmlDrawing::notifyShapeInserted( const Reference< XShape >& /*rxShape*/, const Rectangle& rShapeRect ) +{ + // collect all shape positions in the WorksheetHelper base class + extendShapeBoundingBox( rShapeRect ); +} + +// ============================================================================ + +OoxVmlDrawingFragment::OoxVmlDrawingFragment( const WorksheetHelper& rHelper, const OUString& rFragmentPath ) : + ::oox::vml::DrawingFragment( rHelper.getOoxFilter(), rFragmentPath, rHelper.getVmlDrawing() ), + WorksheetHelper( rHelper ) +{ +} + +void OoxVmlDrawingFragment::finalizeImport() +{ + ::oox::vml::DrawingFragment::finalizeImport(); + getVmlDrawing().convertAndInsert(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/excelchartconverter.cxx b/oox/source/xls/excelchartconverter.cxx new file mode 100644 index 000000000000..bde3dd65d723 --- /dev/null +++ b/oox/source/xls/excelchartconverter.cxx @@ -0,0 +1,125 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/excelchartconverter.hxx" +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/chart2/data/XDataProvider.hpp> +#include <com/sun/star/chart2/data/XDataReceiver.hpp> +#include "oox/drawingml/chart/datasourcemodel.hxx" +#include "oox/xls/formulaparser.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::chart2::XChartDocument; +using ::com::sun::star::chart2::data::XDataProvider; +using ::com::sun::star::chart2::data::XDataReceiver; +using ::com::sun::star::chart2::data::XDataSequence; +using ::oox::drawingml::chart::DataSequenceModel; + +namespace oox { +namespace xls { + +// ============================================================================ + +ExcelChartConverter::ExcelChartConverter( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +ExcelChartConverter::~ExcelChartConverter() +{ +} + +void ExcelChartConverter::createDataProvider( const Reference< XChartDocument >& rxChartDoc ) +{ + try + { + Reference< XDataReceiver > xDataRec( rxChartDoc, UNO_QUERY_THROW ); + Reference< XMultiServiceFactory > xFactory( getDocument(), UNO_QUERY_THROW ); + Reference< XDataProvider > xDataProv( xFactory->createInstance( + CREATE_OUSTRING( "com.sun.star.chart2.data.DataProvider" ) ), UNO_QUERY_THROW ); + xDataRec->attachDataProvider( xDataProv ); + } + catch( Exception& ) + { + } +} + +Reference< XDataSequence > ExcelChartConverter::createDataSequence( + const Reference< XDataProvider >& rxDataProvider, const DataSequenceModel& rDataSeq ) +{ + Reference< XDataSequence > xDataSeq; + if( rxDataProvider.is() ) + { + OUString aRangeRep; + if( rDataSeq.maFormula.getLength() > 0 ) + { + // parse the formula string, create a token sequence + FormulaParser& rParser = getFormulaParser(); + TokensFormulaContext aContext( true, true ); + aContext.setBaseAddress( CellAddress( getCurrentSheetIndex(), 0, 0 ) ); + rParser.importFormula( aContext, rDataSeq.maFormula ); + + // create a range list from the token sequence + ApiCellRangeList aRanges; + rParser.extractCellRangeList( aRanges, aContext.getTokens(), false ); + aRangeRep = rParser.generateApiRangeListString( aRanges ); + } + else if( !rDataSeq.maData.empty() ) + { + // create a single-row array from constant source data + Matrix< Any > aMatrix( rDataSeq.maData.size(), 1 ); + Matrix< Any >::iterator aMIt = aMatrix.begin(); + // TODO: how to handle missing values in the map? + for( DataSequenceModel::AnyMap::const_iterator aDIt = rDataSeq.maData.begin(), aDEnd = rDataSeq.maData.end(); aDIt != aDEnd; ++aDIt, ++aMIt ) + *aMIt = aDIt->second; + aRangeRep = FormulaProcessorBase::generateApiArray( aMatrix ); + } + + if( aRangeRep.getLength() > 0 ) try + { + // create the data sequence + xDataSeq = rxDataProvider->createDataSequenceByRangeRepresentation( aRangeRep ); + } + catch( Exception& ) + { + OSL_ENSURE( false, "ExcelChartConverter::createDataSequence - cannot create data sequence" ); + } + } + return xDataSeq; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/excelfilter.cxx b/oox/source/xls/excelfilter.cxx new file mode 100644 index 000000000000..5affbfff44a7 --- /dev/null +++ b/oox/source/xls/excelfilter.cxx @@ -0,0 +1,244 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/excelfilter.hxx" +#include "oox/helper/binaryinputstream.hxx" +#include "oox/xls/biffdetector.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/excelchartconverter.hxx" +#include "oox/xls/stylesbuffer.hxx" +#include "oox/xls/themebuffer.hxx" +#include "oox/xls/workbookfragment.hxx" +#include "oox/dump/biffdumper.hxx" +#include "oox/dump/xlsbdumper.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::xml::sax::XFastDocumentHandler; +using ::oox::core::BinaryFilterBase; +using ::oox::core::FragmentHandlerRef; +using ::oox::core::Relation; +using ::oox::core::Relations; +using ::oox::core::XmlFilterBase; +using ::oox::drawingml::table::TableStyleListPtr; + +namespace oox { +namespace xls { + +// ============================================================================ + +ExcelFilterBase::ExcelFilterBase() : + mpData( 0 ) +{ +} + +ExcelFilterBase::~ExcelFilterBase() +{ + OSL_ENSURE( !mpData, "ExcelFilterBase::~ExcelFilterBase - workbook data not cleared" ); +} + +void ExcelFilterBase::registerWorkbookData( WorkbookData& rData ) +{ + mpData = &rData; +} + +WorkbookData& ExcelFilterBase::getWorkbookData() const +{ + OSL_ENSURE( mpData, "ExcelFilterBase::getWorkbookData - missing workbook data" ); + return *mpData; +} + +void ExcelFilterBase::unregisterWorkbookData() +{ + mpData = 0; +} + +// ============================================================================ + +OUString SAL_CALL ExcelFilter_getImplementationName() throw() +{ + return CREATE_OUSTRING( "com.sun.star.comp.oox.ExcelFilter" ); +} + +Sequence< OUString > SAL_CALL ExcelFilter_getSupportedServiceNames() throw() +{ + OUString aServiceName = CREATE_OUSTRING( "com.sun.star.comp.oox.ExcelFilter" ); + Sequence< OUString > aSeq( &aServiceName, 1 ); + return aSeq; +} + +Reference< XInterface > SAL_CALL ExcelFilter_createInstance( + const Reference< XMultiServiceFactory >& rxGlobalFactory ) throw( Exception ) +{ + return static_cast< ::cppu::OWeakObject* >( new ExcelFilter( rxGlobalFactory ) ); +} + +// ---------------------------------------------------------------------------- + +ExcelFilter::ExcelFilter( const Reference< XMultiServiceFactory >& rxGlobalFactory ) : + XmlFilterBase( rxGlobalFactory ) +{ +} + +ExcelFilter::~ExcelFilter() +{ +} + +bool ExcelFilter::importDocument() throw() +{ + /* To activate the XLSX/XLSB dumper, insert the full path to the file + file:///<path-to-oox-module>/source/dump/xlsbdumper.ini + into the environment variable OOO_XLSBDUMPER and start the office with + this variable (nonpro only). */ + OOX_DUMP_FILE( ::oox::dump::xlsb::Dumper ); + + OUString aWorkbookPath = getFragmentPathFromFirstType( CREATE_OFFICEDOC_RELATIONSTYPE( "officeDocument" ) ); + if( aWorkbookPath.getLength() == 0 ) + return false; + + WorkbookHelperRoot aHelper( *this ); + return aHelper.isValid() && importFragment( new OoxWorkbookFragment( aHelper, aWorkbookPath ) ); +} + +bool ExcelFilter::exportDocument() throw() +{ + return false; +} + +const ::oox::drawingml::Theme* ExcelFilter::getCurrentTheme() const +{ + return &WorkbookHelper( getWorkbookData() ).getTheme(); +} + +::oox::vml::Drawing* ExcelFilter::getVmlDrawing() +{ + return 0; +} + +const TableStyleListPtr ExcelFilter::getTableStyles() +{ + return TableStyleListPtr(); +} + +::oox::drawingml::chart::ChartConverter& ExcelFilter::getChartConverter() +{ + return WorkbookHelper( getWorkbookData() ).getChartConverter(); +} + +GraphicHelper* ExcelFilter::implCreateGraphicHelper() const +{ + return new ExcelGraphicHelper( getWorkbookData() ); +} + +OUString ExcelFilter::implGetImplementationName() const +{ + return ExcelFilter_getImplementationName(); +} + +// ============================================================================ + +OUString SAL_CALL ExcelBiffFilter_getImplementationName() throw() +{ + return CREATE_OUSTRING( "com.sun.star.comp.oox.ExcelBiffFilter" ); +} + +Sequence< OUString > SAL_CALL ExcelBiffFilter_getSupportedServiceNames() throw() +{ + OUString aServiceName = CREATE_OUSTRING( "com.sun.star.comp.oox.ExcelBiffFilter" ); + Sequence< OUString > aSeq( &aServiceName, 1 ); + return aSeq; +} + +Reference< XInterface > SAL_CALL ExcelBiffFilter_createInstance( + const Reference< XMultiServiceFactory >& rxGlobalFactory ) throw( Exception ) +{ + return static_cast< ::cppu::OWeakObject* >( new ExcelBiffFilter( rxGlobalFactory ) ); +} + +// ---------------------------------------------------------------------------- + +ExcelBiffFilter::ExcelBiffFilter( const Reference< XMultiServiceFactory >& rxGlobalFactory ) : + BinaryFilterBase( rxGlobalFactory ) +{ +} + +ExcelBiffFilter::~ExcelBiffFilter() +{ +} + +bool ExcelBiffFilter::importDocument() throw() +{ + /* To activate the BIFF dumper, insert the full path to the file + file:///<path-to-oox-module>/source/dump/biffdumper.ini + into the environment variable OOO_BIFFDUMPER and start the office with + this variable (nonpro only). */ + OOX_DUMP_FILE( ::oox::dump::biff::Dumper ); + + /* The boolean argument "UseBiffFilter" passed through XInitialisation + decides whether to import/export the document with this filter (true), + or to only use the BIFF file dumper implemented in this filter (false + or missing) */ + Any aUseBiffFilter = getArgument( CREATE_OUSTRING( "UseBiffFilter" ) ); + bool bUseBiffFilter = false; + if( !(aUseBiffFilter >>= bUseBiffFilter) || !bUseBiffFilter ) + return true; + + // detect BIFF version and workbook stream name + OUString aWorkbookName; + BiffType eBiff = BiffDetector::detectStorageBiffVersion( aWorkbookName, getStorage() ); + OSL_ENSURE( eBiff != BIFF_UNKNOWN, "ExcelBiffFilter::ExcelBiffFilter - invalid file format" ); + if( eBiff == BIFF_UNKNOWN ) + return false; + + WorkbookHelperRoot aHelper( *this, eBiff ); + return aHelper.isValid() && BiffWorkbookFragment( aHelper, aWorkbookName ).importFragment(); +} + +bool ExcelBiffFilter::exportDocument() throw() +{ + return false; +} + +GraphicHelper* ExcelBiffFilter::implCreateGraphicHelper() const +{ + return new ExcelGraphicHelper( getWorkbookData() ); +} + +OUString ExcelBiffFilter::implGetImplementationName() const +{ + return ExcelBiffFilter_getImplementationName(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox diff --git a/oox/source/xls/excelhandlers.cxx b/oox/source/xls/excelhandlers.cxx new file mode 100644 index 000000000000..aaf551424483 --- /dev/null +++ b/oox/source/xls/excelhandlers.cxx @@ -0,0 +1,259 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/excelhandlers.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OUString; +using ::oox::core::FilterBase; +using ::oox::core::FragmentHandler2; + +namespace oox { +namespace xls { + +// ============================================================================ +// ============================================================================ + +OoxWorkbookFragmentBase::OoxWorkbookFragmentBase( + const WorkbookHelper& rHelper, const OUString& rFragmentPath ) : + FragmentHandler2( rHelper.getOoxFilter(), rFragmentPath ), + WorkbookHelper( rHelper ) +{ +} + +// ============================================================================ + +OoxWorksheetFragmentBase::OoxWorksheetFragmentBase( const WorkbookHelper& rHelper, + const OUString& rFragmentPath, ISegmentProgressBarRef xProgressBar, WorksheetType eSheetType, sal_Int16 nSheet ) : + FragmentHandler2( rHelper.getOoxFilter(), rFragmentPath ), + WorksheetHelperRoot( rHelper, xProgressBar, eSheetType, nSheet ) +{ +} + +OoxWorksheetFragmentBase::OoxWorksheetFragmentBase( + const WorksheetHelper& rHelper, const OUString& rFragmentPath ) : + FragmentHandler2( rHelper.getOoxFilter(), rFragmentPath ), + WorksheetHelperRoot( rHelper ) +{ +} + +// ============================================================================ +// ============================================================================ + +namespace { + +const sal_uInt16 BIFF_BOF_GLOBALS = 0x0005; /// BIFF5-BIFF8 workbook globals. +const sal_uInt16 BIFF_BOF_MODULE = 0x0006; /// BIFF5-BIFF8 Visual Basic module. +const sal_uInt16 BIFF_BOF_SHEET = 0x0010; /// BIFF2-BIFF8 worksheet/dialog sheet. +const sal_uInt16 BIFF_BOF_CHART = 0x0020; /// BIFF2-BIFF8 chart sheet. +const sal_uInt16 BIFF_BOF_MACRO = 0x0040; /// BIFF4-BIFF8 macro sheet. +const sal_uInt16 BIFF_BOF_WORKSPACE = 0x0100; /// BIFF3-BIFF8 workspace. + +} // namespace + +// ============================================================================ + +BiffHandlerBase::~BiffHandlerBase() +{ +} + +bool BiffHandlerBase::skipRecordBlock( sal_uInt16 nEndRecId ) +{ + sal_uInt16 nStartRecId = mrStrm.getRecId(); + while( mrStrm.startNextRecord() && (mrStrm.getRecId() != nEndRecId) ) + if( mrStrm.getRecId() == nStartRecId ) + skipRecordBlock( nEndRecId ); + return !mrStrm.isEof() && (mrStrm.getRecId() == nEndRecId); +} + +bool BiffHandlerBase::isBofRecord() const +{ + return + (mrStrm.getRecId() == BIFF2_ID_BOF) || + (mrStrm.getRecId() == BIFF3_ID_BOF) || + (mrStrm.getRecId() == BIFF4_ID_BOF) || + (mrStrm.getRecId() == BIFF5_ID_BOF); +} + +// ============================================================================ + +BiffContextHandler::BiffContextHandler( const BiffHandlerBase& rParent ) : + BiffHandlerBase( rParent ) +{ +} + +// ============================================================================ + +namespace prv { + +BiffFragmentStreamOwner::BiffFragmentStreamOwner( const FilterBase& rFilter, const OUString& rStrmName ) +{ + // do not automatically close the root stream (indicated by empty stream name) + mxXInStrm.reset( new BinaryXInputStream( rFilter.openInputStream( rStrmName ), rStrmName.getLength() > 0 ) ); + mxBiffStrm.reset( new BiffInputStream( *mxXInStrm ) ); +} + +BiffFragmentStreamOwner::~BiffFragmentStreamOwner() +{ +} + +} // namespace prv + +// ---------------------------------------------------------------------------- + +BiffFragmentHandler::BiffFragmentHandler( const FilterBase& rFilter, const OUString& rStrmName ) : + prv::BiffFragmentStreamOwner( rFilter, rStrmName ), + BiffHandlerBase( *mxBiffStrm ) +{ +} + +BiffFragmentHandler::BiffFragmentHandler( const BiffFragmentHandler& rHandler ) : + prv::BiffFragmentStreamOwner( rHandler ), + BiffHandlerBase( rHandler ) +{ +} + +BiffFragmentType BiffFragmentHandler::startFragment( BiffType eBiff ) +{ + return mrStrm.startNextRecord() ? implStartFragment( eBiff ) : BIFF_FRAGMENT_UNKNOWN; +} + +BiffFragmentType BiffFragmentHandler::startFragment( BiffType eBiff, sal_Int64 nRecHandle ) +{ + return mrStrm.startRecordByHandle( nRecHandle ) ? implStartFragment( eBiff ) : BIFF_FRAGMENT_UNKNOWN; +} + +bool BiffFragmentHandler::skipFragment() +{ + while( mrStrm.startNextRecord() && (mrStrm.getRecId() != BIFF_ID_EOF) ) + if( isBofRecord() ) + skipFragment(); + return !mrStrm.isEof() && (mrStrm.getRecId() == BIFF_ID_EOF); +} + +BiffFragmentType BiffFragmentHandler::implStartFragment( BiffType eBiff ) +{ + BiffFragmentType eFragment = BIFF_FRAGMENT_UNKNOWN; + /* #i23425# Don't rely on BOF record ID to read BOF contents, but on + the detected BIFF version. */ + if( isBofRecord() ) + { + // BOF is always written unencrypted + mrStrm.enableDecoder( false ); + mrStrm.skip( 2 ); + sal_uInt16 nType = mrStrm.readuInt16(); + + // decide which fragment types are valid for current BIFF version + switch( eBiff ) + { + case BIFF2: switch( nType ) + { + case BIFF_BOF_CHART: eFragment = BIFF_FRAGMENT_EMPTYSHEET; break; + case BIFF_BOF_MACRO: eFragment = BIFF_FRAGMENT_MACROSHEET; break; + // #i51490# Excel interprets invalid types as worksheet + default: eFragment = BIFF_FRAGMENT_WORKSHEET; + } + break; + + case BIFF3: switch( nType ) + { + case BIFF_BOF_CHART: eFragment = BIFF_FRAGMENT_EMPTYSHEET; break; + case BIFF_BOF_MACRO: eFragment = BIFF_FRAGMENT_MACROSHEET; break; + case BIFF_BOF_WORKSPACE:eFragment = BIFF_FRAGMENT_UNKNOWN; break; + // #i51490# Excel interprets invalid types as worksheet + default: eFragment = BIFF_FRAGMENT_WORKSHEET; + }; + break; + + case BIFF4: switch( nType ) + { + case BIFF_BOF_CHART: eFragment = BIFF_FRAGMENT_EMPTYSHEET; break; + case BIFF_BOF_MACRO: eFragment = BIFF_FRAGMENT_MACROSHEET; break; + case BIFF_BOF_WORKSPACE:eFragment = BIFF_FRAGMENT_WORKSPACE; break; + // #i51490# Excel interprets invalid types as worksheet + default: eFragment = BIFF_FRAGMENT_WORKSHEET; + }; + break; + + case BIFF5: + case BIFF8: switch( nType ) + { + case BIFF_BOF_GLOBALS: eFragment = BIFF_FRAGMENT_GLOBALS; break; + case BIFF_BOF_CHART: eFragment = BIFF_FRAGMENT_CHARTSHEET; break; + case BIFF_BOF_MACRO: eFragment = BIFF_FRAGMENT_MACROSHEET; break; + case BIFF_BOF_MODULE: eFragment = BIFF_FRAGMENT_MODULESHEET; break; + case BIFF_BOF_WORKSPACE:eFragment = BIFF_FRAGMENT_UNKNOWN; break; + // #i51490# Excel interprets invalid types as worksheet + default: eFragment = BIFF_FRAGMENT_WORKSHEET; + }; + break; + + case BIFF_UNKNOWN: break; + } + } + return eFragment; +} + +// ============================================================================ + +BiffWorkbookFragmentBase::BiffWorkbookFragmentBase( const WorkbookHelper& rHelper, const OUString& rStrmName, bool bCloneDecoder ) : + BiffFragmentHandler( rHelper.getBaseFilter(), rStrmName ), + WorkbookHelper( rHelper ) +{ + if( bCloneDecoder ) + getCodecHelper().cloneDecoder( mrStrm ); +} + +// ============================================================================ + +BiffWorksheetFragmentBase::BiffWorksheetFragmentBase( const BiffWorkbookFragmentBase& rParent, + ISegmentProgressBarRef xProgressBar, WorksheetType eSheetType, sal_Int16 nSheet ) : + BiffFragmentHandler( rParent ), + WorksheetHelperRoot( rParent, xProgressBar, eSheetType, nSheet ) +{ +} + +// ============================================================================ + +BiffSkipWorksheetFragment::BiffSkipWorksheetFragment( + const BiffWorkbookFragmentBase& rParent, ISegmentProgressBarRef xProgressBar, sal_Int16 nSheet ) : + BiffWorksheetFragmentBase( rParent, xProgressBar, SHEETTYPE_EMPTYSHEET, nSheet ) +{ +} + +bool BiffSkipWorksheetFragment::importFragment() +{ + return skipFragment(); +} + +// ============================================================================ +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/excelvbaproject.cxx b/oox/source/xls/excelvbaproject.cxx new file mode 100755 index 000000000000..ee22dc8398ba --- /dev/null +++ b/oox/source/xls/excelvbaproject.cxx @@ -0,0 +1,161 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/excelvbaproject.hxx" + +#include <list> +#include <set> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/document/XEventsSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/script/ModuleType.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <rtl/ustrbuf.hxx> +#include "oox/helper/helper.hxx" +#include "oox/helper/propertyset.hxx" +#include "properties.hxx" + +namespace oox { +namespace xls { + +// ============================================================================ + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::sheet; +using namespace ::com::sun::star::uno; + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; + +// ============================================================================ + +ExcelVbaProject::ExcelVbaProject( const Reference< XMultiServiceFactory >& rxGlobalFactory, const Reference< XSpreadsheetDocument >& rxDocument ) : + ::oox::ole::VbaProject( rxGlobalFactory, Reference< XModel >( rxDocument, UNO_QUERY ), CREATE_OUSTRING( "Calc" ) ), + mxDocument( rxDocument ) +{ +} + +void ExcelVbaProject::createMissingModules() +{ + /* !! HACK !! This function is called from old XLS filter only, must be + removed when this filter uses the OOX VBA import instead of the old SVX + VBA import. + */ + + // collect and register all sheets without codenames + prepareImport(); + // manually create the registered dummy modules + createDummyModules(); +} + +// protected ------------------------------------------------------------------ + +namespace { + +struct SheetCodeNameInfo +{ + PropertySet maSheetProps; /// Property set of the sheet without codename. + OUString maPrefix; /// Prefix for the codename to be generated. + + inline explicit SheetCodeNameInfo( PropertySet& rSheetProps, const OUString& rPrefix ) : + maSheetProps( rSheetProps ), maPrefix( rPrefix ) {} +}; + +typedef ::std::set< OUString > CodeNameSet; +typedef ::std::list< SheetCodeNameInfo > SheetCodeNameInfoList; + +} // namespace + +void ExcelVbaProject::prepareImport() +{ + /* Check if the sheets have imported codenames. Generate new unused + codenames if not. */ + if( mxDocument.is() ) try + { + // collect existing codenames (do not use them when creating new codenames) + CodeNameSet aUsedCodeNames; + + // collect sheets without codenames + SheetCodeNameInfoList aCodeNameInfos; + + // iterate over all imported sheets + Reference< XEnumerationAccess > xSheetsEA( mxDocument->getSheets(), UNO_QUERY_THROW ); + Reference< XEnumeration > xSheetsEnum( xSheetsEA->createEnumeration(), UNO_SET_THROW ); + // own try/catch for every sheet + while( xSheetsEnum->hasMoreElements() ) try + { + PropertySet aSheetProp( xSheetsEnum->nextElement() ); + OUString aCodeName; + aSheetProp.getProperty( aCodeName, PROP_CodeName ); + if( aCodeName.getLength() > 0 ) + { + aUsedCodeNames.insert( aCodeName ); + } + else + { + // TODO: once we have chart sheets we need a switch/case on sheet type ('SheetNNN' vs. 'ChartNNN') + aCodeNameInfos.push_back( SheetCodeNameInfo( aSheetProp, CREATE_OUSTRING( "Sheet" ) ) ); + } + } + catch( Exception& ) + { + } + + // create new codenames if sheets do not have one + for( SheetCodeNameInfoList::iterator aIt = aCodeNameInfos.begin(), aEnd = aCodeNameInfos.end(); aIt != aEnd; ++aIt ) + { + // search for an unused codename + sal_Int32 nCounter = 1; + OUString aCodeName; + do + { + aCodeName = OUStringBuffer( aIt->maPrefix ).append( nCounter++ ).makeStringAndClear(); + } + while( aUsedCodeNames.count( aCodeName ) > 0 ); + aUsedCodeNames.insert( aCodeName ); + + // set codename at sheet + aIt->maSheetProps.setProperty( PROP_CodeName, aCodeName ); + + // tell base class to create a dummy module + addDummyModule( aCodeName, ModuleType::DOCUMENT ); + } + } + catch( Exception& ) + { + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox diff --git a/oox/source/xls/externallinkbuffer.cxx b/oox/source/xls/externallinkbuffer.cxx new file mode 100644 index 000000000000..04fcd2f84648 --- /dev/null +++ b/oox/source/xls/externallinkbuffer.cxx @@ -0,0 +1,1143 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/externallinkbuffer.hxx" +#include <rtl/strbuf.hxx> +#include <com/sun/star/sheet/ComplexReference.hpp> +#include <com/sun/star/sheet/DDELinkInfo.hpp> +#include <com/sun/star/sheet/ExternalLinkType.hpp> +#include <com/sun/star/sheet/ExternalReference.hpp> +#include <com/sun/star/sheet/ReferenceFlags.hpp> +#include <com/sun/star/sheet/SingleReference.hpp> +#include <com/sun/star/sheet/XDDELinks.hpp> +#include <com/sun/star/sheet/XDDELink.hpp> +#include <com/sun/star/sheet/XDDELinkResults.hpp> +#include <com/sun/star/sheet/XExternalDocLink.hpp> +#include <com/sun/star/sheet/XExternalDocLinks.hpp> +#include "oox/helper/attributelist.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/excelhandlers.hxx" +#include "oox/xls/formulaparser.hxx" +#include "oox/xls/worksheetbuffer.hxx" + +using ::rtl::OString; +using ::rtl::OStringBuffer; +using ::rtl::OStringToOUString; +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::sheet::ComplexReference; +using ::com::sun::star::sheet::DDEItemInfo; +using ::com::sun::star::sheet::DDELinkInfo; +using ::com::sun::star::sheet::ExternalLinkInfo; +using ::com::sun::star::sheet::ExternalReference; +using ::com::sun::star::sheet::SingleReference; +using ::com::sun::star::sheet::XDDELinks; +using ::com::sun::star::sheet::XDDELinkResults; +using ::com::sun::star::sheet::XExternalDocLinks; +using ::com::sun::star::sheet::XExternalSheetCache; +using ::oox::core::Relation; +using ::oox::core::Relations; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt16 OOBIN_EXTERNALBOOK_BOOK = 0; +const sal_uInt16 OOBIN_EXTERNALBOOK_DDE = 1; +const sal_uInt16 OOBIN_EXTERNALBOOK_OLE = 2; + +const sal_uInt16 OOBIN_EXTNAME_AUTOMATIC = 0x0002; +const sal_uInt16 OOBIN_EXTNAME_PREFERPIC = 0x0004; +const sal_uInt16 OOBIN_EXTNAME_STDDOCNAME = 0x0008; +const sal_uInt16 OOBIN_EXTNAME_OLEOBJECT = 0x0010; +const sal_uInt16 OOBIN_EXTNAME_ICONIFIED = 0x0020; + +const sal_uInt16 BIFF_EXTNAME_BUILTIN = 0x0001; +const sal_uInt16 BIFF_EXTNAME_AUTOMATIC = 0x0002; +const sal_uInt16 BIFF_EXTNAME_PREFERPIC = 0x0004; +const sal_uInt16 BIFF_EXTNAME_STDDOCNAME = 0x0008; +const sal_uInt16 BIFF_EXTNAME_OLEOBJECT = 0x0010; +const sal_uInt16 BIFF_EXTNAME_ICONIFIED = 0x8000; + +} // namespace + +// ============================================================================ + +ExternalNameModel::ExternalNameModel() : + mbBuiltIn( false ), + mbNotify( false ), + mbPreferPic( false ), + mbStdDocName( false ), + mbOleObj( false ), + mbIconified( false ) +{ +} + +// ============================================================================ + +ExternalName::ExternalName( const ExternalLink& rParentLink ) : + DefinedNameBase( rParentLink ), + mrParentLink( rParentLink ), + mnStorageId( 0 ), + mbDdeLinkCreated( false ) +{ +} + +void ExternalName::importDefinedName( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importDefinedName - empty name" ); + // zero-based index into sheet list of externalBook + maModel.mnSheet = rAttribs.getInteger( XML_sheetId, -1 ); +} + +void ExternalName::importDdeItem( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importDdeItem - empty name" ); + maExtNameModel.mbOleObj = false; + maExtNameModel.mbStdDocName = rAttribs.getBool( XML_ole, false ); + maExtNameModel.mbNotify = rAttribs.getBool( XML_advise, false ); + maExtNameModel.mbPreferPic = rAttribs.getBool( XML_preferPic, false ); +} + +void ExternalName::importValues( const AttributeList& rAttribs ) +{ + setResultSize( rAttribs.getInteger( XML_cols, 1 ), rAttribs.getInteger( XML_rows, 1 ) ); +} + +void ExternalName::importOleItem( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importOleItem - empty name" ); + maExtNameModel.mbOleObj = true; + maExtNameModel.mbNotify = rAttribs.getBool( XML_advise, false ); + maExtNameModel.mbPreferPic = rAttribs.getBool( XML_preferPic, false ); + maExtNameModel.mbIconified = rAttribs.getBool( XML_icon, false ); +} + +void ExternalName::importExternalName( RecordInputStream& rStrm ) +{ + rStrm >> maModel.maName; + OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importExternalName - empty name" ); +} + +void ExternalName::importExternalNameFlags( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags; + sal_Int32 nSheetId; + rStrm >> nFlags >> nSheetId; + // index into sheet list of EXTSHEETNAMES (one-based in OOBIN) + maModel.mnSheet = nSheetId - 1; + // no flag for built-in names, as in OOX... + maExtNameModel.mbNotify = getFlag( nFlags, OOBIN_EXTNAME_AUTOMATIC ); + maExtNameModel.mbPreferPic = getFlag( nFlags, OOBIN_EXTNAME_PREFERPIC ); + maExtNameModel.mbStdDocName = getFlag( nFlags, OOBIN_EXTNAME_STDDOCNAME ); + maExtNameModel.mbOleObj = getFlag( nFlags, OOBIN_EXTNAME_OLEOBJECT ); + maExtNameModel.mbIconified = getFlag( nFlags, OOBIN_EXTNAME_ICONIFIED ); + OSL_ENSURE( (mrParentLink.getLinkType() == LINKTYPE_OLE) == maExtNameModel.mbOleObj, + "ExternalName::importExternalNameFlags - wrong OLE flag in external name" ); +} + +void ExternalName::importDdeItemValues( RecordInputStream& rStrm ) +{ + sal_Int32 nRows, nCols; + rStrm >> nRows >> nCols; + setResultSize( nCols, nRows ); +} + +void ExternalName::importDdeItemBool( RecordInputStream& rStrm ) +{ + appendResultValue< double >( (rStrm.readuInt8() == 0) ? 0.0 : 1.0 ); +} + +void ExternalName::importDdeItemDouble( RecordInputStream& rStrm ) +{ + appendResultValue( rStrm.readDouble() ); +} + +void ExternalName::importDdeItemError( RecordInputStream& rStrm ) +{ + appendResultValue( BiffHelper::calcDoubleFromError( rStrm.readuInt8() ) ); +} + +void ExternalName::importDdeItemString( RecordInputStream& rStrm ) +{ + appendResultValue( rStrm.readString() ); +} + +void ExternalName::importExternalName( BiffInputStream& rStrm ) +{ + sal_uInt16 nFlags = 0; + if( getBiff() >= BIFF3 ) + { + rStrm >> nFlags; + maExtNameModel.mbBuiltIn = getFlag( nFlags, BIFF_EXTNAME_BUILTIN ); + maExtNameModel.mbNotify = getFlag( nFlags, BIFF_EXTNAME_AUTOMATIC ); + maExtNameModel.mbPreferPic = getFlag( nFlags, BIFF_EXTNAME_PREFERPIC ); + + // BIFF5-BIFF8: sheet index for sheet-local names, OLE settings + if( getBiff() >= BIFF5 ) + { + maExtNameModel.mbStdDocName = getFlag( nFlags, BIFF_EXTNAME_STDDOCNAME ); + maExtNameModel.mbOleObj = getFlag( nFlags, BIFF_EXTNAME_OLEOBJECT ); + maExtNameModel.mbIconified = getFlag( nFlags, BIFF_EXTNAME_ICONIFIED ); + + if( maExtNameModel.mbOleObj ) + { + rStrm >> mnStorageId; + } + else + { + /* Import the reference ID for names that are sheet-local in + the external document. This index will be resolved later to + the index of the external sheet cache which is able to + provide the name of the sheet related to this defined name. + - BIFF5: one-based index to EXTERNSHEET record containing + the document and sheet name + - BIFF8: one-based index into EXTERNALBOOK sheet name list + The value zero means this external name is a global name. + */ + rStrm.skip( 2 ); + maModel.mnSheet = rStrm.readuInt16(); + } + } + } + + maModel.maName = (getBiff() == BIFF8) ? + rStrm.readUniStringBody( rStrm.readuInt8() ) : + rStrm.readByteStringUC( false, getTextEncoding() ); + OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importExternalName - empty name" ); + + // load cell references that are stored in hidden external names (seen in BIFF3-BIFF4) + bool bHiddenRef = (getBiff() <= BIFF4) && (maModel.maName.getLength() > 1) && (maModel.maName[ 0 ] == '\x01') && (rStrm.getRemaining() > 2); + switch( mrParentLink.getLinkType() ) + { + case LINKTYPE_INTERNAL: + // cell references to other internal sheets are stored in hidden external names + if( bHiddenRef && (getBiff() == BIFF4) && isWorkbookFile() ) + { + TokensFormulaContext aContext( true, true ); + importBiffFormula( aContext, mrParentLink.getCalcSheetIndex(), rStrm ); + extractReference( aContext.getTokens() ); + } + break; + + case LINKTYPE_EXTERNAL: + // cell references to other documents are stored in hidden external names + if( bHiddenRef ) + { + TokensFormulaContext aContext( true, true ); + importBiffFormula( aContext, 0, rStrm ); + extractExternalReference( aContext.getTokens() ); + } + break; + + case LINKTYPE_DDE: + case LINKTYPE_OLE: + case LINKTYPE_MAYBE_DDE_OLE: + // DDE/OLE link results + if( rStrm.getRemaining() > 3 ) + { + bool bBiff8 = getBiff() == BIFF8; + sal_Int32 nCols = rStrm.readuInt8(); + sal_Int32 nRows = rStrm.readuInt16(); + if( bBiff8 ) { ++nCols; ++nRows; } else if( nCols == 0 ) nCols = 256; + setResultSize( nCols, nRows ); + + bool bLoop = true; + while( bLoop && !rStrm.isEof() && (maCurrIt != maResults.end()) ) + { + switch( rStrm.readuInt8() ) + { + case BIFF_DATATYPE_EMPTY: + appendResultValue( OUString() ); + rStrm.skip( 8 ); + break; + case BIFF_DATATYPE_DOUBLE: + appendResultValue( rStrm.readDouble() ); + break; + case BIFF_DATATYPE_STRING: + appendResultValue( bBiff8 ? rStrm.readUniString() : rStrm.readByteStringUC( false, getTextEncoding() ) ); + break; + case BIFF_DATATYPE_BOOL: + appendResultValue< double >( (rStrm.readuInt8() == 0) ? 0.0 : 1.0 ); + rStrm.skip( 7 ); + break; + case BIFF_DATATYPE_ERROR: + appendResultValue( BiffHelper::calcDoubleFromError( rStrm.readuInt8() ) ); + rStrm.skip( 7 ); + break; + default: + bLoop = false; + } + } + OSL_ENSURE( bLoop && !rStrm.isEof() && (maCurrIt == maResults.end()), + "ExternalName::importExternalName - stream error in result set" ); + } + break; + + default:; + } +} + +#if 0 +sal_Int32 ExternalName::getSheetCacheIndex() const +{ + OSL_ENSURE( mrParentLink.getLinkType() == LINKTYPE_DDE, "ExternalName::getSheetCacheIndex - unexpected link type" ); + sal_Int32 nCacheIdx = -1; + switch( getFilterType() ) + { + case FILTER_OOX: + // OOXML/OOBIN: zero-based index into sheet list, -1 means global name + if( maModel.mnSheet >= 0 ) + nCacheIdx = mrParentLink.getSheetIndex( maModel.mnSheet ); + break; + case FILTER_BIFF: + switch( getBiff() ) + { + case BIFF2: + case BIFF3: + case BIFF4: + break; + case BIFF5: + if( maModel.mnSheet > 0 ) + if( const ExternalLink* pExtLink = getExternalLinks().getExternalLink( maModel.mnSheet ).get() ) + if( pExtLink->getLinkType() == LINKTYPE_EXTERNAL ) + nCacheIdx = pExtLink->getSheetIndex(); + break; + case BIFF8: + if( maModel.mnSheet > 0 ) + nCacheIdx = mrParentLink.getSheetIndex( maModel.mnSheet - 1 ); + break; + case BIFF_UNKNOWN: + break; + } + break; + case FILTER_UNKNOWN: + break; + } + return nCacheIdx; +} +#endif + +bool ExternalName::getDdeItemInfo( DDEItemInfo& orItemInfo ) const +{ + if( (mrParentLink.getLinkType() == LINKTYPE_DDE) && (maModel.maName.getLength() > 0) ) + { + orItemInfo.Item = maModel.maName; + orItemInfo.Results = ContainerHelper::matrixToSequenceSequence( maResults ); + return true; + } + return false; +} + +bool ExternalName::getDdeLinkData( OUString& orDdeServer, OUString& orDdeTopic, OUString& orDdeItem ) +{ + if( (mrParentLink.getLinkType() == LINKTYPE_DDE) && (maModel.maName.getLength() > 0) ) + { + // try to create a DDE link and to set the imported link results + if( !mbDdeLinkCreated ) try + { + Reference< XDDELinks > xDdeLinks( getDdeLinks(), UNO_QUERY_THROW ); + mxDdeLink = xDdeLinks->addDDELink( mrParentLink.getClassName(), mrParentLink.getTargetUrl(), maModel.maName, ::com::sun::star::sheet::DDELinkMode_DEFAULT ); + if( !maResults.empty() ) + { + Reference< XDDELinkResults > xResults( mxDdeLink, UNO_QUERY_THROW ); + xResults->setResults( ContainerHelper::matrixToSequenceSequence( maResults ) ); + } + mbDdeLinkCreated = true; + } + catch( Exception& ) + { + OSL_ENSURE( false, "ExternalName::getDdeLinkData - cannot create DDE link" ); + } + // get link data from created DDE link + if( mxDdeLink.is() ) + { + orDdeServer = mxDdeLink->getApplication(); + orDdeTopic = mxDdeLink->getTopic(); + orDdeItem = mxDdeLink->getItem(); + return true; + } + } + return false; +} + +// private -------------------------------------------------------------------- + +namespace { + +void lclSetSheetCacheIndex( SingleReference& orApiRef, sal_Int32 nCacheIdx ) +{ + using namespace ::com::sun::star::sheet::ReferenceFlags; + setFlag( orApiRef.Flags, SHEET_RELATIVE, false ); + setFlag( orApiRef.Flags, SHEET_3D, true ); + orApiRef.Sheet = nCacheIdx; +} + +} // namespace + +void ExternalName::extractExternalReference( const ApiTokenSequence& rTokens ) +{ + OSL_ENSURE( (getFilterType() == FILTER_BIFF) && (getBiff() <= BIFF4), "ExternalName::setExternalReference - unexpected call" ); + sal_Int32 nDocLinkIdx = mrParentLink.getDocumentLinkIndex(); + sal_Int32 nCacheIdx = mrParentLink.getSheetCacheIndex(); + if( (nDocLinkIdx >= 0) && (nCacheIdx >= 0) ) + { + ExternalReference aExtApiRef; + aExtApiRef.Index = nDocLinkIdx; + + Any aRefAny = getFormulaParser().extractReference( rTokens ); + if( aRefAny.has< SingleReference >() ) + { + SingleReference aApiRef; + aRefAny >>= aApiRef; + lclSetSheetCacheIndex( aApiRef, nCacheIdx ); + aExtApiRef.Reference <<= aApiRef; + maRefAny <<= aExtApiRef; + } + else if( aRefAny.has< ComplexReference >() ) + { + ComplexReference aApiRef; + aRefAny >>= aApiRef; + lclSetSheetCacheIndex( aApiRef.Reference1, nCacheIdx ); + lclSetSheetCacheIndex( aApiRef.Reference2, nCacheIdx ); + aExtApiRef.Reference <<= aApiRef; + maRefAny <<= aExtApiRef; + } + } +} + +void ExternalName::setResultSize( sal_Int32 nColumns, sal_Int32 nRows ) +{ + OSL_ENSURE( (mrParentLink.getLinkType() == LINKTYPE_DDE) || (mrParentLink.getLinkType() == LINKTYPE_OLE) || + (mrParentLink.getLinkType() == LINKTYPE_MAYBE_DDE_OLE), "ExternalName::setResultSize - wrong link type" ); + OSL_ENSURE( (nRows > 0) && (nColumns > 0), "ExternalName::setResultSize - invalid matrix size" ); + const CellAddress& rMaxPos = getAddressConverter().getMaxApiAddress(); + if( (0 < nRows) && (nRows <= rMaxPos.Row + 1) && (0 < nColumns) && (nColumns <= rMaxPos.Column + 1) ) + maResults.resize( static_cast< size_t >( nColumns ), static_cast< size_t >( nRows ), Any( BiffHelper::calcDoubleFromError( BIFF_ERR_NA ) ) ); + else + maResults.clear(); + maCurrIt = maResults.begin(); +} + +// ============================================================================ + +void LinkSheetRange::setDeleted() +{ + meType = LINKSHEETRANGE_INTERNAL; + mnDocLink = mnFirst = mnLast = -1; +} + +void LinkSheetRange::setSameSheet() +{ + meType = LINKSHEETRANGE_SAMESHEET; + mnDocLink = -1; + mnFirst = mnLast = 0; +} + +void LinkSheetRange::setRange( sal_Int32 nFirst, sal_Int32 nLast ) +{ + meType = LINKSHEETRANGE_INTERNAL; + mnDocLink = -1; + mnFirst = ::std::min( nFirst, nLast ); + mnLast = ::std::max( nFirst, nLast ); +} + +void LinkSheetRange::setExternalRange( sal_Int32 nDocLink, sal_Int32 nFirst, sal_Int32 nLast ) +{ + if( nDocLink < 0 ) + { + setDeleted(); + } + else + { + meType = LINKSHEETRANGE_EXTERNAL; + mnDocLink = nDocLink; + mnFirst = ::std::min( nFirst, nLast ); + mnLast = ::std::max( nFirst, nLast ); + } +} + +// ============================================================================ + +ExternalLink::ExternalLink( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + meLinkType( LINKTYPE_UNKNOWN ), + meFuncLibType( FUNCLIB_UNKNOWN ) +{ +} + +void ExternalLink::importExternalReference( const AttributeList& rAttribs ) +{ + maRelId = rAttribs.getString( R_TOKEN( id ), OUString() ); +} + +void ExternalLink::importExternalBook( const Relations& rRelations, const AttributeList& rAttribs ) +{ + parseExternalReference( rRelations, rAttribs.getString( R_TOKEN( id ), OUString() ) ); +} + +void ExternalLink::importSheetName( const AttributeList& rAttribs ) +{ + insertExternalSheet( rAttribs.getXString( XML_val, OUString() ) ); +} + +void ExternalLink::importDefinedName( const AttributeList& rAttribs ) +{ + createExternalName()->importDefinedName( rAttribs ); +} + +void ExternalLink::importDdeLink( const AttributeList& rAttribs ) +{ + OUString aDdeService = rAttribs.getXString( XML_ddeService, OUString() ); + OUString aDdeTopic = rAttribs.getXString( XML_ddeTopic, OUString() ); + setDdeOleTargetUrl( aDdeService, aDdeTopic, LINKTYPE_DDE ); +} + +ExternalNameRef ExternalLink::importDdeItem( const AttributeList& rAttribs ) +{ + ExternalNameRef xExtName = createExternalName(); + xExtName->importDdeItem( rAttribs ); + return xExtName; +} + +void ExternalLink::importOleLink( const Relations& rRelations, const AttributeList& rAttribs ) +{ + OUString aProgId = rAttribs.getXString( XML_progId, OUString() ); + OUString aTargetUrl = rRelations.getExternalTargetFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ); + setDdeOleTargetUrl( aProgId, aTargetUrl, LINKTYPE_OLE ); +} + +ExternalNameRef ExternalLink::importOleItem( const AttributeList& rAttribs ) +{ + ExternalNameRef xExtName = createExternalName(); + xExtName->importOleItem( rAttribs ); + return xExtName; +} + +void ExternalLink::importExternalRef( RecordInputStream& rStrm ) +{ + rStrm >> maRelId; +} + +void ExternalLink::importExternalSelf( RecordInputStream& ) +{ + meLinkType = LINKTYPE_SELF; +} + +void ExternalLink::importExternalSame( RecordInputStream& ) +{ + meLinkType = LINKTYPE_SAME; +} + +void ExternalLink::importExternalAddin( RecordInputStream& ) +{ + meLinkType = LINKTYPE_UNKNOWN; +} + +void ExternalLink::importExternalBook( const Relations& rRelations, RecordInputStream& rStrm ) +{ + switch( rStrm.readuInt16() ) + { + case OOBIN_EXTERNALBOOK_BOOK: + parseExternalReference( rRelations, rStrm.readString() ); + break; + case OOBIN_EXTERNALBOOK_DDE: + { + OUString aDdeService, aDdeTopic; + rStrm >> aDdeService >> aDdeTopic; + setDdeOleTargetUrl( aDdeService, aDdeTopic, LINKTYPE_DDE ); + } + break; + case OOBIN_EXTERNALBOOK_OLE: + { + OUString aTargetUrl = rRelations.getExternalTargetFromRelId( rStrm.readString() ); + OUString aProgId = rStrm.readString(); + setDdeOleTargetUrl( aProgId, aTargetUrl, LINKTYPE_OLE ); + } + break; + default: + OSL_ENSURE( false, "ExternalLink::importExternalBook - unknown link type" ); + } +} + +void ExternalLink::importExtSheetNames( RecordInputStream& rStrm ) +{ + // load external sheet names and create the sheet caches in the Calc document + OSL_ENSURE( (meLinkType == LINKTYPE_EXTERNAL) || (meLinkType == LINKTYPE_LIBRARY), + "ExternalLink::importExtSheetNames - invalid link type" ); + if( meLinkType == LINKTYPE_EXTERNAL ) // ignore sheets of external libraries + for( sal_Int32 nSheet = 0, nCount = rStrm.readInt32(); !rStrm.isEof() && (nSheet < nCount); ++nSheet ) + insertExternalSheet( rStrm.readString() ); +} + +ExternalNameRef ExternalLink::importExternalName( RecordInputStream& rStrm ) +{ + ExternalNameRef xExtName = createExternalName(); + xExtName->importExternalName( rStrm ); + return xExtName; +} + +void ExternalLink::importExternSheet( BiffInputStream& rStrm ) +{ + OStringBuffer aTargetBuffer( rStrm.readByteString( false, true ) ); + // references to own sheets have wrong string length field (off by 1) + if( (aTargetBuffer.getLength() > 0) && (aTargetBuffer[ 0 ] == 3) ) + aTargetBuffer.append( static_cast< sal_Char >( rStrm.readuInt8() ) ); + // parse the encoded URL + OUString aBiffTarget = OStringToOUString( aTargetBuffer.makeStringAndClear(), getTextEncoding() ); + OUString aSheetName = parseBiffTargetUrl( aBiffTarget ); + switch( meLinkType ) + { + case LINKTYPE_INTERNAL: + maCalcSheets.push_back( getWorksheets().getCalcSheetIndex( aSheetName ) ); + break; + case LINKTYPE_EXTERNAL: + insertExternalSheet( (aSheetName.getLength() > 0) ? aSheetName : WorksheetBuffer::getBaseFileName( maTargetUrl ) ); + break; + default:; + } +} + +void ExternalLink::importExternalBook( BiffInputStream& rStrm ) +{ + OUString aTarget; + sal_uInt16 nSheetCount; + rStrm >> nSheetCount; + if( rStrm.getRemaining() == 2 ) + { + if( rStrm.readuInt8() == 1 ) + { + sal_Char cChar = static_cast< sal_Char >( rStrm.readuInt8() ); + if( cChar != 0 ) + aTarget = OStringToOUString( OString( cChar ), getTextEncoding() ); + } + } + else if( rStrm.getRemaining() >= 3 ) + { + // NUL characters may occur + aTarget = rStrm.readUniString( true ); + } + + // parse the encoded URL + OUString aDummySheetName = parseBiffTargetUrl( aTarget ); + OSL_ENSURE( aDummySheetName.getLength() == 0, "ExternalLink::importExternalBook - sheet name in encoded URL" ); + (void)aDummySheetName; // prevent compiler warning + + // load external sheet names and create the sheet caches in the Calc document + if( meLinkType == LINKTYPE_EXTERNAL ) + for( sal_uInt16 nSheet = 0; !rStrm.isEof() && (nSheet < nSheetCount); ++nSheet ) + insertExternalSheet( rStrm.readUniString() ); +} + +void ExternalLink::importExternalName( BiffInputStream& rStrm ) +{ + ExternalNameRef xExtName = createExternalName(); + xExtName->importExternalName( rStrm ); + switch( meLinkType ) + { + case LINKTYPE_DDE: + OSL_ENSURE( !xExtName->isOleObject(), "ExternalLink::importExternalName - OLE object in DDE link" ); + break; + case LINKTYPE_OLE: + OSL_ENSURE( xExtName->isOleObject(), "ExternalLink::importExternalName - anything but OLE object in OLE link" ); + break; + case LINKTYPE_MAYBE_DDE_OLE: + meLinkType = xExtName->isOleObject() ? LINKTYPE_OLE : LINKTYPE_DDE; + break; + default: + OSL_ENSURE( !xExtName->isOleObject(), "ExternalLink::importExternalName - OLE object in external name" ); + } +} + +ExternalLinkInfo ExternalLink::getLinkInfo() const +{ + ExternalLinkInfo aLinkInfo; + switch( meLinkType ) + { + case LINKTYPE_EXTERNAL: + aLinkInfo.Type = ::com::sun::star::sheet::ExternalLinkType::DOCUMENT; + aLinkInfo.Data <<= maTargetUrl; + break; + case LINKTYPE_DDE: + { + aLinkInfo.Type = ::com::sun::star::sheet::ExternalLinkType::DDE; + DDELinkInfo aDdeLinkInfo; + aDdeLinkInfo.Service = maClassName; + aDdeLinkInfo.Topic = maTargetUrl; + ::std::vector< DDEItemInfo > aItemInfos; + DDEItemInfo aItemInfo; + for( ExternalNameVector::const_iterator aIt = maExtNames.begin(), aEnd = maExtNames.end(); aIt != aEnd; ++aIt ) + if( (*aIt)->getDdeItemInfo( aItemInfo ) ) + aItemInfos.push_back( aItemInfo ); + aDdeLinkInfo.Items = ContainerHelper::vectorToSequence( aItemInfos ); + aLinkInfo.Data <<= aDdeLinkInfo; + } + break; + default: + aLinkInfo.Type = ::com::sun::star::sheet::ExternalLinkType::UNKNOWN; + } + return aLinkInfo; +} + +FunctionLibraryType ExternalLink::getFuncLibraryType() const +{ + return (meLinkType == LINKTYPE_LIBRARY) ? meFuncLibType : FUNCLIB_UNKNOWN; +} + +sal_Int16 ExternalLink::getCalcSheetIndex( sal_Int32 nTabId ) const +{ + OSL_ENSURE( meLinkType == LINKTYPE_INTERNAL, "ExternalLink::getCalcSheetIndex - invalid link type" ); + OSL_ENSURE( (nTabId == 0) || (getFilterType() == FILTER_OOX) || (getBiff() == BIFF8), + "ExternalLink::getCalcSheetIndex - invalid sheet index" ); + return ContainerHelper::getVectorElement< sal_Int16 >( maCalcSheets, nTabId, -1 ); +} + +sal_Int32 ExternalLink::getDocumentLinkIndex() const +{ + OSL_ENSURE( meLinkType == LINKTYPE_EXTERNAL, "ExternalLink::getDocumentLinkIndex - invalid link type" ); + return mxDocLink.is() ? mxDocLink->getTokenIndex() : -1; +} + +sal_Int32 ExternalLink::getSheetCacheIndex( sal_Int32 nTabId ) const +{ + OSL_ENSURE( meLinkType == LINKTYPE_EXTERNAL, "ExternalLink::getSheetCacheIndex - invalid link type" ); + OSL_ENSURE( (nTabId == 0) || (getFilterType() == FILTER_OOX) || (getBiff() == BIFF8), + "ExternalLink::getSheetCacheIndex - invalid sheet index" ); + return ContainerHelper::getVectorElement< sal_Int32 >( maSheetCaches, nTabId, -1 ); +} + +Reference< XExternalSheetCache > ExternalLink::getSheetCache( sal_Int32 nTabId ) const +{ + sal_Int32 nCacheIdx = getSheetCacheIndex( nTabId ); + if( mxDocLink.is() && (nCacheIdx >= 0) ) try + { + // existing mxDocLink implies that this is an external link + Reference< XExternalSheetCache > xSheetCache( mxDocLink->getByIndex( nCacheIdx ), UNO_QUERY_THROW ); + return xSheetCache; + } + catch( Exception& ) + { + } + return 0; +} + +void ExternalLink::getSheetRange( LinkSheetRange& orSheetRange, sal_Int32 nTabId1, sal_Int32 nTabId2 ) const +{ + switch( meLinkType ) + { + case LINKTYPE_SAME: + orSheetRange.setSameSheet(); + break; + + case LINKTYPE_SELF: + case LINKTYPE_INTERNAL: + orSheetRange.setRange( nTabId1, nTabId2 ); + break; + + case LINKTYPE_EXTERNAL: + { + sal_Int32 nDocLinkIdx = getDocumentLinkIndex(); + switch( getFilterType() ) + { + case FILTER_OOX: + // OOBIN: passed indexes point into sheet list of EXTSHEETLIST + orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) ); + break; + case FILTER_BIFF: + switch( getBiff() ) + { + case BIFF2: + case BIFF3: + case BIFF4: + orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) ); + break; + case BIFF5: + // BIFF5: first sheet from this external link, last sheet is passed in nTabId2 + if( const ExternalLink* pExtLink2 = getExternalLinks().getExternalLink( nTabId2 ).get() ) + if( (pExtLink2->getLinkType() == LINKTYPE_EXTERNAL) && (maTargetUrl == pExtLink2->getTargetUrl()) ) + orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex(), pExtLink2->getSheetCacheIndex() ); + break; + case BIFF8: + // BIFF8: passed indexes point into sheet list of EXTERNALBOOK + orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) ); + break; + case BIFF_UNKNOWN: break; + } + break; + case FILTER_UNKNOWN: break; + } + } + break; + + default: + // unsupported/unexpected link type: #REF! error + orSheetRange.setDeleted(); + } +} + +ExternalNameRef ExternalLink::getNameByIndex( sal_Int32 nIndex ) const +{ + return maExtNames.get( nIndex ); +} + +// private -------------------------------------------------------------------- + +#define OOX_TARGETTYPE_EXTLINK CREATE_OFFICEDOC_RELATIONSTYPE( "externalLinkPath" ) +#define OOX_TARGETTYPE_LIBRARY CREATE_MSOFFICE_RELATIONSTYPE( "xlExternalLinkPath/xlLibrary" ) + +void ExternalLink::setExternalTargetUrl( const OUString& rTargetUrl, const OUString& rTargetType ) +{ + meLinkType = LINKTYPE_UNKNOWN; + if( rTargetType == OOX_TARGETTYPE_EXTLINK ) + { + maTargetUrl = getBaseFilter().getAbsoluteUrl( rTargetUrl ); + if( maTargetUrl.getLength() > 0 ) + meLinkType = LINKTYPE_EXTERNAL; + } + else if( rTargetType == OOX_TARGETTYPE_LIBRARY ) + { + meLinkType = LINKTYPE_LIBRARY; + meFuncLibType = getFormulaParser().getFuncLibTypeFromLibraryName( rTargetUrl ); + } + OSL_ENSURE( meLinkType != LINKTYPE_UNKNOWN, "ExternalLink::setExternalTargetUrl - empty target URL or unknown target type" ); + + // create the external document link API object that will contain the sheet caches + if( meLinkType == LINKTYPE_EXTERNAL ) + { + Reference< XExternalDocLinks > xDocLinks = getExternalDocLinks(); + if( xDocLinks.is() ) + mxDocLink = xDocLinks->addDocLink( maTargetUrl ); + } +} + +void ExternalLink::setDdeOleTargetUrl( const OUString& rClassName, const OUString& rTargetUrl, ExternalLinkType eLinkType ) +{ + maClassName = rClassName; + maTargetUrl = rTargetUrl; + meLinkType = ((maClassName.getLength() > 0) && (maTargetUrl.getLength() > 0)) ? eLinkType : LINKTYPE_UNKNOWN; + OSL_ENSURE( meLinkType == eLinkType, "ExternalLink::setDdeOleTargetUrl - missing classname or target" ); +} + +void ExternalLink::parseExternalReference( const Relations& rRelations, const OUString& rRelId ) +{ + if( const Relation* pRelation = rRelations.getRelationFromRelId( rRelId ) ) + setExternalTargetUrl( pRelation->maTarget, pRelation->maType ); +} + +OUString ExternalLink::parseBiffTargetUrl( const OUString& rBiffTargetUrl ) +{ + meLinkType = LINKTYPE_UNKNOWN; + + OUString aClassName, aTargetUrl, aSheetName; + switch( getAddressConverter().parseBiffTargetUrl( aClassName, aTargetUrl, aSheetName, rBiffTargetUrl ) ) + { + case BIFF_TARGETTYPE_URL: + if( aTargetUrl.getLength() == 0 ) + { + meLinkType = (aSheetName.getLength() > 0) ? LINKTYPE_INTERNAL : LINKTYPE_SELF; + } + else if( (aTargetUrl.getLength() == 1) && (aTargetUrl[ 0 ] == ':') ) + { + if( getBiff() >= BIFF4 ) + meLinkType = LINKTYPE_ANALYSIS; + } + else if( (aTargetUrl.getLength() > 1) || (aTargetUrl[ 0 ] != ' ') ) + { + setExternalTargetUrl( aTargetUrl, OOX_TARGETTYPE_EXTLINK ); + } + break; + + case BIFF_TARGETTYPE_SAMESHEET: + OSL_ENSURE( (aTargetUrl.getLength() == 0) && (aSheetName.getLength() == 0), "ExternalLink::parseBiffTargetUrl - unexpected target or sheet name" ); + meLinkType = LINKTYPE_SAME; + break; + + case BIFF_TARGETTYPE_LIBRARY: + OSL_ENSURE( aSheetName.getLength() == 0, "ExternalLink::parseBiffTargetUrl - unexpected sheet name" ); + setExternalTargetUrl( aTargetUrl, OOX_TARGETTYPE_LIBRARY ); + break; + + case BIFF_TARGETTYPE_DDE_OLE: + setDdeOleTargetUrl( aClassName, aTargetUrl, LINKTYPE_MAYBE_DDE_OLE ); + break; + + case BIFF_TARGETTYPE_UNKNOWN: + break; + } + return aSheetName; +} + +void ExternalLink::insertExternalSheet( const OUString& rSheetName ) +{ + OSL_ENSURE( rSheetName.getLength() > 0, "ExternalLink::insertExternalSheet - empty sheet name" ); + if( mxDocLink.is() ) + { + Reference< XExternalSheetCache > xSheetCache = mxDocLink->addSheetCache( rSheetName, false ); + sal_Int32 nCacheIdx = xSheetCache.is() ? xSheetCache->getTokenIndex() : -1; + maSheetCaches.push_back( nCacheIdx ); + } +} + +ExternalNameRef ExternalLink::createExternalName() +{ + ExternalNameRef xExtName( new ExternalName( *this ) ); + maExtNames.push_back( xExtName ); + return xExtName; +} + +// ============================================================================ + +RefSheetsModel::RefSheetsModel() : + mnExtRefId( -1 ), + mnTabId1( -1 ), + mnTabId2( -1 ) +{ +} + +void RefSheetsModel::readOobData( RecordInputStream& rStrm ) +{ + rStrm >> mnExtRefId >> mnTabId1 >> mnTabId2; +} + +void RefSheetsModel::readBiff8Data( BiffInputStream& rStrm ) +{ + mnExtRefId = rStrm.readuInt16(); + mnTabId1 = rStrm.readInt16(); + mnTabId2 = rStrm.readInt16(); +} + +// ---------------------------------------------------------------------------- + +ExternalLinkBuffer::ExternalLinkBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mbUseRefSheets( false ) +{ +} + +ExternalLinkRef ExternalLinkBuffer::importExternalReference( const AttributeList& rAttribs ) +{ + ExternalLinkRef xExtLink = createExternalLink(); + xExtLink->importExternalReference( rAttribs ); + maExtLinks.push_back( xExtLink ); + return xExtLink; +} + +ExternalLinkRef ExternalLinkBuffer::importExternalRef( RecordInputStream& rStrm ) +{ + mbUseRefSheets = true; + ExternalLinkRef xExtLink = createExternalLink(); + xExtLink->importExternalRef( rStrm ); + maExtLinks.push_back( xExtLink ); + return xExtLink; +} + +void ExternalLinkBuffer::importExternalSelf( RecordInputStream& rStrm ) +{ + mbUseRefSheets = true; + createExternalLink()->importExternalSelf( rStrm ); +} + +void ExternalLinkBuffer::importExternalSame( RecordInputStream& rStrm ) +{ + mbUseRefSheets = true; + createExternalLink()->importExternalSame( rStrm ); +} + +void ExternalLinkBuffer::importExternalAddin( RecordInputStream& rStrm ) +{ + mbUseRefSheets = true; + createExternalLink()->importExternalAddin( rStrm ); +} + +void ExternalLinkBuffer::importExternalSheets( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbUseRefSheets, "ExternalLinkBuffer::importExternalSheets - missing EXTERNALREFS records" ); + mbUseRefSheets = true; + OSL_ENSURE( maRefSheets.empty(), "ExternalLinkBuffer::importExternalSheets - multiple EXTERNALSHEETS records" ); + maRefSheets.clear(); + sal_Int32 nRefCount; + rStrm >> nRefCount; + size_t nMaxCount = getLimitedValue< size_t, sal_Int64 >( nRefCount, 0, rStrm.getRemaining() / 12 ); + maRefSheets.reserve( nMaxCount ); + for( size_t nRefId = 0; !rStrm.isEof() && (nRefId < nMaxCount); ++nRefId ) + { + RefSheetsModel aRefSheets; + aRefSheets.readOobData( rStrm ); + maRefSheets.push_back( aRefSheets ); + } +} + +ExternalLinkRef ExternalLinkBuffer::importExternSheet( BiffInputStream& rStrm ) +{ + OSL_ENSURE( getBiff() <= BIFF5, "ExternalLinkBuffer::importExternSheet - wrong BIFF version" ); + ExternalLinkRef xExtLink = createExternalLink(); + xExtLink->importExternSheet( rStrm ); + return xExtLink; +} + +ExternalLinkRef ExternalLinkBuffer::importExternalBook( BiffInputStream& rStrm ) +{ + ExternalLinkRef xExtLink = createExternalLink(); + xExtLink->importExternalBook( rStrm ); + return xExtLink; +} + +void ExternalLinkBuffer::importExternalName( BiffInputStream& rStrm ) +{ + if( !maLinks.empty() ) + maLinks.back()->importExternalName( rStrm ); +} + +void ExternalLinkBuffer::importExternSheet8( BiffInputStream& rStrm ) +{ + OSL_ENSURE( getBiff() == BIFF8, "ExternalLinkBuffer::importExternSheet8 - wrong BIFF version" ); + + sal_uInt16 nRefCount; + rStrm >> nRefCount; + OSL_ENSURE( static_cast< sal_Int64 >( nRefCount * 6 ) == rStrm.getRemaining(), "ExternalLinkBuffer::importExternSheet8 - invalid count" ); + nRefCount = static_cast< sal_uInt16 >( ::std::min< sal_Int64 >( nRefCount, rStrm.getRemaining() / 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. */ + maRefSheets.insert( maRefSheets.begin(), nRefCount, RefSheetsModel() ); + for( RefSheetsModelVec::iterator aIt = maRefSheets.begin(); !rStrm.isEof() && (nRefCount > 0); --nRefCount ) + aIt->readBiff8Data( rStrm ); +} + +Sequence< ExternalLinkInfo > ExternalLinkBuffer::getLinkInfos() const +{ + ::std::vector< ExternalLinkInfo > aLinkInfos; + // dummy entry for index 0 + aLinkInfos.push_back( ExternalLinkInfo( ::com::sun::star::sheet::ExternalLinkType::UNKNOWN, Any() ) ); + for( ExternalLinkVec::const_iterator aIt = maExtLinks.begin(), aEnd = maExtLinks.end(); aIt != aEnd; ++aIt ) + aLinkInfos.push_back( (*aIt)->getLinkInfo() ); + return ContainerHelper::vectorToSequence( aLinkInfos ); +} + +ExternalLinkRef ExternalLinkBuffer::getExternalLink( sal_Int32 nRefId ) const +{ + ExternalLinkRef xExtLink; + switch( getFilterType() ) + { + case FILTER_OOX: + // OOXML: one-based index + if( !mbUseRefSheets ) + xExtLink = maLinks.get( nRefId - 1 ); + // OOBIN: zero-based index into ref-sheets list + else if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) ) + xExtLink = maLinks.get( pRefSheets->mnExtRefId ); + break; + case FILTER_BIFF: + switch( getBiff() ) + { + case BIFF2: + case BIFF3: + case BIFF4: + // one-based index to EXTERNSHEET records + xExtLink = maLinks.get( nRefId - 1 ); + break; + case BIFF5: + if( nRefId < 0 ) + { + // internal links in formula tokens have negative index + xExtLink = maLinks.get( -nRefId - 1 ); + if( xExtLink.get() && !xExtLink->isInternalLink() ) + xExtLink.reset(); + } + else + { + // one-based index to EXTERNSHEET records + xExtLink = maLinks.get( nRefId - 1 ); + } + break; + case BIFF8: + // zero-based index into REF list in EXTERNSHEET record + if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) ) + xExtLink = maLinks.get( pRefSheets->mnExtRefId ); + break; + case BIFF_UNKNOWN: break; + } + break; + case FILTER_UNKNOWN: break; + } + return xExtLink; +} + +LinkSheetRange ExternalLinkBuffer::getSheetRange( sal_Int32 nRefId, sal_Int16 nTabId1, sal_Int16 nTabId2 ) const +{ + OSL_ENSURE( getBiff() <= BIFF5, "ExternalLinkBuffer::getSheetRange - wrong BIFF version" ); + LinkSheetRange aSheetRange; + if( const ExternalLink* pExtLink = getExternalLink( nRefId ).get() ) + pExtLink->getSheetRange( aSheetRange, nTabId1, nTabId2 ); + return aSheetRange; +} + +LinkSheetRange ExternalLinkBuffer::getSheetRange( sal_Int32 nRefId ) const +{ + OSL_ENSURE( ((getFilterType() == FILTER_OOX) && mbUseRefSheets) || (getBiff() == BIFF8), "ExternalLinkBuffer::getSheetRange - wrong BIFF version" ); + LinkSheetRange aSheetRange; + if( const ExternalLink* pExtLink = getExternalLink( nRefId ).get() ) + if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) ) + pExtLink->getSheetRange( aSheetRange, pRefSheets->mnTabId1, pRefSheets->mnTabId2 ); + return aSheetRange; +} + +// private -------------------------------------------------------------------- + +ExternalLinkRef ExternalLinkBuffer::createExternalLink() +{ + ExternalLinkRef xExtLink( new ExternalLink( *this ) ); + maLinks.push_back( xExtLink ); + return xExtLink; +} + +const RefSheetsModel* ExternalLinkBuffer::getRefSheets( sal_Int32 nRefId ) const +{ + return ((0 <= nRefId) && (static_cast< size_t >( nRefId ) < maRefSheets.size())) ? + &maRefSheets[ static_cast< size_t >( nRefId ) ] : 0; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/externallinkfragment.cxx b/oox/source/xls/externallinkfragment.cxx new file mode 100644 index 000000000000..e1bff95ff724 --- /dev/null +++ b/oox/source/xls/externallinkfragment.cxx @@ -0,0 +1,551 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/externallinkfragment.hxx" +#include <com/sun/star/sheet/XExternalSheetCache.hpp> +#include "oox/helper/attributelist.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/defnamesbuffer.hxx" +#include "oox/xls/sheetdatacontext.hxx" +#include "oox/xls/unitconverter.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::sheet::XExternalSheetCache; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; +using ::oox::core::Relation; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxExternalSheetDataContext::OoxExternalSheetDataContext( + OoxWorkbookFragmentBase& rFragment, const Reference< XExternalSheetCache >& rxSheetCache ) : + OoxWorkbookContextBase( rFragment ), + mxSheetCache( rxSheetCache ) +{ + OSL_ENSURE( mxSheetCache.is(), "OoxExternalSheetDataContext::OoxExternalSheetDataContext - missing sheet cache" ); +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxExternalSheetDataContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( sheetData ): + if( nElement == XLS_TOKEN( row ) ) return this; + break; + case XLS_TOKEN( row ): + if( nElement == XLS_TOKEN( cell ) ) { importCell( rAttribs ); return this; } + break; + case XLS_TOKEN( cell ): + if( nElement == XLS_TOKEN( v ) ) return this; // collect characters in onEndElement() + break; + } + return 0; +} + +void OoxExternalSheetDataContext::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( v ): + switch( mnCurrType ) + { + case XML_b: + case XML_n: + setCellValue( Any( rChars.toDouble() ) ); + break; + case XML_e: + setCellValue( Any( BiffHelper::calcDoubleFromError( getUnitConverter().calcBiffErrorCode( rChars ) ) ) ); + break; + case XML_str: + setCellValue( Any( rChars ) ); + break; + } + mnCurrType = XML_TOKEN_INVALID; + break; + } +} + +ContextHandlerRef OoxExternalSheetDataContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_EXTSHEETDATA: + if( nRecId == OOBIN_ID_EXTROW ) { maCurrPos.Row = rStrm.readInt32(); return this; } + break; + case OOBIN_ID_EXTROW: + switch( nRecId ) + { + case OOBIN_ID_EXTCELL_BLANK: importExtCellBlank( rStrm ); break; + case OOBIN_ID_EXTCELL_BOOL: importExtCellBool( rStrm ); break; + case OOBIN_ID_EXTCELL_DOUBLE: importExtCellDouble( rStrm ); break; + case OOBIN_ID_EXTCELL_ERROR: importExtCellError( rStrm ); break; + case OOBIN_ID_EXTCELL_STRING: importExtCellString( rStrm ); break; + } + break; + } + return 0; +} + +// private -------------------------------------------------------------------- + +void OoxExternalSheetDataContext::importCell( const AttributeList& rAttribs ) +{ + if( getAddressConverter().convertToCellAddress( maCurrPos, rAttribs.getString( XML_r, OUString() ), 0, false ) ) + mnCurrType = rAttribs.getToken( XML_t, XML_n ); + else + mnCurrType = XML_TOKEN_INVALID; +} + +void OoxExternalSheetDataContext::importExtCellBlank( RecordInputStream& rStrm ) +{ + maCurrPos.Column = rStrm.readInt32(); + setCellValue( Any( OUString() ) ); +} + +void OoxExternalSheetDataContext::importExtCellBool( RecordInputStream& rStrm ) +{ + maCurrPos.Column = rStrm.readInt32(); + double fValue = (rStrm.readuInt8() == 0) ? 0.0 : 1.0; + setCellValue( Any( fValue ) ); +} + +void OoxExternalSheetDataContext::importExtCellDouble( RecordInputStream& rStrm ) +{ + maCurrPos.Column = rStrm.readInt32(); + setCellValue( Any( rStrm.readDouble() ) ); +} + +void OoxExternalSheetDataContext::importExtCellError( RecordInputStream& rStrm ) +{ + maCurrPos.Column = rStrm.readInt32(); + setCellValue( Any( BiffHelper::calcDoubleFromError( rStrm.readuInt8() ) ) ); +} + +void OoxExternalSheetDataContext::importExtCellString( RecordInputStream& rStrm ) +{ + maCurrPos.Column = rStrm.readInt32(); + setCellValue( Any( rStrm.readString() ) ); +} + +void OoxExternalSheetDataContext::setCellValue( const Any& rValue ) +{ + if( mxSheetCache.is() && getAddressConverter().checkCellAddress( maCurrPos, false ) ) try + { + mxSheetCache->setCellValue( maCurrPos.Column, maCurrPos.Row, rValue ); + } + catch( Exception& ) + { + } +} + +// ============================================================================ + +OoxExternalLinkFragment::OoxExternalLinkFragment( const WorkbookHelper& rHelper, + const OUString& rFragmentPath, ExternalLink& rExtLink ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ), + mrExtLink( rExtLink ), + mnResultType( XML_TOKEN_INVALID ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxExternalLinkFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( externalLink ) ) return this; + break; + + case XLS_TOKEN( externalLink ): + switch( nElement ) + { + case XLS_TOKEN( externalBook ): mrExtLink.importExternalBook( getRelations(), rAttribs ); return this; + case XLS_TOKEN( ddeLink ): mrExtLink.importDdeLink( rAttribs ); return this; + case XLS_TOKEN( oleLink ): mrExtLink.importOleLink( getRelations(), rAttribs ); return this; + } + break; + + case XLS_TOKEN( externalBook ): + switch( nElement ) + { + case XLS_TOKEN( sheetNames ): + case XLS_TOKEN( definedNames ): + case XLS_TOKEN( sheetDataSet ): return this; + } + break; + + case XLS_TOKEN( sheetNames ): + if( nElement == XLS_TOKEN( sheetName ) ) mrExtLink.importSheetName( rAttribs ); + break; + case XLS_TOKEN( definedNames ): + if( nElement == XLS_TOKEN( definedName ) ) mrExtLink.importDefinedName( rAttribs ); + break; + case XLS_TOKEN( sheetDataSet ): + if( (nElement == XLS_TOKEN( sheetData )) && (mrExtLink.getLinkType() == LINKTYPE_EXTERNAL) ) + return createSheetDataContext( rAttribs.getInteger( XML_sheetId, -1 ) ); + break; + + case XLS_TOKEN( ddeLink ): + if( nElement == XLS_TOKEN( ddeItems ) ) return this; + break; + case XLS_TOKEN( ddeItems ): + if( nElement == XLS_TOKEN( ddeItem ) ) + { + mxExtName = mrExtLink.importDdeItem( rAttribs ); + return this; + } + break; + case XLS_TOKEN( ddeItem ): + if( nElement == XLS_TOKEN( values ) ) + { + if( mxExtName.get() ) mxExtName->importValues( rAttribs ); + return this; + } + break; + case XLS_TOKEN( values ): + if( nElement == XLS_TOKEN( value ) ) + { + mnResultType = rAttribs.getToken( XML_t, XML_n ); + return this; + } + break; + case XLS_TOKEN( value ): + if( nElement == XLS_TOKEN( val ) ) return this; // collect value in onEndElement() + break; + + case XLS_TOKEN( oleLink ): + if( nElement == XLS_TOKEN( oleItems ) ) return this; + break; + case XLS_TOKEN( oleItems ): + if( nElement == XLS_TOKEN( oleItem ) ) mxExtName = mrExtLink.importOleItem( rAttribs ); + break; + } + return 0; +} + +void OoxExternalLinkFragment::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( val ): + maResultValue = rChars; + break; + case XLS_TOKEN( value ): + if( mxExtName.get() ) switch( mnResultType ) + { + case XML_b: + mxExtName->appendResultValue( maResultValue.toDouble() ); + break; + case XML_e: + mxExtName->appendResultValue( BiffHelper::calcDoubleFromError( getUnitConverter().calcBiffErrorCode( maResultValue ) ) ); + break; + case XML_n: + mxExtName->appendResultValue( maResultValue.toDouble() ); + break; + case XML_str: + mxExtName->appendResultValue( maResultValue ); + break; + default: + mxExtName->appendResultValue( BiffHelper::calcDoubleFromError( BIFF_ERR_NA ) ); + } + break; + } +} + +ContextHandlerRef OoxExternalLinkFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_EXTERNALBOOK ) + { + mrExtLink.importExternalBook( getRelations(), rStrm ); + return this; + } + break; + + case OOBIN_ID_EXTERNALBOOK: + switch( nRecId ) + { + case OOBIN_ID_EXTSHEETDATA: + if( mrExtLink.getLinkType() == LINKTYPE_EXTERNAL ) + return createSheetDataContext( rStrm.readInt32() ); + break; + + case OOBIN_ID_EXTSHEETNAMES: mrExtLink.importExtSheetNames( rStrm ); break; + case OOBIN_ID_EXTERNALNAME: mxExtName = mrExtLink.importExternalName( rStrm ); return this; + } + break; + + case OOBIN_ID_EXTERNALNAME: + switch( nRecId ) + { + case OOBIN_ID_EXTERNALNAMEFLAGS: if( mxExtName.get() ) mxExtName->importExternalNameFlags( rStrm ); break; + case OOBIN_ID_DDEITEMVALUES: if( mxExtName.get() ) mxExtName->importDdeItemValues( rStrm ); return this; + } + break; + + case OOBIN_ID_DDEITEMVALUES: + switch( nRecId ) + { + case OOBIN_ID_DDEITEM_BOOL: if( mxExtName.get() ) mxExtName->importDdeItemBool( rStrm ); break; + case OOBIN_ID_DDEITEM_DOUBLE: if( mxExtName.get() ) mxExtName->importDdeItemDouble( rStrm ); break; + case OOBIN_ID_DDEITEM_ERROR: if( mxExtName.get() ) mxExtName->importDdeItemError( rStrm ); break; + case OOBIN_ID_DDEITEM_STRING: if( mxExtName.get() ) mxExtName->importDdeItemString( rStrm ); break; + } + break; + } + return 0; +} + +ContextHandlerRef OoxExternalLinkFragment::createSheetDataContext( sal_Int32 nSheetId ) +{ + return new OoxExternalSheetDataContext( *this, mrExtLink.getSheetCache( nSheetId ) ); +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxExternalLinkFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_DDEITEMVALUES, OOBIN_ID_DDEITEMVALUES + 1 }, + { OOBIN_ID_EXTERNALBOOK, OOBIN_ID_EXTERNALBOOK + 228 }, + { OOBIN_ID_EXTERNALNAME, OOBIN_ID_EXTERNALNAME + 10 }, + { OOBIN_ID_EXTROW, -1 }, + { OOBIN_ID_EXTSHEETDATA, OOBIN_ID_EXTSHEETDATA + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +// ============================================================================ + +BiffExternalLinkFragment::BiffExternalLinkFragment( const BiffWorkbookFragmentBase& rParent, bool bImportDefNames ) : + BiffWorkbookFragmentBase( rParent ), + mbImportDefNames( bImportDefNames ) +{ +} + +BiffExternalLinkFragment::~BiffExternalLinkFragment() +{ +} + +bool BiffExternalLinkFragment::importFragment() +{ + // process all record in this sheet fragment + while( mrStrm.startNextRecord() && (mrStrm.getRecId() != BIFF_ID_EOF) ) + { + if( isBofRecord() ) + skipFragment(); // skip unknown embedded fragments + else + importRecord(); + } + return !mrStrm.isEof() && (mrStrm.getRecId() == BIFF_ID_EOF); +} + +void BiffExternalLinkFragment::importRecord() +{ + sal_uInt16 nRecId = mrStrm.getRecId(); + switch( getBiff() ) + { + case BIFF2: switch( nRecId ) + { + case BIFF2_ID_EXTERNALNAME: importExternalName(); break; + case BIFF_ID_EXTERNSHEET: importExternSheet(); break; + case BIFF2_ID_DEFINEDNAME: importDefinedName(); break; + } + break; + case BIFF3: switch( nRecId ) + { + case BIFF_ID_CRN: importCrn(); break; + case BIFF3_ID_EXTERNALNAME: importExternalName(); break; + case BIFF_ID_EXTERNSHEET: importExternSheet(); break; + case BIFF3_ID_DEFINEDNAME: importDefinedName(); break; + case BIFF_ID_XCT: importXct(); break; + } + break; + case BIFF4: switch( nRecId ) + { + case BIFF_ID_CRN: importCrn(); break; + case BIFF3_ID_EXTERNALNAME: importExternalName(); break; + case BIFF_ID_EXTERNSHEET: importExternSheet(); break; + case BIFF3_ID_DEFINEDNAME: importDefinedName(); break; + case BIFF_ID_XCT: importXct(); break; + } + break; + case BIFF5: switch( nRecId ) + { + case BIFF_ID_CRN: importCrn(); break; + case BIFF5_ID_EXTERNALNAME: importExternalName(); break; + case BIFF_ID_EXTERNSHEET: importExternSheet(); break; + case BIFF5_ID_DEFINEDNAME: importDefinedName(); break; + case BIFF_ID_XCT: importXct(); break; + } + break; + case BIFF8: switch( nRecId ) + { + case BIFF_ID_CRN: importCrn(); break; + case BIFF_ID_EXTERNALBOOK: importExternalBook(); break; + case BIFF5_ID_EXTERNALNAME: importExternalName(); break; + case BIFF_ID_EXTERNSHEET: importExternSheet(); break; + case BIFF5_ID_DEFINEDNAME: importDefinedName(); break; + case BIFF_ID_XCT: importXct(); break; + } + break; + case BIFF_UNKNOWN: break; + } +} + +void BiffExternalLinkFragment::finalizeImport() +{ + getDefinedNames().finalizeImport(); +} + +// private -------------------------------------------------------------------- + +void BiffExternalLinkFragment::importExternSheet() +{ + mxSheetCache.clear(); + if( getBiff() == BIFF8 ) + getExternalLinks().importExternSheet8( mrStrm ); + else + mxExtLink = getExternalLinks().importExternSheet( mrStrm ); +} + +void BiffExternalLinkFragment::importExternalBook() +{ + mxSheetCache.clear(); + mxExtLink = getExternalLinks().importExternalBook( mrStrm ); +} + +void BiffExternalLinkFragment::importExternalName() +{ + if( mxExtLink.get() ) + mxExtLink->importExternalName( mrStrm ); +} + +void BiffExternalLinkFragment::importXct() +{ + mxSheetCache.clear(); + if( mxExtLink.get() && (mxExtLink->getLinkType() == LINKTYPE_EXTERNAL) ) + { + switch( getBiff() ) + { + case BIFF2: + break; + case BIFF3: + case BIFF4: + case BIFF5: + mxSheetCache = mxExtLink->getSheetCache( 0 ); + break; + case BIFF8: + mrStrm.skip( 2 ); + mxSheetCache = mxExtLink->getSheetCache( mrStrm.readInt16() ); + break; + case BIFF_UNKNOWN: break; + } + } +} + +void BiffExternalLinkFragment::importCrn() +{ + if( !mxSheetCache.is() ) return; + + sal_uInt8 nCol2, nCol1; + sal_uInt16 nRow; + mrStrm >> nCol2 >> nCol1 >> nRow; + bool bLoop = true; + for( BinAddress aBinAddr( nCol1, nRow ); bLoop && !mrStrm.isEof() && (aBinAddr.mnCol <= nCol2); ++aBinAddr.mnCol ) + { + switch( mrStrm.readuInt8() ) + { + case BIFF_DATATYPE_EMPTY: + mrStrm.skip( 8 ); + setCellValue( aBinAddr, Any( OUString() ) ); + break; + case BIFF_DATATYPE_DOUBLE: + setCellValue( aBinAddr, Any( mrStrm.readDouble() ) ); + break; + case BIFF_DATATYPE_STRING: + { + OUString aText = (getBiff() == BIFF8) ? mrStrm.readUniString() : mrStrm.readByteStringUC( false, getTextEncoding() ); + setCellValue( aBinAddr, Any( aText ) ); + } + break; + case BIFF_DATATYPE_BOOL: + { + double fValue = (mrStrm.readuInt8() == 0) ? 0.0 : 1.0; + setCellValue( aBinAddr, Any( fValue ) ); + mrStrm.skip( 7 ); + } + break; + case BIFF_DATATYPE_ERROR: + setCellValue( aBinAddr, Any( BiffHelper::calcDoubleFromError( mrStrm.readuInt8() ) ) ); + mrStrm.skip( 7 ); + break; + default: + OSL_ENSURE( false, "BiffExternalLinkFragment::importCrn - unknown data type" ); + bLoop = false; + } + } +} + +void BiffExternalLinkFragment::importDefinedName() +{ + if( mbImportDefNames ) + getDefinedNames().importDefinedName( mrStrm ); +} + +void BiffExternalLinkFragment::setCellValue( const BinAddress& rBinAddr, const Any& rValue ) +{ + CellAddress aCellPos; + if( mxSheetCache.is() && getAddressConverter().convertToCellAddress( aCellPos, rBinAddr, 0, false ) ) try + { + mxSheetCache->setCellValue( aCellPos.Column, aCellPos.Row, rValue ); + } + catch( Exception& ) + { + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/formulabase.cxx b/oox/source/xls/formulabase.cxx new file mode 100644 index 000000000000..ea353bb48558 --- /dev/null +++ b/oox/source/xls/formulabase.cxx @@ -0,0 +1,1750 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/formulabase.hxx" +#include <map> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/sheet/AddressConvention.hpp> +#include <com/sun/star/sheet/ReferenceFlags.hpp> +#include <com/sun/star/sheet/SingleReference.hpp> +#include <com/sun/star/sheet/ComplexReference.hpp> +#include <com/sun/star/sheet/FormulaLanguage.hpp> +#include <com/sun/star/sheet/FormulaMapGroup.hpp> +#include <com/sun/star/sheet/FormulaMapGroupSpecialOffset.hpp> +#include <com/sun/star/sheet/XFormulaOpCodeMapper.hpp> +#include <com/sun/star/sheet/XFormulaParser.hpp> +#include <com/sun/star/sheet/XFormulaTokens.hpp> +#include "properties.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OString; +using ::rtl::OStringBuffer; +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::rtl::OStringToOUString; +using ::rtl::OUStringToOString; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::table::XCellRange; +using ::com::sun::star::sheet::SingleReference; +using ::com::sun::star::sheet::ComplexReference; +using ::com::sun::star::sheet::FormulaToken; +using ::com::sun::star::sheet::FormulaOpCodeMapEntry; +using ::com::sun::star::sheet::XSpreadsheetDocument; +using ::com::sun::star::sheet::XFormulaOpCodeMapper; +using ::com::sun::star::sheet::XFormulaTokens; + +namespace oox { +namespace xls { + +// reference helpers ========================================================== + +BinSingleRef2d::BinSingleRef2d() : + mnCol( 0 ), + mnRow( 0 ), + mbColRel( false ), + mbRowRel( false ) +{ +} + +void BinSingleRef2d::setOobData( sal_uInt16 nCol, sal_Int32 nRow, bool bRelativeAsOffset ) +{ + mnCol = nCol & OOBIN_TOK_REF_COLMASK; + mnRow = nRow & OOBIN_TOK_REF_ROWMASK; + mbColRel = getFlag( nCol, OOBIN_TOK_REF_COLREL ); + mbRowRel = getFlag( nCol, OOBIN_TOK_REF_ROWREL ); + if( bRelativeAsOffset && mbColRel && (mnCol > (OOBIN_TOK_REF_COLMASK >> 1)) ) + mnCol -= (OOBIN_TOK_REF_COLMASK + 1); + if( bRelativeAsOffset && mbRowRel && (mnRow > (OOBIN_TOK_REF_ROWMASK >> 1)) ) + mnRow -= (OOBIN_TOK_REF_ROWMASK + 1); +} + +void BinSingleRef2d::setBiff2Data( sal_uInt8 nCol, sal_uInt16 nRow, bool bRelativeAsOffset ) +{ + mnCol = nCol; + mnRow = nRow & BIFF_TOK_REF_ROWMASK; + mbColRel = getFlag( nRow, BIFF_TOK_REF_COLREL ); + mbRowRel = getFlag( nRow, BIFF_TOK_REF_ROWREL ); + if( bRelativeAsOffset && mbColRel && (mnCol >= 0x80) ) + mnCol -= 0x100; + if( bRelativeAsOffset && mbRowRel && (mnRow > (BIFF_TOK_REF_ROWMASK >> 1)) ) + mnRow -= (BIFF_TOK_REF_ROWMASK + 1); +} + +void BinSingleRef2d::setBiff8Data( sal_uInt16 nCol, sal_uInt16 nRow, bool bRelativeAsOffset ) +{ + mnCol = nCol & BIFF_TOK_REF_COLMASK; + mnRow = nRow; + mbColRel = getFlag( nCol, BIFF_TOK_REF_COLREL ); + mbRowRel = getFlag( nCol, BIFF_TOK_REF_ROWREL ); + if( bRelativeAsOffset && mbColRel && (mnCol > (BIFF_TOK_REF_COLMASK >> 1)) ) + mnCol -= (BIFF_TOK_REF_COLMASK + 1); + if( bRelativeAsOffset && mbRowRel && (mnRow >= 0x8000) ) + mnRow -= 0x10000; +} + +void BinSingleRef2d::readOobData( RecordInputStream& rStrm, bool bRelativeAsOffset ) +{ + sal_Int32 nRow; + sal_uInt16 nCol; + rStrm >> nRow >> nCol; + setOobData( nCol, nRow, bRelativeAsOffset ); +} + +void BinSingleRef2d::readBiff2Data( BiffInputStream& rStrm, bool bRelativeAsOffset ) +{ + sal_uInt16 nRow; + sal_uInt8 nCol; + rStrm >> nRow >> nCol; + setBiff2Data( nCol, nRow, bRelativeAsOffset ); +} + +void BinSingleRef2d::readBiff8Data( BiffInputStream& rStrm, bool bRelativeAsOffset ) +{ + sal_uInt16 nRow, nCol; + rStrm >> nRow >> nCol; + setBiff8Data( nCol, nRow, bRelativeAsOffset ); +} + +// ---------------------------------------------------------------------------- + +void BinComplexRef2d::readOobData( RecordInputStream& rStrm, bool bRelativeAsOffset ) +{ + sal_Int32 nRow1, nRow2; + sal_uInt16 nCol1, nCol2; + rStrm >> nRow1 >> nRow2 >> nCol1 >> nCol2; + maRef1.setOobData( nCol1, nRow1, bRelativeAsOffset ); + maRef2.setOobData( nCol2, nRow2, bRelativeAsOffset ); +} + +void BinComplexRef2d::readBiff2Data( BiffInputStream& rStrm, bool bRelativeAsOffset ) +{ + sal_uInt16 nRow1, nRow2; + sal_uInt8 nCol1, nCol2; + rStrm >> nRow1 >> nRow2 >> nCol1 >> nCol2; + maRef1.setBiff2Data( nCol1, nRow1, bRelativeAsOffset ); + maRef2.setBiff2Data( nCol2, nRow2, bRelativeAsOffset ); +} + +void BinComplexRef2d::readBiff8Data( BiffInputStream& rStrm, bool bRelativeAsOffset ) +{ + sal_uInt16 nRow1, nRow2, nCol1, nCol2; + rStrm >> nRow1 >> nRow2 >> nCol1 >> nCol2; + maRef1.setBiff8Data( nCol1, nRow1, bRelativeAsOffset ); + maRef2.setBiff8Data( nCol2, nRow2, bRelativeAsOffset ); +} + +// token vector, sequence ===================================================== + +ApiTokenVector::ApiTokenVector() +{ +} + +Any& ApiTokenVector::append( sal_Int32 nOpCode ) +{ + resize( size() + 1 ); + back().OpCode = nOpCode; + return back().Data; +} + +// token sequence iterator ==================================================== + +ApiTokenIterator::ApiTokenIterator( const ApiTokenSequence& rTokens, sal_Int32 nSpacesOpCode, bool bSkipSpaces ) : + mpToken( rTokens.getConstArray() ), + mpTokenEnd( rTokens.getConstArray() + rTokens.getLength() ), + mnSpacesOpCode( nSpacesOpCode ), + mbSkipSpaces( bSkipSpaces ) +{ + skipSpaces(); +} + +ApiTokenIterator::ApiTokenIterator( const ApiTokenIterator& rIter, bool bSkipSpaces ) : + mpToken( rIter.mpToken ), + mpTokenEnd( rIter.mpTokenEnd ), + mnSpacesOpCode( rIter.mnSpacesOpCode ), + mbSkipSpaces( bSkipSpaces ) +{ + skipSpaces(); +} + +ApiTokenIterator& ApiTokenIterator::operator++() +{ + if( is() ) + { + ++mpToken; + skipSpaces(); + } + return *this; +} + +void ApiTokenIterator::skipSpaces() +{ + if( mbSkipSpaces ) + while( is() && (mpToken->OpCode == mnSpacesOpCode) ) + ++mpToken; +} + +// function data ============================================================== + +namespace { + +const size_t FUNCINFO_PARAMINFOCOUNT = 5; /// Number of parameter type entries. + +const sal_uInt16 FUNCFLAG_VOLATILE = 0x0001; /// Result is volatile (e.g. NOW() function). +const sal_uInt16 FUNCFLAG_IMPORTONLY = 0x0002; /// Only used in import filter. +const sal_uInt16 FUNCFLAG_EXPORTONLY = 0x0004; /// Only used in export filter. +const sal_uInt16 FUNCFLAG_MACROCALL = 0x0008; /// Function is stored as macro call in Excel (_xlfn. prefix). OOXML name MUST exist. +const sal_uInt16 FUNCFLAG_MACROCALLODF = 0x0010; /// ODF-only function stored as macro call in Excel (_xlfnodf. prefix). ODF name MUST exist. +const sal_uInt16 FUNCFLAG_EXTERNAL = 0x0020; /// Function is external in Calc. +const sal_uInt16 FUNCFLAG_MACROFUNC = 0x0040; /// Function is a macro-sheet function. +const sal_uInt16 FUNCFLAG_MACROCMD = 0x0080; /// Function is a macro-sheet command. +const sal_uInt16 FUNCFLAG_ALWAYSVAR = 0x0100; /// Function is always represented by a tFuncVar token. +const sal_uInt16 FUNCFLAG_PARAMPAIRS = 0x0200; /// Optional parameters are expected to appear in pairs. + +const sal_uInt16 FUNCFLAG_FUNCLIBMASK = 0xF000; /// Mask for function library bits. +const sal_uInt16 FUNCFLAG_EUROTOOL = 0x1000; /// Function is part of the EuroTool add-in. + +typedef ::boost::shared_ptr< FunctionInfo > FunctionInfoRef; + +struct FunctionData +{ + const sal_Char* mpcOdfFuncName; /// ODF function name. + const sal_Char* mpcOoxFuncName; /// OOXML function name. + sal_uInt16 mnOobFuncId; /// OOBIN function identifier. + sal_uInt16 mnBiffFuncId; /// BIFF function identifier. + sal_uInt8 mnMinParamCount; /// Minimum number of parameters. + sal_uInt8 mnMaxParamCount; /// Maximum number of parameters. + sal_uInt8 mnRetClass; /// BIFF token class of the return value. + FunctionParamInfo mpParamInfos[ FUNCINFO_PARAMINFOCOUNT ]; /// Information about all parameters. + sal_uInt16 mnFlags; /// Additional flags. + + inline bool isSupported( bool bImportFilter ) const; +}; + +inline bool FunctionData::isSupported( bool bImportFilter ) const +{ + /* For import filters: the FUNCFLAG_EXPORTONLY flag must not be set, + for export filters: the FUNCFLAG_IMPORTONLY flag must not be set. */ + return !getFlag( mnFlags, bImportFilter ? FUNCFLAG_EXPORTONLY : FUNCFLAG_IMPORTONLY ); +} + +const sal_uInt16 NOID = SAL_MAX_UINT16; /// No BIFF/OOBIN function identifier available. +const sal_uInt8 MX = SAL_MAX_UINT8; /// Maximum parameter count. + +// abbreviations for function return token class +const sal_uInt8 R = BIFF_TOKCLASS_REF; +const sal_uInt8 V = BIFF_TOKCLASS_VAL; +const sal_uInt8 A = BIFF_TOKCLASS_ARR; + +// abbreviations for parameter infos +#define RO { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_ORG, false } +#define RV { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_VAL, false } +#define RA { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_ARR, false } +#define RR { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_RPT, false } +#define RX { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_RPX, false } +#define VO { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_ORG, true } +#define VV { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_VAL, true } +#define VA { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_ARR, true } +#define VR { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_RPT, true } +#define VX { FUNC_PARAM_REGULAR, FUNC_PARAMCONV_RPX, true } +#define RO_E { FUNC_PARAM_EXCELONLY, FUNC_PARAMCONV_ORG, false } +#define VR_E { FUNC_PARAM_EXCELONLY, FUNC_PARAMCONV_RPT, true } +#define C { FUNC_PARAM_CALCONLY, FUNC_PARAMCONV_ORG, false } + +// Note: parameter types of all macro sheet functions (FUNCFLAG_MACROFUNC/FUNCFLAG_MACROCMD) untested! + +/** Functions new in BIFF2. */ +static const FunctionData saFuncTableBiff2[] = +{ + { "COUNT", "COUNT", 0, 0, 0, MX, V, { RX }, 0 }, + { "IF", "IF", 1, 1, 2, 3, R, { VO, RO }, 0 }, + { "ISNA", "ISNA", 2, 2, 1, 1, V, { VR }, 0 }, + { "ISERROR", "ISERROR", 3, 3, 1, 1, V, { VR }, 0 }, + { "SUM", "SUM", 4, 4, 0, MX, V, { RX }, 0 }, + { "AVERAGE", "AVERAGE", 5, 5, 1, MX, V, { RX }, 0 }, + { "MIN", "MIN", 6, 6, 1, MX, V, { RX }, 0 }, + { "MAX", "MAX", 7, 7, 1, MX, V, { RX }, 0 }, + { "ROW", "ROW", 8, 8, 0, 1, V, { RO }, 0 }, + { "COLUMN", "COLUMN", 9, 9, 0, 1, V, { RO }, 0 }, + { "NA", "NA", 10, 10, 0, 0, V, {}, 0 }, + { "NPV", "NPV", 11, 11, 2, MX, V, { VR, RX }, 0 }, + { "STDEV", "STDEV", 12, 12, 1, MX, V, { RX }, 0 }, + { "DOLLAR", "DOLLAR", 13, 13, 1, 2, V, { VR }, 0 }, + { "FIXED", "FIXED", 14, 14, 1, 2, V, { VR, VR, C }, 0 }, + { "SIN", "SIN", 15, 15, 1, 1, V, { VR }, 0 }, + { "COS", "COS", 16, 16, 1, 1, V, { VR }, 0 }, + { "TAN", "TAN", 17, 17, 1, 1, V, { VR }, 0 }, + { "COT", "TAN", 17, 17, 1, 1, V, { VR }, FUNCFLAG_EXPORTONLY }, + { "ATAN", "ATAN", 18, 18, 1, 1, V, { VR }, 0 }, + { "ACOT", "ATAN", 18, 18, 1, 1, V, { VR }, FUNCFLAG_EXPORTONLY }, + { "PI", "PI", 19, 19, 0, 0, V, {}, 0 }, + { "SQRT", "SQRT", 20, 20, 1, 1, V, { VR }, 0 }, + { "EXP", "EXP", 21, 21, 1, 1, V, { VR }, 0 }, + { "LN", "LN", 22, 22, 1, 1, V, { VR }, 0 }, + { "LOG10", "LOG10", 23, 23, 1, 1, V, { VR }, 0 }, + { "ABS", "ABS", 24, 24, 1, 1, V, { VR }, 0 }, + { "INT", "INT", 25, 25, 1, 1, V, { VR }, 0 }, + { "SIGN", "SIGN", 26, 26, 1, 1, V, { VR }, 0 }, + { "ROUND", "ROUND", 27, 27, 2, 2, V, { VR }, 0 }, + { "LOOKUP", "LOOKUP", 28, 28, 2, 3, V, { VR, RA }, 0 }, + { "INDEX", "INDEX", 29, 29, 2, 4, R, { RA, VV }, 0 }, + { "REPT", "REPT", 30, 30, 2, 2, V, { VR }, 0 }, + { "MID", "MID", 31, 31, 3, 3, V, { VR }, 0 }, + { "LEN", "LEN", 32, 32, 1, 1, V, { VR }, 0 }, + { "VALUE", "VALUE", 33, 33, 1, 1, V, { VR }, 0 }, + { "TRUE", "TRUE", 34, 34, 0, 0, V, {}, 0 }, + { "FALSE", "FALSE", 35, 35, 0, 0, V, {}, 0 }, + { "AND", "AND", 36, 36, 1, MX, V, { RX }, 0 }, + { "OR", "OR", 37, 37, 1, MX, V, { RX }, 0 }, + { "NOT", "NOT", 38, 38, 1, 1, V, { VR }, 0 }, + { "MOD", "MOD", 39, 39, 2, 2, V, { VR }, 0 }, + { "DCOUNT", "DCOUNT", 40, 40, 3, 3, V, { RO, RR }, 0 }, + { "DSUM", "DSUM", 41, 41, 3, 3, V, { RO, RR }, 0 }, + { "DAVERAGE", "DAVERAGE", 42, 42, 3, 3, V, { RO, RR }, 0 }, + { "DMIN", "DMIN", 43, 43, 3, 3, V, { RO, RR }, 0 }, + { "DMAX", "DMAX", 44, 44, 3, 3, V, { RO, RR }, 0 }, + { "DSTDEV", "DSTDEV", 45, 45, 3, 3, V, { RO, RR }, 0 }, + { "VAR", "VAR", 46, 46, 1, MX, V, { RX }, 0 }, + { "DVAR", "DVAR", 47, 47, 3, 3, V, { RO, RR }, 0 }, + { "TEXT", "TEXT", 48, 48, 2, 2, V, { VR }, 0 }, + { "LINEST", "LINEST", 49, 49, 1, 2, A, { RA, RA, C, C }, 0 }, + { "TREND", "TREND", 50, 50, 1, 3, A, { RA, RA, RA, C }, 0 }, + { "LOGEST", "LOGEST", 51, 51, 1, 2, A, { RA, RA, C, C }, 0 }, + { "GROWTH", "GROWTH", 52, 52, 1, 3, A, { RA, RA, RA, C }, 0 }, + { "PV", "PV", 56, 56, 3, 5, V, { VR }, 0 }, + { "FV", "FV", 57, 57, 3, 5, V, { VR }, 0 }, + { "NPER", "NPER", 58, 58, 3, 5, V, { VR }, 0 }, + { "PMT", "PMT", 59, 59, 3, 5, V, { VR }, 0 }, + { "RATE", "RATE", 60, 60, 3, 6, V, { VR }, 0 }, + { "MIRR", "MIRR", 61, 61, 3, 3, V, { RA, VR }, 0 }, + { "IRR", "IRR", 62, 62, 1, 2, V, { RA, VR }, 0 }, + { "RAND", "RAND", 63, 63, 0, 0, V, {}, FUNCFLAG_VOLATILE }, + { "MATCH", "MATCH", 64, 64, 2, 3, V, { VR, RX, RR }, 0 }, + { "DATE", "DATE", 65, 65, 3, 3, V, { VR }, 0 }, + { "TIME", "TIME", 66, 66, 3, 3, V, { VR }, 0 }, + { "DAY", "DAY", 67, 67, 1, 1, V, { VR }, 0 }, + { "MONTH", "MONTH", 68, 68, 1, 1, V, { VR }, 0 }, + { "YEAR", "YEAR", 69, 69, 1, 1, V, { VR }, 0 }, + { "WEEKDAY", "WEEKDAY", 70, 70, 1, 1, V, { VR, C }, 0 }, + { "HOUR", "HOUR", 71, 71, 1, 1, V, { VR }, 0 }, + { "MINUTE", "MINUTE", 72, 72, 1, 1, V, { VR }, 0 }, + { "SECOND", "SECOND", 73, 73, 1, 1, V, { VR }, 0 }, + { "NOW", "NOW", 74, 74, 0, 0, V, {}, FUNCFLAG_VOLATILE }, + { "AREAS", "AREAS", 75, 75, 1, 1, V, { RO }, 0 }, + { "ROWS", "ROWS", 76, 76, 1, 1, V, { RO }, 0 }, + { "COLUMNS", "COLUMNS", 77, 77, 1, 1, V, { RO }, 0 }, + { "OFFSET", "OFFSET", 78, 78, 3, 5, R, { RO, VR }, FUNCFLAG_VOLATILE }, + { "SEARCH", "SEARCH", 82, 82, 2, 3, V, { VR }, 0 }, + { "TRANSPOSE", "TRANSPOSE", 83, 83, 1, 1, A, { VO }, 0 }, + { "TYPE", "TYPE", 86, 86, 1, 1, V, { VX }, 0 }, + { "ATAN2", "ATAN2", 97, 97, 2, 2, V, { VR }, 0 }, + { "ASIN", "ASIN", 98, 98, 1, 1, V, { VR }, 0 }, + { "ACOS", "ACOS", 99, 99, 1, 1, V, { VR }, 0 }, + { "CHOOSE", "CHOOSE", 100, 100, 2, MX, R, { VO, RO }, 0 }, + { "HLOOKUP", "HLOOKUP", 101, 101, 3, 3, V, { VV, RO, RO, C }, 0 }, + { "VLOOKUP", "VLOOKUP", 102, 102, 3, 3, V, { VV, RO, RO, C }, 0 }, + { "ISREF", "ISREF", 105, 105, 1, 1, V, { RX }, 0 }, + { "LOG", "LOG", 109, 109, 1, 2, V, { VR }, 0 }, + { "CHAR", "CHAR", 111, 111, 1, 1, V, { VR }, 0 }, + { "LOWER", "LOWER", 112, 112, 1, 1, V, { VR }, 0 }, + { "UPPER", "UPPER", 113, 113, 1, 1, V, { VR }, 0 }, + { "PROPER", "PROPER", 114, 114, 1, 1, V, { VR }, 0 }, + { "LEFT", "LEFT", 115, 115, 1, 2, V, { VR }, 0 }, + { "RIGHT", "RIGHT", 116, 116, 1, 2, V, { VR }, 0 }, + { "EXACT", "EXACT", 117, 117, 2, 2, V, { VR }, 0 }, + { "TRIM", "TRIM", 118, 118, 1, 1, V, { VR }, 0 }, + { "REPLACE", "REPLACE", 119, 119, 4, 4, V, { VR }, 0 }, + { "SUBSTITUTE", "SUBSTITUTE", 120, 120, 3, 4, V, { VR }, 0 }, + { "CODE", "CODE", 121, 121, 1, 1, V, { VR }, 0 }, + { "FIND", "FIND", 124, 124, 2, 3, V, { VR }, 0 }, + { "CELL", "CELL", 125, 125, 1, 2, V, { VV, RO }, FUNCFLAG_VOLATILE }, + { "ISERR", "ISERR", 126, 126, 1, 1, V, { VR }, 0 }, + { "ISTEXT", "ISTEXT", 127, 127, 1, 1, V, { VR }, 0 }, + { "ISNUMBER", "ISNUMBER", 128, 128, 1, 1, V, { VR }, 0 }, + { "ISBLANK", "ISBLANK", 129, 129, 1, 1, V, { VR }, 0 }, + { "T", "T", 130, 130, 1, 1, V, { RO }, 0 }, + { "N", "N", 131, 131, 1, 1, V, { RO }, 0 }, + { "DATEVALUE", "DATEVALUE", 140, 140, 1, 1, V, { VR }, 0 }, + { "TIMEVALUE", "TIMEVALUE", 141, 141, 1, 1, V, { VR }, 0 }, + { "SLN", "SLN", 142, 142, 3, 3, V, { VR }, 0 }, + { "SYD", "SYD", 143, 143, 4, 4, V, { VR }, 0 }, + { "DDB", "DDB", 144, 144, 4, 5, V, { VR }, 0 }, + { "INDIRECT", "INDIRECT", 148, 148, 1, 2, R, { VR }, FUNCFLAG_VOLATILE }, + { "CLEAN", "CLEAN", 162, 162, 1, 1, V, { VR }, 0 }, + { "MDETERM", "MDETERM", 163, 163, 1, 1, V, { VA }, 0 }, + { "MINVERSE", "MINVERSE", 164, 164, 1, 1, A, { VA }, 0 }, + { "MMULT", "MMULT", 165, 165, 2, 2, A, { VA }, 0 }, + { "IPMT", "IPMT", 167, 167, 4, 6, V, { VR }, 0 }, + { "PPMT", "PPMT", 168, 168, 4, 6, V, { VR }, 0 }, + { "COUNTA", "COUNTA", 169, 169, 0, MX, V, { RX }, 0 }, + { "PRODUCT", "PRODUCT", 183, 183, 0, MX, V, { RX }, 0 }, + { "FACT", "FACT", 184, 184, 1, 1, V, { VR }, 0 }, + { "DPRODUCT", "DPRODUCT", 189, 189, 3, 3, V, { RO, RR }, 0 }, + { "ISNONTEXT", "ISNONTEXT", 190, 190, 1, 1, V, { VR }, 0 }, + { "STDEVP", "STDEVP", 193, 193, 1, MX, V, { RX }, 0 }, + { "VARP", "VARP", 194, 194, 1, MX, V, { RX }, 0 }, + { "DSTDEVP", "DSTDEVP", 195, 195, 3, 3, V, { RO, RR }, 0 }, + { "DVARP", "DVARP", 196, 196, 3, 3, V, { RO, RR }, 0 }, + { "TRUNC", "TRUNC", 197, 197, 1, 1, V, { VR, C }, 0 }, + { "ISLOGICAL", "ISLOGICAL", 198, 198, 1, 1, V, { VR }, 0 }, + { "DCOUNTA", "DCOUNTA", 199, 199, 3, 3, V, { RO, RR }, 0 }, + { 0, "EXTERN.CALL", 255, 255, 1, MX, R, { RO_E, RO }, FUNCFLAG_IMPORTONLY }, + + // *** macro sheet commands *** + + { 0, "A1.R1C1", 30, 30, 0, 1, V, { VR }, FUNCFLAG_MACROCMD }, + { 0, "RETURN", 55, 55, 0, 1, R, { RO }, FUNCFLAG_MACROFUNC }, + { 0, "ABSREF", 79, 79, 2, 2, R, { VR, RO }, FUNCFLAG_MACROFUNC }, + { 0, "ADD.ARROW", 81, 81, 0, 0, V, {}, FUNCFLAG_MACROCMD }, + { 0, "ACTIVE.CELL", 94, 94, 0, 0, R, {}, FUNCFLAG_MACROFUNC }, + { 0, "ACTIVATE", 103, 103, 0, 2, V, { VR }, FUNCFLAG_MACROCMD }, + { 0, "ACTIVATE.NEXT", 104, 104, 0, 0, V, {}, FUNCFLAG_MACROCMD }, + { 0, "ACTIVATE.PREV", 105, 105, 0, 0, V, {}, FUNCFLAG_MACROCMD }, + { 0, "ADD.BAR", 151, 151, 0, 0, V, {}, FUNCFLAG_MACROFUNC | FUNCFLAG_ALWAYSVAR }, + { 0, "ADD.MENU", 152, 152, 2, 2, V, { VR, RO }, FUNCFLAG_MACROFUNC | FUNCFLAG_ALWAYSVAR }, + { 0, "ADD.COMMAND", 153, 153, 3, 3, V, { VR, RO }, FUNCFLAG_MACROFUNC | FUNCFLAG_ALWAYSVAR } +}; + +/** Functions new in BIFF3. */ +static const FunctionData saFuncTableBiff3[] = +{ + { "LINEST", "LINEST", 49, 49, 1, 4, A, { RA, RA, VV }, 0 }, // BIFF2: 1-2, BIFF3: 1-4 + { "TREND", "TREND", 50, 50, 1, 4, A, { RA, RA, RA, VV }, 0 }, // BIFF2: 1-3, BIFF3: 1-4 + { "LOGEST", "LOGEST", 51, 51, 1, 4, A, { RA, RA, VV }, 0 }, // BIFF2: 1-2, BIFF3: 1-4 + { "GROWTH", "GROWTH", 52, 52, 1, 4, A, { RA, RA, RA, VV }, 0 }, // BIFF2: 1-3, BIFF3: 1-4 + { "TRUNC", "TRUNC", 197, 197, 1, 2, V, { VR }, 0 }, // BIFF2: 1, BIFF3: 1-2 + { "DOLLAR", "USDOLLAR", 204, 204, 1, 2, V, { VR }, FUNCFLAG_IMPORTONLY }, + { 0/*"FIND"*/, "FINDB", 205, 205, 2, 3, V, { VR }, 0 }, + { 0/*"SEARCH"*/, "SEARCHB", 206, 206, 2, 3, V, { VR }, 0 }, + { 0/*"REPLACE"*/, "REPLACEB", 207, 207, 4, 4, V, { VR }, 0 }, + { 0/*"LEFT"*/, "LEFTB", 208, 208, 1, 2, V, { VR }, 0 }, + { 0/*"RIGHT"*/, "RIGHTB", 209, 209, 1, 2, V, { VR }, 0 }, + { 0/*"MID"*/, "MIDB", 210, 210, 3, 3, V, { VR }, 0 }, + { 0/*"LEN"*/, "LENB", 211, 211, 1, 1, V, { VR }, 0 }, + { "ROUNDUP", "ROUNDUP", 212, 212, 2, 2, V, { VR }, 0 }, + { "ROUNDDOWN", "ROUNDDOWN", 213, 213, 2, 2, V, { VR }, 0 }, + { "ASC", "ASC", 214, 214, 1, 1, V, { VR }, 0 }, + { "JIS", "DBCS", 215, 215, 1, 1, V, { VR }, 0 }, + { "ADDRESS", "ADDRESS", 219, 219, 2, 5, V, { VR }, 0 }, + { "DAYS360", "DAYS360", 220, 220, 2, 2, V, { VR, VR, C }, 0 }, + { "TODAY", "TODAY", 221, 221, 0, 0, V, {}, FUNCFLAG_VOLATILE }, + { "VDB", "VDB", 222, 222, 5, 7, V, { VR }, 0 }, + { "MEDIAN", "MEDIAN", 227, 227, 1, MX, V, { RX }, 0 }, + { "SUMPRODUCT", "SUMPRODUCT", 228, 228, 1, MX, V, { VA }, 0 }, + { "SINH", "SINH", 229, 229, 1, 1, V, { VR }, 0 }, + { "COSH", "COSH", 230, 230, 1, 1, V, { VR }, 0 }, + { "TANH", "TANH", 231, 231, 1, 1, V, { VR }, 0 }, + { "COTH", "TANH", 231, 231, 1, 1, V, { VR }, FUNCFLAG_EXPORTONLY }, + { "ASINH", "ASINH", 232, 232, 1, 1, V, { VR }, 0 }, + { "ACOSH", "ACOSH", 233, 233, 1, 1, V, { VR }, 0 }, + { "ATANH", "ATANH", 234, 234, 1, 1, V, { VR }, 0 }, + { "ACOTH", "ATANH", 234, 234, 1, 1, V, { VR }, FUNCFLAG_EXPORTONLY }, + { "DGET", "DGET", 235, 235, 3, 3, V, { RO, RR }, 0 }, + { "INFO", "INFO", 244, 244, 1, 1, V, { VR }, FUNCFLAG_VOLATILE }, + + // *** macro sheet commands *** + + { 0, "ADD.BAR", 151, 151, 0, 1, V, { VR }, FUNCFLAG_MACROFUNC }, // BIFF2: 0, BIFF3: 0-1 + { 0, "ADD.MENU", 152, 152, 2, 3, V, { VR, RO }, FUNCFLAG_MACROFUNC }, // BIFF2: 2, BIFF3: 2-3 + { 0, "ADD.COMMAND", 153, 153, 3, 4, V, { VR, RO }, FUNCFLAG_MACROFUNC } // BIFF2: 3, BIFF3: 3-4 +}; + +/** Functions new in BIFF4. */ +static const FunctionData saFuncTableBiff4[] = +{ + { "FIXED", "FIXED", 14, 14, 1, 3, V, { VR }, 0 }, // BIFF2-3: 1-2, BIFF4: 1-3 + { "RANK", "RANK", 216, 216, 2, 3, V, { VR, RO, VR }, 0 }, + { "DB", "DB", 247, 247, 4, 5, V, { VR }, 0 }, + { "FREQUENCY", "FREQUENCY", 252, 252, 2, 2, A, { RA }, 0 }, + { "ORG.OPENOFFICE.ERRORTYPE","ERROR.TYPE", 261, 261, 1, 1, V, { VR }, 0 }, + { "AVEDEV", "AVEDEV", 269, 269, 1, MX, V, { RX }, 0 }, + { "BETADIST", "BETADIST", 270, 270, 3, 5, V, { VR }, 0 }, + { "GAMMALN", "GAMMALN", 271, 271, 1, 1, V, { VR }, 0 }, + { "BETAINV", "BETAINV", 272, 272, 3, 5, V, { VR }, 0 }, + { "BINOMDIST", "BINOMDIST", 273, 273, 4, 4, V, { VR }, 0 }, + { "LEGACY.CHIDIST", "CHIDIST", 274, 274, 2, 2, V, { VR }, 0 }, + { "LEGACY.CHIINV", "CHIINV", 275, 275, 2, 2, V, { VR }, 0 }, + { "COMBIN", "COMBIN", 276, 276, 2, 2, V, { VR }, 0 }, + { "CONFIDENCE", "CONFIDENCE", 277, 277, 3, 3, V, { VR }, 0 }, + { "CRITBINOM", "CRITBINOM", 278, 278, 3, 3, V, { VR }, 0 }, + { "EVEN", "EVEN", 279, 279, 1, 1, V, { VR }, 0 }, + { "EXPONDIST", "EXPONDIST", 280, 280, 3, 3, V, { VR }, 0 }, + { "LEGACY.FDIST", "FDIST", 281, 281, 3, 3, V, { VR }, 0 }, + { "LEGACY.FINV", "FINV", 282, 282, 3, 3, V, { VR }, 0 }, + { "FISHER", "FISHER", 283, 283, 1, 1, V, { VR }, 0 }, + { "FISHERINV", "FISHERINV", 284, 284, 1, 1, V, { VR }, 0 }, + { "FLOOR", "FLOOR", 285, 285, 2, 2, V, { VR, VR, C }, 0 }, + { "GAMMADIST", "GAMMADIST", 286, 286, 4, 4, V, { VR }, 0 }, + { "GAMMAINV", "GAMMAINV", 287, 287, 3, 3, V, { VR }, 0 }, + { "CEILING", "CEILING", 288, 288, 2, 2, V, { VR, VR, C }, 0 }, + { "HYPGEOMDIST", "HYPGEOMDIST", 289, 289, 4, 4, V, { VR }, 0 }, + { "LOGNORMDIST", "LOGNORMDIST", 290, 290, 3, 3, V, { VR }, 0 }, + { "LOGINV", "LOGINV", 291, 291, 3, 3, V, { VR }, 0 }, + { "NEGBINOMDIST", "NEGBINOMDIST", 292, 292, 3, 3, V, { VR }, 0 }, + { "NORMDIST", "NORMDIST", 293, 293, 4, 4, V, { VR }, 0 }, + { "LEGACY.NORMSDIST", "NORMSDIST", 294, 294, 1, 1, V, { VR }, 0 }, + { "NORMINV", "NORMINV", 295, 295, 3, 3, V, { VR }, 0 }, + { "LEGACY.NORMSINV", "NORMSINV", 296, 296, 1, 1, V, { VR }, 0 }, + { "STANDARDIZE", "STANDARDIZE", 297, 297, 3, 3, V, { VR }, 0 }, + { "ODD", "ODD", 298, 298, 1, 1, V, { VR }, 0 }, + { "PERMUT", "PERMUT", 299, 299, 2, 2, V, { VR }, 0 }, + { "POISSON", "POISSON", 300, 300, 3, 3, V, { VR }, 0 }, + { "TDIST", "TDIST", 301, 301, 3, 3, V, { VR }, 0 }, + { "WEIBULL", "WEIBULL", 302, 302, 4, 4, V, { VR }, 0 }, + { "SUMXMY2", "SUMXMY2", 303, 303, 2, 2, V, { VA }, 0 }, + { "SUMX2MY2", "SUMX2MY2", 304, 304, 2, 2, V, { VA }, 0 }, + { "SUMX2PY2", "SUMX2PY2", 305, 305, 2, 2, V, { VA }, 0 }, + { "LEGACY.CHITEST", "CHITEST", 306, 306, 2, 2, V, { VA }, 0 }, + { "CORREL", "CORREL", 307, 307, 2, 2, V, { VA }, 0 }, + { "COVAR", "COVAR", 308, 308, 2, 2, V, { VA }, 0 }, + { "FORECAST", "FORECAST", 309, 309, 3, 3, V, { VR, VA }, 0 }, + { "FTEST", "FTEST", 310, 310, 2, 2, V, { VA }, 0 }, + { "INTERCEPT", "INTERCEPT", 311, 311, 2, 2, V, { VA }, 0 }, + { "PEARSON", "PEARSON", 312, 312, 2, 2, V, { VA }, 0 }, + { "RSQ", "RSQ", 313, 313, 2, 2, V, { VA }, 0 }, + { "STEYX", "STEYX", 314, 314, 2, 2, V, { VA }, 0 }, + { "SLOPE", "SLOPE", 315, 315, 2, 2, V, { VA }, 0 }, + { "TTEST", "TTEST", 316, 316, 4, 4, V, { VA, VA, VR }, 0 }, + { "PROB", "PROB", 317, 317, 3, 4, V, { VA, VA, VR }, 0 }, + { "DEVSQ", "DEVSQ", 318, 318, 1, MX, V, { RX }, 0 }, + { "GEOMEAN", "GEOMEAN", 319, 319, 1, MX, V, { RX }, 0 }, + { "HARMEAN", "HARMEAN", 320, 320, 1, MX, V, { RX }, 0 }, + { "SUMSQ", "SUMSQ", 321, 321, 0, MX, V, { RX }, 0 }, + { "KURT", "KURT", 322, 322, 1, MX, V, { RX }, 0 }, + { "SKEW", "SKEW", 323, 323, 1, MX, V, { RX }, 0 }, + { "ZTEST", "ZTEST", 324, 324, 2, 3, V, { RX, VR }, 0 }, + { "LARGE", "LARGE", 325, 325, 2, 2, V, { RX, VR }, 0 }, + { "SMALL", "SMALL", 326, 326, 2, 2, V, { RX, VR }, 0 }, + { "QUARTILE", "QUARTILE", 327, 327, 2, 2, V, { RX, VR }, 0 }, + { "PERCENTILE", "PERCENTILE", 328, 328, 2, 2, V, { RX, VR }, 0 }, + { "PERCENTRANK", "PERCENTRANK", 329, 329, 2, 3, V, { RX, VR, VR_E }, 0 }, + { "MODE", "MODE", 330, 330, 1, MX, V, { VA }, 0 }, + { "TRIMMEAN", "TRIMMEAN", 331, 331, 2, 2, V, { RX, VR }, 0 }, + { "TINV", "TINV", 332, 332, 2, 2, V, { VR }, 0 }, + + // *** Analysis add-in *** + + { "HEX2BIN", "HEX2BIN", 384, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "HEX2DEC", "HEX2DEC", 385, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "HEX2OCT", "HEX2OCT", 386, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DEC2BIN", "DEC2BIN", 387, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DEC2HEX", "DEC2HEX", 388, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DEC2OCT", "DEC2OCT", 389, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "OCT2BIN", "OCT2BIN", 390, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "OCT2HEX", "OCT2HEX", 391, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "OCT2DEC", "OCT2DEC", 392, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BIN2DEC", "BIN2DEC", 393, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BIN2OCT", "BIN2OCT", 394, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BIN2HEX", "BIN2HEX", 395, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMSUB", "IMSUB", 396, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMDIV", "IMDIV", 397, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMPOWER", "IMPOWER", 398, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMABS", "IMABS", 399, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMSQRT", "IMSQRT", 400, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMLN", "IMLN", 401, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMLOG2", "IMLOG2", 402, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMLOG10", "IMLOG10", 403, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMSIN", "IMSIN", 404, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMCOS", "IMCOS", 405, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMEXP", "IMEXP", 406, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMARGUMENT", "IMARGUMENT", 407, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMCONJUGATE", "IMCONJUGATE", 408, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMAGINARY", "IMAGINARY", 409, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMREAL", "IMREAL", 410, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COMPLEX", "COMPLEX", 411, NOID, 2, 3, V, { RR }, FUNCFLAG_EXTERNAL }, + { "IMSUM", "IMSUM", 412, NOID, 1, MX, V, { RX }, FUNCFLAG_EXTERNAL }, + { "IMPRODUCT", "IMPRODUCT", 413, NOID, 1, MX, V, { RX }, FUNCFLAG_EXTERNAL }, + { "SERIESSUM", "SERIESSUM", 414, NOID, 4, 4, V, { RR, RR, RR, RX }, FUNCFLAG_EXTERNAL }, + { "FACTDOUBLE", "FACTDOUBLE", 415, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "SQRTPI", "SQRTPI", 416, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "QUOTIENT", "QUOTIENT", 417, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DELTA", "DELTA", 418, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "GESTEP", "GESTEP", 419, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ISEVEN", "ISEVEN", 420, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "ISODD", "ISODD", 421, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "MROUND", "MROUND", 422, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ERF", "ERF", 423, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ERFC", "ERFC", 424, NOID, 1, 1, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BESSELJ", "BESSELJ", 425, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BESSELK", "BESSELK", 426, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BESSELY", "BESSELY", 427, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "BESSELI", "BESSELI", 428, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "XIRR", "XIRR", 429, NOID, 2, 3, V, { RX, RX, RR }, FUNCFLAG_EXTERNAL }, + { "XNPV", "XNPV", 430, NOID, 3, 3, V, { RR, RX, RX }, FUNCFLAG_EXTERNAL }, + { "PRICEMAT", "PRICEMAT", 431, NOID, 5, 6, V, { RR }, FUNCFLAG_EXTERNAL }, + { "YIELDMAT", "YIELDMAT", 432, NOID, 5, 6, V, { RR }, FUNCFLAG_EXTERNAL }, + { "INTRATE", "INTRATE", 433, NOID, 4, 5, V, { RR }, FUNCFLAG_EXTERNAL }, + { "RECEIVED", "RECEIVED", 434, NOID, 4, 5, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DISC", "DISC", 435, NOID, 4, 5, V, { RR }, FUNCFLAG_EXTERNAL }, + { "PRICEDISC", "PRICEDISC", 436, NOID, 4, 5, V, { RR }, FUNCFLAG_EXTERNAL }, + { "YIELDDISC", "YIELDDISC", 437, NOID, 4, 5, V, { RR }, FUNCFLAG_EXTERNAL }, + { "TBILLEQ", "TBILLEQ", 438, NOID, 3, 3, V, { RR }, FUNCFLAG_EXTERNAL }, + { "TBILLPRICE", "TBILLPRICE", 439, NOID, 3, 3, V, { RR }, FUNCFLAG_EXTERNAL }, + { "TBILLYIELD", "TBILLYIELD", 440, NOID, 3, 3, V, { RR }, FUNCFLAG_EXTERNAL }, + { "PRICE", "PRICE", 441, NOID, 6, 7, V, { RR }, FUNCFLAG_EXTERNAL }, + { "YIELD", "YIELD", 442, NOID, 6, 7, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DOLLARDE", "DOLLARDE", 443, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DOLLARFR", "DOLLARFR", 444, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "NOMINAL", "NOMINAL", 445, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "EFFECT", "EFFECT", 446, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "CUMPRINC", "CUMPRINC", 447, NOID, 6, 6, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "CUMIPMT", "CUMIPMT", 448, NOID, 6, 6, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "EDATE", "EDATE", 449, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "EOMONTH", "EOMONTH", 450, NOID, 2, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "YEARFRAC", "YEARFRAC", 451, NOID, 2, 3, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COUPDAYBS", "COUPDAYBS", 452, NOID, 3, 4, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COUPDAYS", "COUPDAYS", 453, NOID, 3, 4, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COUPDAYSNC", "COUPDAYSNC", 454, NOID, 3, 4, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COUPNCD", "COUPNCD", 455, NOID, 3, 4, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COUPNUM", "COUPNUM", 456, NOID, 3, 4, V, { RR }, FUNCFLAG_EXTERNAL }, + { "COUPPCD", "COUPPCD", 457, NOID, 3, 4, V, { RR }, FUNCFLAG_EXTERNAL }, + { "DURATION", "DURATION", 458, NOID, 5, 6, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "MDURATION", "MDURATION", 459, NOID, 5, 6, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ODDLPRICE", "ODDLPRICE", 460, NOID, 7, 8, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ODDLYIELD", "ODDLYIELD", 461, NOID, 8, 9, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ODDFPRICE", "ODDFPRICE", 462, NOID, 8, 9, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ODDFYIELD", "ODDFYIELD", 463, NOID, 8, 9, V, { RR }, FUNCFLAG_EXTERNAL }, + { "RANDBETWEEN", "RANDBETWEEN", 464, NOID, 2, 2, V, { RR }, FUNCFLAG_VOLATILE | FUNCFLAG_EXTERNAL }, + { "WEEKNUM", "WEEKNUM", 465, NOID, 1, 2, V, { RR }, FUNCFLAG_EXTERNAL }, + { "AMORDEGRC", "AMORDEGRC", 466, NOID, 6, 7, V, { RR }, FUNCFLAG_EXTERNAL }, + { "AMORLINC", "AMORLINC", 467, NOID, 6, 7, V, { RR }, FUNCFLAG_EXTERNAL }, + { "CONVERT", "CONVERT", 468, NOID, 3, 3, V, { RR }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "ACCRINT", "ACCRINT", 469, NOID, 6, 7, V, { RR }, FUNCFLAG_EXTERNAL }, + { "ACCRINTM", "ACCRINTM", 470, NOID, 4, 5, V, { RR }, FUNCFLAG_EXTERNAL }, + { "WORKDAY", "WORKDAY", 471, NOID, 2, 3, V, { RR, RR, RX, C }, FUNCFLAG_EXTERNAL }, + { "NETWORKDAYS", "NETWORKDAYS", 472, NOID, 2, 3, V, { RR, RR, RX, C }, FUNCFLAG_EXTERNAL }, + { "GCD", "GCD", 473, NOID, 1, MX, V, { RX }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "MULTINOMIAL", "MULTINOMIAL", 474, NOID, 1, MX, V, { RX }, FUNCFLAG_EXTERNAL }, + { "LCM", "LCM", 475, NOID, 1, MX, V, { RX }, FUNCFLAG_EXTERNAL }, // Calc: builtin and add-in + { "FVSCHEDULE", "FVSCHEDULE", 476, NOID, 2, 2, V, { RR, RX }, FUNCFLAG_EXTERNAL }, + + // *** macro sheet commands *** + + { 0, "ACTIVATE.NEXT", 104, 104, 0, 1, V, { VR }, FUNCFLAG_MACROCMD }, // BIFF2-3: 0, BIFF4: 0-1 + { 0, "ACTIVATE.PREV", 105, 105, 0, 1, V, { VR }, FUNCFLAG_MACROCMD } // BIFF2-3: 0, BIFF4: 0-1 +}; + +/** Functions new in BIFF5/BIFF7. */ +static const FunctionData saFuncTableBiff5[] = +{ + { "WEEKDAY", "WEEKDAY", 70, 70, 1, 2, V, { VR }, 0 }, // BIFF2-4: 1, BIFF5: 1-2 + { "HLOOKUP", "HLOOKUP", 101, 101, 3, 4, V, { VV, RO, RO, VV }, 0 }, // BIFF2-4: 3, BIFF5: 3-4 + { "VLOOKUP", "VLOOKUP", 102, 102, 3, 4, V, { VV, RO, RO, VV }, 0 }, // BIFF2-4: 3, BIFF5: 3-4 + { "DAYS360", "DAYS360", 220, 220, 2, 3, V, { VR }, 0 }, // BIFF3-4: 2, BIFF5: 2-3 + { 0, "EXTERN.CALL", 255, 255, 1, MX, R, { RO_E, RO }, FUNCFLAG_EXPORTONLY }, // MACRO or EXTERNAL + { "CONCATENATE", "CONCATENATE", 336, 336, 0, MX, V, { VR }, 0 }, + { "POWER", "POWER", 337, 337, 2, 2, V, { VR }, 0 }, + { "RADIANS", "RADIANS", 342, 342, 1, 1, V, { VR }, 0 }, + { "DEGREES", "DEGREES", 343, 343, 1, 1, V, { VR }, 0 }, + { "SUBTOTAL", "SUBTOTAL", 344, 344, 2, MX, V, { VR, RO }, 0 }, + { "SUMIF", "SUMIF", 345, 345, 2, 3, V, { RO, VR, RO }, 0 }, + { "COUNTIF", "COUNTIF", 346, 346, 2, 2, V, { RO, VR }, 0 }, + { "COUNTBLANK", "COUNTBLANK", 347, 347, 1, 1, V, { RO }, 0 }, + { "ISPMT", "ISPMT", 350, 350, 4, 4, V, { VR }, 0 }, + { 0, "DATEDIF", 351, 351, 3, 3, V, { VR }, FUNCFLAG_IMPORTONLY }, // not supported in Calc + { 0, "DATESTRING", 352, 352, 1, 1, V, { VR }, FUNCFLAG_IMPORTONLY }, // not supported in Calc, missing in OOX spec + { 0, "NUMBERSTRING", 353, 353, 2, 2, V, { VR }, FUNCFLAG_IMPORTONLY }, // not supported in Calc, missing in OOX spec + { "ROMAN", "ROMAN", 354, 354, 1, 2, V, { VR }, 0 }, + + // *** EuroTool add-in *** + + { "EUROCONVERT", "EUROCONVERT", NOID, NOID, 3, 5, V, { VR }, FUNCFLAG_EUROTOOL }, + + // *** macro sheet commands *** + + { 0, "ADD.MENU", 152, 152, 2, 4, V, { VR, RO, RO, VR }, FUNCFLAG_MACROFUNC }, // BIFF3-4: 2-3, BIFF5: 2-4 + { 0, "ADD.COMMAND", 153, 153, 3, 5, V, { VR, RO, RO, RO, VR }, FUNCFLAG_MACROFUNC }, // BIFF3-4: 3-4, BIFF5: 3-5 + { 0, "ADD.CHART.AUTOFORMAT", 390, 390, 0, 2, V, { VR }, FUNCFLAG_MACROCMD }, + { 0, "ADD.LIST.ITEM", 451, 451, 0, 2, V, { VR }, FUNCFLAG_MACROCMD }, + { 0, "ACTIVE.CELL.FONT", 476, 476, 0, 14, V, { VR }, FUNCFLAG_MACROCMD } +}; + +/** Functions new in BIFF8. */ +static const FunctionData saFuncTableBiff8[] = +{ + { "GETPIVOTDATA", "GETPIVOTDATA", 358, 358, 2, MX, V, { RR, RR, VR, VR }, FUNCFLAG_IMPORTONLY | FUNCFLAG_PARAMPAIRS }, + { "HYPERLINK", "HYPERLINK", 359, 359, 1, 2, V, { VV, VO }, 0 }, + { 0, "PHONETIC", 360, 360, 1, 1, V, { RO }, FUNCFLAG_IMPORTONLY }, + { "AVERAGEA", "AVERAGEA", 361, 361, 1, MX, V, { RX }, 0 }, + { "MAXA", "MAXA", 362, 362, 1, MX, V, { RX }, 0 }, + { "MINA", "MINA", 363, 363, 1, MX, V, { RX }, 0 }, + { "STDEVPA", "STDEVPA", 364, 364, 1, MX, V, { RX }, 0 }, + { "VARPA", "VARPA", 365, 365, 1, MX, V, { RX }, 0 }, + { "STDEVA", "STDEVA", 366, 366, 1, MX, V, { RX }, 0 }, + { "VARA", "VARA", 367, 367, 1, MX, V, { RX }, 0 }, + { "COM.MICROSOFT.BAHTTEXT", "BAHTTEXT", 368, 368, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAIDAYOFWEEK", 369, 369, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAIDIGIT", 370, 370, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAIMONTHOFYEAR", 371, 371, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAINUMSOUND", 372, 372, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAINUMSTRING", 373, 373, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAISTRINGLENGTH", 374, 374, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "ISTHAIDIGIT", 375, 375, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "ROUNDBAHTDOWN", 376, 376, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "ROUNDBAHTUP", 377, 377, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "THAIYEAR", 378, 378, 1, 1, V, { VR }, FUNCFLAG_MACROCALL }, + { 0, "RTD", 379, 379, 3, 3, A, { VR, VR, RO }, 0 } +}; + +/** Functions new in OOX. */ +static const FunctionData saFuncTableOox[] = +{ + { 0, "CUBEVALUE", 380, NOID, 1, MX, V, { VR, RX }, 0 }, + { 0, "CUBEMEMBER", 381, NOID, 2, 3, V, { VR, RX, VR }, 0 }, + { 0, "CUBEMEMBERPROPERTY", 382, NOID, 3, 3, V, { VR }, 0 }, + { 0, "CUBERANKEDMEMBER", 383, NOID, 3, 4, V, { VR }, 0 }, + { 0, "CUBEKPIMEMBER", 477, NOID, 3, 4, V, { VR }, 0 }, + { 0, "CUBESET", 478, NOID, 2, 5, V, { VR, RX, VR }, 0 }, + { 0, "CUBESETCOUNT", 479, NOID, 1, 1, V, { VR }, 0 }, + { 0, "IFERROR", 480, NOID, 2, 2, V, { VO, RO }, 0 }, + { 0, "COUNTIFS", 481, NOID, 2, MX, V, { RO, VR }, FUNCFLAG_PARAMPAIRS }, + { 0, "SUMIFS", 482, NOID, 3, MX, V, { RO, RO, VR }, FUNCFLAG_PARAMPAIRS }, + { 0, "AVERAGEIF", 483, NOID, 2, 3, V, { RO, VR, RO }, 0 }, + { 0, "AVERAGEIFS", 484, NOID, 3, MX, V, { RO, RO, VR }, 0 } +}; + +/** Functions defined by OpenFormula, but not supported by Calc or by Excel. */ +static const FunctionData saFuncTableOdf[] = +{ + { "ARABIC", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "B", 0, NOID, NOID, 3, 4, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "BASE", 0, NOID, NOID, 2, 3, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "BITAND", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "BITLSHIFT", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "BITOR", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "BITRSHIFT", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "BITXOR", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "CHISQDIST", 0, NOID, NOID, 2, 3, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "CHISQINV", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "COMBINA", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "DAYS", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "DECIMAL", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "FDIST", 0, NOID, NOID, 3, 4, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "FINV", 0, NOID, NOID, 3, 3, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "FORMULA", 0, NOID, NOID, 1, 1, V, { RO }, FUNCFLAG_MACROCALLODF }, + { "GAMMA", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "GAUSS", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "IFNA", 0, NOID, NOID, 2, 2, V, { VR, RO }, FUNCFLAG_MACROCALLODF }, + { "ISFORMULA", 0, NOID, NOID, 1, 1, V, { RO }, FUNCFLAG_MACROCALLODF }, + { "ISOWEEKNUM", 0, NOID, NOID, 1, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "MUNIT", 0, NOID, NOID, 1, 1, A, { VR }, FUNCFLAG_MACROCALLODF }, + { "NUMBERVALUE", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "PDURATION", 0, NOID, NOID, 3, 3, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "PERMUTATIONA", 0, NOID, NOID, 2, 2, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "PHI", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "RRI", 0, NOID, NOID, 3, 3, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "SHEET", 0, NOID, NOID, 1, 1, V, { RO }, FUNCFLAG_MACROCALLODF }, + { "SHEETS", 0, NOID, NOID, 0, 1, V, { RO }, FUNCFLAG_MACROCALLODF }, + { "SKEWP", 0, NOID, NOID, 1, MX, V, { RX }, FUNCFLAG_MACROCALLODF }, + { "UNICHAR", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "UNICODE", 0, NOID, NOID, 1, 1, V, { VR }, FUNCFLAG_MACROCALLODF }, + { "XOR", 0, NOID, NOID, 1, MX, V, { RX }, FUNCFLAG_MACROCALLODF } +}; + +// ---------------------------------------------------------------------------- + +const sal_Unicode API_TOKEN_OPEN = '('; +const sal_Unicode API_TOKEN_CLOSE = ')'; +const sal_Unicode API_TOKEN_SEP = ';'; + +const sal_Unicode API_TOKEN_ARRAY_OPEN = '{'; +const sal_Unicode API_TOKEN_ARRAY_CLOSE = '}'; +const sal_Unicode API_TOKEN_ARRAY_ROWSEP = '|'; +const sal_Unicode API_TOKEN_ARRAY_COLSEP = ';'; + +} // namespace + +// function info parameter class iterator ===================================== + +FunctionParamInfoIterator::FunctionParamInfoIterator( const FunctionInfo& rFuncInfo ) : + mpParamInfo( rFuncInfo.mpParamInfos ), + mpParamInfoEnd( rFuncInfo.mpParamInfos + FUNCINFO_PARAMINFOCOUNT ), + mbParamPairs( rFuncInfo.mbParamPairs ) +{ + OSL_ENSURE( !mbParamPairs || (mpParamInfo + 1 < mpParamInfoEnd), + "FunctionParamInfoIterator::FunctionParamInfoIterator - expecting at least 2 infos for paired parameters" ); +} + +const FunctionParamInfo& FunctionParamInfoIterator::getParamInfo() const +{ + static const FunctionParamInfo saInvalidInfo = { FUNC_PARAM_NONE, FUNC_PARAMCONV_ORG, false }; + return mpParamInfo ? *mpParamInfo : saInvalidInfo; +} + +bool FunctionParamInfoIterator::isCalcOnlyParam() const +{ + return mpParamInfo && (mpParamInfo->meValid == FUNC_PARAM_CALCONLY); +} + +bool FunctionParamInfoIterator::isExcelOnlyParam() const +{ + return mpParamInfo && (mpParamInfo->meValid == FUNC_PARAM_EXCELONLY); +} + +FunctionParamInfoIterator& FunctionParamInfoIterator::operator++() +{ + if( mpParamInfo ) + { + // move pointer to next entry, if something explicit follows + if( (mpParamInfo + 1 < mpParamInfoEnd) && (mpParamInfo[ 1 ].meValid != FUNC_PARAM_NONE) ) + ++mpParamInfo; + // points to last info, but parameter pairs expected, move to previous info + else if( mbParamPairs ) + --mpParamInfo; + // if last parameter type is 'Excel-only' or 'Calc-only', do not repeat it + else if( isExcelOnlyParam() || isCalcOnlyParam() ) + mpParamInfo = 0; + // otherwise: repeat last parameter class + } + return *this; +} + +// function provider ========================================================== + +struct FunctionProviderImpl +{ + typedef RefMap< OUString, FunctionInfo > FuncNameMap; + typedef RefMap< sal_uInt16, FunctionInfo > FuncIdMap; + + FunctionInfoVector maFuncs; /// All function infos in one list. + FuncNameMap maOdfFuncs; /// Maps ODF function names to function data. + FuncNameMap maOoxFuncs; /// Maps OOXML function names to function data. + FuncIdMap maOobFuncs; /// Maps OOBIN function indexes to function data. + FuncIdMap maBiffFuncs; /// Maps BIFF function indexes to function data. + FuncNameMap maMacroFuncs; /// Maps macro function names to function data. + + explicit FunctionProviderImpl( FilterType eFilter, BiffType eBiff, bool bImportFilter ); + +private: + /** Creates and inserts a function info struct from the passed function data. */ + void initFunc( const FunctionData& rFuncData, sal_uInt8 nMaxParam ); + + /** Initializes the members from the passed function data list. */ + void initFuncs( + const FunctionData* pBeg, const FunctionData* pEnd, + sal_uInt8 nMaxParam, bool bImportFilter ); +}; + +// ---------------------------------------------------------------------------- + +FunctionProviderImpl::FunctionProviderImpl( FilterType eFilter, BiffType eBiff, bool bImportFilter ) +{ + OSL_ENSURE( bImportFilter, "FunctionProviderImpl::FunctionProviderImpl - need special handling for macro call functions" ); + sal_uInt8 nMaxParam = 0; + switch( eFilter ) + { + case FILTER_OOX: + nMaxParam = OOX_MAX_PARAMCOUNT; + eBiff = BIFF8; // insert all BIFF function tables, then the OOX table + break; + case FILTER_BIFF: + nMaxParam = BIFF_MAX_PARAMCOUNT; + break; + case FILTER_UNKNOWN: + OSL_ENSURE( false, "FunctionProviderImpl::FunctionProviderImpl - invalid filter type" ); + break; + } + OSL_ENSURE( eBiff != BIFF_UNKNOWN, "FunctionProviderImpl::FunctionProviderImpl - invalid BIFF type" ); + + /* Add functions supported in the current BIFF version only. Function + tables from later BIFF versions may overwrite single functions from + earlier tables. */ + if( eBiff >= BIFF2 ) + initFuncs( saFuncTableBiff2, STATIC_ARRAY_END( saFuncTableBiff2 ), nMaxParam, bImportFilter ); + if( eBiff >= BIFF3 ) + initFuncs( saFuncTableBiff3, STATIC_ARRAY_END( saFuncTableBiff3 ), nMaxParam, bImportFilter ); + if( eBiff >= BIFF4 ) + initFuncs( saFuncTableBiff4, STATIC_ARRAY_END( saFuncTableBiff4 ), nMaxParam, bImportFilter ); + if( eBiff >= BIFF5 ) + initFuncs( saFuncTableBiff5, STATIC_ARRAY_END( saFuncTableBiff5 ), nMaxParam, bImportFilter ); + if( eBiff >= BIFF8 ) + initFuncs( saFuncTableBiff8, STATIC_ARRAY_END( saFuncTableBiff8 ), nMaxParam, bImportFilter ); + if( eFilter == FILTER_OOX ) + initFuncs( saFuncTableOox, STATIC_ARRAY_END( saFuncTableOox ), nMaxParam, bImportFilter ); + initFuncs( saFuncTableOdf, STATIC_ARRAY_END( saFuncTableOdf ), nMaxParam, bImportFilter ); +} + +void FunctionProviderImpl::initFunc( const FunctionData& rFuncData, sal_uInt8 nMaxParam ) +{ + // create a function info object + FunctionInfoRef xFuncInfo( new FunctionInfo ); + if( rFuncData.mpcOdfFuncName ) + xFuncInfo->maOdfFuncName = OUString::createFromAscii( rFuncData.mpcOdfFuncName ); + if( rFuncData.mpcOoxFuncName ) + xFuncInfo->maOoxFuncName = OUString::createFromAscii( rFuncData.mpcOoxFuncName ); + + if( getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALL ) ) + { + OSL_ENSURE( xFuncInfo->maOoxFuncName.getLength() > 0, "FunctionProviderImpl::initFunc - missing OOXML function name" ); + OSL_ENSURE( !getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALLODF ), "FunctionProviderImpl::initFunc - unexpected flag FUNCFLAG_MACROCALLODF" ); + xFuncInfo->maBiffMacroName = CREATE_OUSTRING( "_xlfn." ) + xFuncInfo->maOoxFuncName; + } + else if( getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCALLODF ) ) + { + OSL_ENSURE( xFuncInfo->maOdfFuncName.getLength() > 0, "FunctionProviderImpl::initFunc - missing ODF function name" ); + xFuncInfo->maBiffMacroName = CREATE_OUSTRING( "_xlfnodf." ) + xFuncInfo->maOdfFuncName; + } + + switch( rFuncData.mnFlags & FUNCFLAG_FUNCLIBMASK ) + { + case FUNCFLAG_EUROTOOL: xFuncInfo->meFuncLibType = FUNCLIB_EUROTOOL; break; + default: xFuncInfo->meFuncLibType = FUNCLIB_UNKNOWN; + } + + xFuncInfo->mnApiOpCode = -1; + xFuncInfo->mnOobFuncId = rFuncData.mnOobFuncId; + xFuncInfo->mnBiffFuncId = rFuncData.mnBiffFuncId; + xFuncInfo->mnMinParamCount = rFuncData.mnMinParamCount; + xFuncInfo->mnMaxParamCount = (rFuncData.mnMaxParamCount == MX) ? nMaxParam : rFuncData.mnMaxParamCount; + xFuncInfo->mnRetClass = rFuncData.mnRetClass; + xFuncInfo->mpParamInfos = rFuncData.mpParamInfos; + xFuncInfo->mbParamPairs = getFlag( rFuncData.mnFlags, FUNCFLAG_PARAMPAIRS ); + xFuncInfo->mbVolatile = getFlag( rFuncData.mnFlags, FUNCFLAG_VOLATILE ); + xFuncInfo->mbExternal = getFlag( rFuncData.mnFlags, FUNCFLAG_EXTERNAL ); + bool bMacroCmd = getFlag( rFuncData.mnFlags, FUNCFLAG_MACROCMD ); + xFuncInfo->mbMacroFunc = bMacroCmd || getFlag( rFuncData.mnFlags, FUNCFLAG_MACROFUNC ); + xFuncInfo->mbVarParam = bMacroCmd || (rFuncData.mnMinParamCount != rFuncData.mnMaxParamCount) || getFlag( rFuncData.mnFlags, FUNCFLAG_ALWAYSVAR ); + + setFlag( xFuncInfo->mnOobFuncId, BIFF_TOK_FUNCVAR_CMD, bMacroCmd ); + setFlag( xFuncInfo->mnBiffFuncId, BIFF_TOK_FUNCVAR_CMD, bMacroCmd ); + + // insert the function info into the member maps + maFuncs.push_back( xFuncInfo ); + if( xFuncInfo->maOdfFuncName.getLength() > 0 ) + maOdfFuncs[ xFuncInfo->maOdfFuncName ] = xFuncInfo; + if( xFuncInfo->maOoxFuncName.getLength() > 0 ) + maOoxFuncs[ xFuncInfo->maOoxFuncName ] = xFuncInfo; + if( xFuncInfo->mnOobFuncId != NOID ) + maOobFuncs[ xFuncInfo->mnOobFuncId ] = xFuncInfo; + if( xFuncInfo->mnBiffFuncId != NOID ) + maBiffFuncs[ xFuncInfo->mnBiffFuncId ] = xFuncInfo; + if( xFuncInfo->maBiffMacroName.getLength() > 0 ) + maMacroFuncs[ xFuncInfo->maBiffMacroName ] = xFuncInfo; +} + +void FunctionProviderImpl::initFuncs( const FunctionData* pBeg, const FunctionData* pEnd, sal_uInt8 nMaxParam, bool bImportFilter ) +{ + for( const FunctionData* pIt = pBeg; pIt != pEnd; ++pIt ) + if( pIt->isSupported( bImportFilter ) ) + initFunc( *pIt, nMaxParam ); +} + +// ---------------------------------------------------------------------------- + +FunctionProvider::FunctionProvider( FilterType eFilter, BiffType eBiff, bool bImportFilter ) : + mxFuncImpl( new FunctionProviderImpl( eFilter, eBiff, bImportFilter ) ) +{ +} + +FunctionProvider::~FunctionProvider() +{ +} + +const FunctionInfo* FunctionProvider::getFuncInfoFromOdfFuncName( const OUString& rFuncName ) const +{ + return mxFuncImpl->maOdfFuncs.get( rFuncName ).get(); +} + +const FunctionInfo* FunctionProvider::getFuncInfoFromOoxFuncName( const OUString& rFuncName ) const +{ + return mxFuncImpl->maOoxFuncs.get( rFuncName ).get(); +} + +const FunctionInfo* FunctionProvider::getFuncInfoFromOobFuncId( sal_uInt16 nFuncId ) const +{ + return mxFuncImpl->maOobFuncs.get( nFuncId ).get(); +} + +const FunctionInfo* FunctionProvider::getFuncInfoFromBiffFuncId( sal_uInt16 nFuncId ) const +{ + return mxFuncImpl->maBiffFuncs.get( nFuncId ).get(); +} + +const FunctionInfo* FunctionProvider::getFuncInfoFromMacroName( const OUString& rFuncName ) const +{ + return mxFuncImpl->maMacroFuncs.get( rFuncName ).get(); +} + +FunctionLibraryType FunctionProvider::getFuncLibTypeFromLibraryName( const OUString& rLibraryName ) const +{ +#define OOX_XLS_IS_LIBNAME( libname, basename ) (libname.equalsIgnoreAsciiCaseAscii( basename ".XLA" ) || libname.equalsIgnoreAsciiCaseAscii( basename ".XLAM" )) + + // the EUROTOOL add-in containing the EUROCONVERT function + if( OOX_XLS_IS_LIBNAME( rLibraryName, "EUROTOOL" ) ) + return FUNCLIB_EUROTOOL; + +#undef OOX_XLS_IS_LIBNAME + + // default: unknown library + return FUNCLIB_UNKNOWN; +} + +const FunctionInfoVector& FunctionProvider::getFuncs() const +{ + return mxFuncImpl->maFuncs; +} + +// op-code and function provider ============================================== + +struct OpCodeProviderImpl : public ApiOpCodes +{ + typedef RefMap< sal_Int32, FunctionInfo > OpCodeFuncMap; + typedef RefMap< OUString, FunctionInfo > FuncNameMap; + typedef ::std::vector< FormulaOpCodeMapEntry > OpCodeEntryVector; + + OpCodeFuncMap maOpCodeFuncs; /// Maps API function op-codes to function data. + FuncNameMap maExtProgFuncs; /// Maps programmatical API function names to function data. + OpCodeEntryVector maParserMap; /// OOXML token mapping for formula parser service. + + explicit OpCodeProviderImpl( + const FunctionInfoVector& rFuncInfos, + const Reference< XMultiServiceFactory >& rxFactory ); + +private: + typedef ::std::map< OUString, ApiToken > ApiTokenMap; + typedef Sequence< FormulaOpCodeMapEntry > OpCodeEntrySequence; + + static bool fillEntrySeq( OpCodeEntrySequence& orEntrySeq, const Reference< XFormulaOpCodeMapper >& rxMapper, sal_Int32 nMapGroup ); + static bool fillTokenMap( ApiTokenMap& orTokenMap, OpCodeEntrySequence& orEntrySeq, const Reference< XFormulaOpCodeMapper >& rxMapper, sal_Int32 nMapGroup ); + bool fillFuncTokenMaps( ApiTokenMap& orIntFuncTokenMap, ApiTokenMap& orExtFuncTokenMap, OpCodeEntrySequence& orEntrySeq, const Reference< XFormulaOpCodeMapper >& rxMapper ) const; + + static bool initOpCode( sal_Int32& ornOpCode, const OpCodeEntrySequence& rEntrySeq, sal_Int32 nSpecialId ); + bool initOpCode( sal_Int32& ornOpCode, const ApiTokenMap& rTokenMap, const OUString& rOdfName, const OUString& rOoxName ); + bool initOpCode( sal_Int32& ornOpCode, const ApiTokenMap& rTokenMap, const sal_Char* pcOdfName, const sal_Char* pcOoxName ); + bool initOpCode( sal_Int32& ornOpCode, const ApiTokenMap& rTokenMap, sal_Unicode cOdfName, sal_Unicode cOoxName ); + + bool initFuncOpCode( FunctionInfo& orFuncInfo, const ApiTokenMap& rFuncTokenMap ); + bool initFuncOpCodes( const ApiTokenMap& rIntFuncTokenMap, const ApiTokenMap& rExtFuncTokenMap, const FunctionInfoVector& rFuncInfos ); +}; + +// ---------------------------------------------------------------------------- + +OpCodeProviderImpl::OpCodeProviderImpl( const FunctionInfoVector& rFuncInfos, + const Reference< XMultiServiceFactory >& rxFactory ) +{ + if( rxFactory.is() ) try + { + Reference< XFormulaOpCodeMapper > xMapper( rxFactory->createInstance( + CREATE_OUSTRING( "com.sun.star.sheet.FormulaOpCodeMapper" ) ), UNO_QUERY_THROW ); + + // op-codes provided as attributes + OPCODE_UNKNOWN = xMapper->getOpCodeUnknown(); + OPCODE_EXTERNAL = xMapper->getOpCodeExternal(); + + using namespace ::com::sun::star::sheet::FormulaMapGroup; + using namespace ::com::sun::star::sheet::FormulaMapGroupSpecialOffset; + + OpCodeEntrySequence aEntrySeq; + ApiTokenMap aTokenMap, aExtFuncTokenMap; + bool bIsValid = + // special + fillEntrySeq( aEntrySeq, xMapper, SPECIAL ) && + initOpCode( OPCODE_PUSH, aEntrySeq, PUSH ) && + initOpCode( OPCODE_MISSING, aEntrySeq, MISSING ) && + initOpCode( OPCODE_SPACES, aEntrySeq, SPACES ) && + initOpCode( OPCODE_NAME, aEntrySeq, NAME ) && + initOpCode( OPCODE_DBAREA, aEntrySeq, DB_AREA ) && + initOpCode( OPCODE_NLR, aEntrySeq, COL_ROW_NAME ) && + initOpCode( OPCODE_MACRO, aEntrySeq, MACRO ) && + initOpCode( OPCODE_BAD, aEntrySeq, BAD ) && + initOpCode( OPCODE_NONAME, aEntrySeq, NO_NAME ) && + // separators + fillTokenMap( aTokenMap, aEntrySeq, xMapper, SEPARATORS ) && + initOpCode( OPCODE_OPEN, aTokenMap, API_TOKEN_OPEN, '(' ) && + initOpCode( OPCODE_CLOSE, aTokenMap, API_TOKEN_CLOSE, ')' ) && + initOpCode( OPCODE_SEP, aTokenMap, API_TOKEN_SEP, ',' ) && + // array separators + fillTokenMap( aTokenMap, aEntrySeq, xMapper, ARRAY_SEPARATORS ) && + initOpCode( OPCODE_ARRAY_OPEN, aTokenMap, API_TOKEN_ARRAY_OPEN, '{' ) && + initOpCode( OPCODE_ARRAY_CLOSE, aTokenMap, API_TOKEN_ARRAY_CLOSE, '}' ) && + initOpCode( OPCODE_ARRAY_ROWSEP, aTokenMap, API_TOKEN_ARRAY_ROWSEP, ';' ) && + initOpCode( OPCODE_ARRAY_COLSEP, aTokenMap, API_TOKEN_ARRAY_COLSEP, ',' ) && + // unary operators + fillTokenMap( aTokenMap, aEntrySeq, xMapper, UNARY_OPERATORS ) && + initOpCode( OPCODE_PLUS_SIGN, aTokenMap, '+', '\0' ) && // same op-code as OPCODE_ADD + initOpCode( OPCODE_MINUS_SIGN, aTokenMap, '-', '-' ) && + initOpCode( OPCODE_PERCENT, aTokenMap, '%', '%' ) && + // binary operators + fillTokenMap( aTokenMap, aEntrySeq, xMapper, BINARY_OPERATORS ) && + initOpCode( OPCODE_ADD, aTokenMap, '+', '+' ) && + initOpCode( OPCODE_SUB, aTokenMap, '-', '-' ) && + initOpCode( OPCODE_MULT, aTokenMap, '*', '*' ) && + initOpCode( OPCODE_DIV, aTokenMap, '/', '/' ) && + initOpCode( OPCODE_POWER, aTokenMap, '^', '^' ) && + initOpCode( OPCODE_CONCAT, aTokenMap, '&', '&' ) && + initOpCode( OPCODE_EQUAL, aTokenMap, '=', '=' ) && + initOpCode( OPCODE_NOT_EQUAL, aTokenMap, "<>", "<>" ) && + initOpCode( OPCODE_LESS, aTokenMap, '<', '<' ) && + initOpCode( OPCODE_LESS_EQUAL, aTokenMap, "<=", "<=" ) && + initOpCode( OPCODE_GREATER, aTokenMap, '>', '>' ) && + initOpCode( OPCODE_GREATER_EQUAL, aTokenMap, ">=", ">=" ) && + initOpCode( OPCODE_INTERSECT, aTokenMap, '!', ' ' ) && + initOpCode( OPCODE_LIST, aTokenMap, '~', ',' ) && + initOpCode( OPCODE_RANGE, aTokenMap, ':', ':' ) && + // functions + fillFuncTokenMaps( aTokenMap, aExtFuncTokenMap, aEntrySeq, xMapper ) && + initFuncOpCodes( aTokenMap, aExtFuncTokenMap, rFuncInfos ) && + initOpCode( OPCODE_DDE, aTokenMap, "DDE", 0 ); + + OSL_ENSURE( bIsValid, "OpCodeProviderImpl::OpCodeProviderImpl - opcodes not initialized" ); + (void)bIsValid; + + // OPCODE_PLUS_SIGN and OPCODE_ADD should be equal, otherwise "+" has to be passed above + OSL_ENSURE( OPCODE_PLUS_SIGN == OPCODE_ADD, "OpCodeProviderImpl::OpCodeProviderImpl - need opcode mapping for OPCODE_PLUS_SIGN" ); + } + catch( Exception& ) + { + OSL_ENSURE( false, "OpCodeProviderImpl::OpCodeProviderImpl - cannot receive formula opcode mapper" ); + } +} + +bool OpCodeProviderImpl::fillEntrySeq( OpCodeEntrySequence& orEntrySeq, + const Reference< XFormulaOpCodeMapper >& rxMapper, sal_Int32 nMapGroup ) +{ + try + { + orEntrySeq = rxMapper->getAvailableMappings( ::com::sun::star::sheet::FormulaLanguage::ODFF, nMapGroup ); + return orEntrySeq.hasElements(); + } + catch( Exception& ) + { + } + return false; +} + +bool OpCodeProviderImpl::fillTokenMap( ApiTokenMap& orTokenMap, OpCodeEntrySequence& orEntrySeq, + const Reference< XFormulaOpCodeMapper >& rxMapper, sal_Int32 nMapGroup ) +{ + orTokenMap.clear(); + if( fillEntrySeq( orEntrySeq, rxMapper, nMapGroup ) ) + { + const FormulaOpCodeMapEntry* pEntry = orEntrySeq.getConstArray(); + const FormulaOpCodeMapEntry* pEntryEnd = pEntry + orEntrySeq.getLength(); + for( ; pEntry != pEntryEnd; ++pEntry ) + orTokenMap[ pEntry->Name ] = pEntry->Token; + } + return orEntrySeq.hasElements(); +} + +bool OpCodeProviderImpl::fillFuncTokenMaps( ApiTokenMap& orIntFuncTokenMap, ApiTokenMap& orExtFuncTokenMap, OpCodeEntrySequence& orEntrySeq, const Reference< XFormulaOpCodeMapper >& rxMapper ) const +{ + orIntFuncTokenMap.clear(); + orExtFuncTokenMap.clear(); + if( fillEntrySeq( orEntrySeq, rxMapper, ::com::sun::star::sheet::FormulaMapGroup::FUNCTIONS ) ) + { + const FormulaOpCodeMapEntry* pEntry = orEntrySeq.getConstArray(); + const FormulaOpCodeMapEntry* pEntryEnd = pEntry + orEntrySeq.getLength(); + for( ; pEntry != pEntryEnd; ++pEntry ) + ((pEntry->Token.OpCode == OPCODE_EXTERNAL) ? orExtFuncTokenMap : orIntFuncTokenMap)[ pEntry->Name ] = pEntry->Token; + } + return orEntrySeq.hasElements(); +} + +bool OpCodeProviderImpl::initOpCode( sal_Int32& ornOpCode, const OpCodeEntrySequence& rEntrySeq, sal_Int32 nSpecialId ) +{ + if( (0 <= nSpecialId) && (nSpecialId < rEntrySeq.getLength()) ) + { + ornOpCode = rEntrySeq[ nSpecialId ].Token.OpCode; + return true; + } + OSL_ENSURE( false, + OStringBuffer( "OpCodeProviderImpl::initOpCode - opcode for special offset " ). + append( nSpecialId ).append( " not found" ).getStr() ); + return false; +} + +bool OpCodeProviderImpl::initOpCode( sal_Int32& ornOpCode, const ApiTokenMap& rTokenMap, const OUString& rOdfName, const OUString& rOoxName ) +{ + ApiTokenMap::const_iterator aIt = rTokenMap.find( rOdfName ); + if( aIt != rTokenMap.end() ) + { + ornOpCode = aIt->second.OpCode; + if( rOoxName.getLength() > 0 ) + { + FormulaOpCodeMapEntry aEntry; + aEntry.Name = rOoxName; + aEntry.Token.OpCode = ornOpCode; + maParserMap.push_back( aEntry ); + } + return true; + } + OSL_ENSURE( false, + OStringBuffer( "OpCodeProviderImpl::initOpCode - opcode for \"" ). + append( OUStringToOString( rOdfName, RTL_TEXTENCODING_ASCII_US ) ). + append( "\" not found" ).getStr() ); + return false; +} + +bool OpCodeProviderImpl::initOpCode( sal_Int32& ornOpCode, const ApiTokenMap& rTokenMap, const sal_Char* pcOdfName, const sal_Char* pcOoxName ) +{ + OUString aOoxName; + if( pcOoxName ) aOoxName = OUString::createFromAscii( pcOoxName ); + return initOpCode( ornOpCode, rTokenMap, OUString::createFromAscii( pcOdfName ), aOoxName ); +} + +bool OpCodeProviderImpl::initOpCode( sal_Int32& ornOpCode, const ApiTokenMap& rTokenMap, sal_Unicode cOdfName, sal_Unicode cOoxName ) +{ + OUString aOoxName; + if( cOoxName ) aOoxName = OUString( cOoxName ); + return initOpCode( ornOpCode, rTokenMap, OUString( cOdfName ), aOoxName ); +} + +bool OpCodeProviderImpl::initFuncOpCode( FunctionInfo& orFuncInfo, const ApiTokenMap& rFuncTokenMap ) +{ + bool bIsValid = false; + if( orFuncInfo.maOdfFuncName.getLength() > 0 ) + { + ApiTokenMap::const_iterator aIt = rFuncTokenMap.find( orFuncInfo.maOdfFuncName ); + if( aIt != rFuncTokenMap.end() ) + { + orFuncInfo.mnApiOpCode = aIt->second.OpCode; + bIsValid = + (orFuncInfo.mnApiOpCode >= 0) && + (orFuncInfo.mnApiOpCode != OPCODE_UNKNOWN) && + (orFuncInfo.mnApiOpCode != OPCODE_NONAME); + OSL_ENSURE( bIsValid, + OStringBuffer( "OpCodeProviderImpl::initFuncOpCode - no valid opcode for ODF function \"" ). + append( OUStringToOString( orFuncInfo.maOdfFuncName, RTL_TEXTENCODING_ASCII_US ) ). + append( '"' ).getStr() ); + + if( bIsValid && (orFuncInfo.mnApiOpCode == OPCODE_EXTERNAL) ) + { + bIsValid = (aIt->second.Data >>= orFuncInfo.maExtProgName) && (orFuncInfo.maExtProgName.getLength() > 0); + OSL_ENSURE( bIsValid, + OStringBuffer( "OpCodeProviderImpl::initFuncOpCode - no programmatical name for external function \"" ). + append( OUStringToOString( orFuncInfo.maOdfFuncName, RTL_TEXTENCODING_ASCII_US ) ). + append( '"' ).getStr() ); + } + + // add to parser map, if OOX function name exists + if( bIsValid && (orFuncInfo.maOoxFuncName.getLength() > 0) ) + { + // create the parser map entry + FormulaOpCodeMapEntry aEntry; + aEntry.Name = orFuncInfo.maOoxFuncName; + aEntry.Token = aIt->second; + maParserMap.push_back( aEntry ); + } + } + else + { + // ignore entries for functions unknown by Calc *and* by Excel + bIsValid = orFuncInfo.maOoxFuncName.getLength() == 0; + } + } + else if( orFuncInfo.mnBiffFuncId == BIFF_FUNC_EXTERNCALL ) + { + orFuncInfo.mnApiOpCode = OPCODE_EXTERNAL; + bIsValid = true; + } + else if( orFuncInfo.maOoxFuncName.getLength() > 0 ) + { + orFuncInfo.mnApiOpCode = OPCODE_BAD; + bIsValid = true; + } + + if( !bIsValid || (orFuncInfo.mnApiOpCode == OPCODE_UNKNOWN) || (orFuncInfo.mnApiOpCode < 0) ) + orFuncInfo.mnApiOpCode = OPCODE_NONAME; + return bIsValid; +} + +bool OpCodeProviderImpl::initFuncOpCodes( const ApiTokenMap& rIntFuncTokenMap, const ApiTokenMap& rExtFuncTokenMap, const FunctionInfoVector& rFuncInfos ) +{ + bool bIsValid = true; + for( FunctionInfoVector::const_iterator aIt = rFuncInfos.begin(), aEnd = rFuncInfos.end(); aIt != aEnd; ++aIt ) + { + FunctionInfoRef xFuncInfo = *aIt; + // set API opcode from ODF function name + bIsValid &= initFuncOpCode( *xFuncInfo, xFuncInfo->mbExternal ? rExtFuncTokenMap : rIntFuncTokenMap ); + // insert the function info into the maps + if( xFuncInfo->mnApiOpCode != OPCODE_NONAME ) + { + if( (xFuncInfo->mnApiOpCode == OPCODE_EXTERNAL) && (xFuncInfo->maExtProgName.getLength() > 0) ) + maExtProgFuncs[ xFuncInfo->maExtProgName ] = xFuncInfo; + else + maOpCodeFuncs[ xFuncInfo->mnApiOpCode ] = xFuncInfo; + } + } + return bIsValid; +} + +// ---------------------------------------------------------------------------- + +OpCodeProvider::OpCodeProvider( const Reference< XMultiServiceFactory >& rxFactory, + FilterType eFilter, BiffType eBiff, bool bImportFilter ) : + FunctionProvider( eFilter, eBiff, bImportFilter ), + mxOpCodeImpl( new OpCodeProviderImpl( getFuncs(), rxFactory ) ) +{ +} + +OpCodeProvider::~OpCodeProvider() +{ +} + +const ApiOpCodes& OpCodeProvider::getOpCodes() const +{ + return *mxOpCodeImpl; +} + +const FunctionInfo* OpCodeProvider::getFuncInfoFromApiToken( const ApiToken& rToken ) const +{ + const FunctionInfo* pFuncInfo = 0; + if( (rToken.OpCode == mxOpCodeImpl->OPCODE_EXTERNAL) && rToken.Data.has< OUString >() ) + pFuncInfo = mxOpCodeImpl->maExtProgFuncs.get( rToken.Data.get< OUString >() ).get(); + else if( (rToken.OpCode == mxOpCodeImpl->OPCODE_MACRO) && rToken.Data.has< OUString >() ) + pFuncInfo = getFuncInfoFromMacroName( rToken.Data.get< OUString >() ); + else if( (rToken.OpCode == mxOpCodeImpl->OPCODE_BAD) && rToken.Data.has< OUString >() ) + pFuncInfo = getFuncInfoFromOoxFuncName( rToken.Data.get< OUString >() ); + else + pFuncInfo = mxOpCodeImpl->maOpCodeFuncs.get( rToken.OpCode ).get(); + return pFuncInfo; +} + +Sequence< FormulaOpCodeMapEntry > OpCodeProvider::getOoxParserMap() const +{ + return ContainerHelper::vectorToSequence( mxOpCodeImpl->maParserMap ); +} + +// API formula parser wrapper ================================================= + +ApiParserWrapper::ApiParserWrapper( + const Reference< XMultiServiceFactory >& rxFactory, const OpCodeProvider& rOpCodeProv ) : + OpCodeProvider( rOpCodeProv ) +{ + if( rxFactory.is() ) try + { + mxParser.set( rxFactory->createInstance( CREATE_OUSTRING( "com.sun.star.sheet.FormulaParser" ) ), UNO_QUERY_THROW ); + } + catch( Exception& ) + { + } + OSL_ENSURE( mxParser.is(), "ApiParserWrapper::ApiParserWrapper - cannot create API formula parser object" ); + maParserProps.set( mxParser ); + maParserProps.setProperty( PROP_CompileEnglish, true ); + maParserProps.setProperty( PROP_FormulaConvention, ::com::sun::star::sheet::AddressConvention::XL_OOX ); + maParserProps.setProperty( PROP_IgnoreLeadingSpaces, false ); + maParserProps.setProperty( PROP_OpCodeMap, getOoxParserMap() ); +} + +ApiTokenSequence ApiParserWrapper::parseFormula( const OUString& rFormula, const CellAddress& rRefPos ) +{ + ApiTokenSequence aTokenSeq; + if( mxParser.is() ) try + { + aTokenSeq = mxParser->parseFormula( rFormula, rRefPos ); + } + catch( Exception& ) + { + } + return aTokenSeq; +} + +// formula contexts =========================================================== + +FormulaContext::FormulaContext( bool bRelativeAsOffset, bool b2dRefsAs3dRefs, bool bAllowNulChars ) : + maBaseAddress( 0, 0, 0 ), + mbRelativeAsOffset( bRelativeAsOffset ), + mb2dRefsAs3dRefs( b2dRefsAs3dRefs ), + mbAllowNulChars( bAllowNulChars ) +{ +} + +FormulaContext::~FormulaContext() +{ +} + +void FormulaContext::setSharedFormula( const CellAddress& ) +{ +} + +// ---------------------------------------------------------------------------- + +TokensFormulaContext::TokensFormulaContext( bool bRelativeAsOffset, bool b2dRefsAs3dRefs, bool bAllowNulChars ) : + FormulaContext( bRelativeAsOffset, b2dRefsAs3dRefs, bAllowNulChars ) +{ +} + +void TokensFormulaContext::setTokens( const ApiTokenSequence& rTokens ) +{ + maTokens = rTokens; +} + +// ---------------------------------------------------------------------------- + +SimpleFormulaContext::SimpleFormulaContext( const Reference< XFormulaTokens >& rxTokens, + bool bRelativeAsOffset, bool b2dRefsAs3dRefs, bool bAllowNulChars ) : + FormulaContext( bRelativeAsOffset, b2dRefsAs3dRefs, bAllowNulChars ), + mxTokens( rxTokens ) +{ + OSL_ENSURE( mxTokens.is(), "SimpleFormulaContext::SimpleFormulaContext - missing XFormulaTokens interface" ); +} + +void SimpleFormulaContext::setTokens( const ApiTokenSequence& rTokens ) +{ + mxTokens->setTokens( rTokens ); +} + +// formula parser/printer base class for filters ============================== + +namespace { + +bool lclConvertToCellAddress( CellAddress& orAddress, const SingleReference& rSingleRef, sal_Int32 nForbiddenFlags, sal_Int32 nFilterBySheet ) +{ + orAddress = CellAddress( static_cast< sal_Int16 >( rSingleRef.Sheet ), + rSingleRef.Column, rSingleRef.Row ); + return + !getFlag( rSingleRef.Flags, nForbiddenFlags ) && + ((nFilterBySheet < 0) || (nFilterBySheet == rSingleRef.Sheet)); +} + +bool lclConvertToCellRange( CellRangeAddress& orRange, const ComplexReference& rComplexRef, sal_Int32 nForbiddenFlags, sal_Int32 nFilterBySheet ) +{ + orRange = CellRangeAddress( static_cast< sal_Int16 >( rComplexRef.Reference1.Sheet ), + rComplexRef.Reference1.Column, rComplexRef.Reference1.Row, + rComplexRef.Reference2.Column, rComplexRef.Reference2.Row ); + return + !getFlag( rComplexRef.Reference1.Flags, nForbiddenFlags ) && + !getFlag( rComplexRef.Reference2.Flags, nForbiddenFlags ) && + (rComplexRef.Reference1.Sheet == rComplexRef.Reference2.Sheet) && + ((nFilterBySheet < 0) || (nFilterBySheet == rComplexRef.Reference1.Sheet)); +} + +enum TokenToRangeListState { STATE_REF, STATE_SEP, STATE_OPEN, STATE_CLOSE, STATE_ERROR }; + +TokenToRangeListState lclProcessRef( ApiCellRangeList& orRanges, const Any& rData, bool bAllowRelative, sal_Int32 nFilterBySheet ) +{ + using namespace ::com::sun::star::sheet::ReferenceFlags; + const sal_Int32 FORBIDDEN_FLAGS_DEL = COLUMN_DELETED | ROW_DELETED | SHEET_DELETED; + const sal_Int32 FORBIDDEN_FLAGS_REL = FORBIDDEN_FLAGS_DEL | COLUMN_RELATIVE | ROW_RELATIVE | SHEET_RELATIVE | RELATIVE_NAME; + + sal_Int32 nForbiddenFlags = bAllowRelative ? FORBIDDEN_FLAGS_DEL : FORBIDDEN_FLAGS_REL; + SingleReference aSingleRef; + if( rData >>= aSingleRef ) + { + CellAddress aAddress; + // ignore invalid addresses (with #REF! errors), but do not stop parsing + if( lclConvertToCellAddress( aAddress, aSingleRef, nForbiddenFlags, nFilterBySheet ) ) + orRanges.push_back( CellRangeAddress( aAddress.Sheet, aAddress.Column, aAddress.Row, aAddress.Column, aAddress.Row ) ); + return STATE_REF; + } + ComplexReference aComplexRef; + if( rData >>= aComplexRef ) + { + CellRangeAddress aRange; + // ignore invalid ranges (with #REF! errors), but do not stop parsing + if( lclConvertToCellRange( aRange, aComplexRef, nForbiddenFlags, nFilterBySheet ) ) + orRanges.push_back( aRange ); + return STATE_REF; + } + return STATE_ERROR; +} + +TokenToRangeListState lclProcessOpen( sal_Int32& ornParenLevel ) +{ + ++ornParenLevel; + return STATE_OPEN; +} + +TokenToRangeListState lclProcessClose( sal_Int32& ornParenLevel ) +{ + --ornParenLevel; + return (ornParenLevel >= 0) ? STATE_CLOSE : STATE_ERROR; +} + +} // namespace + +// ---------------------------------------------------------------------------- + +FormulaProcessorBase::FormulaProcessorBase( const WorkbookHelper& rHelper ) : + OpCodeProvider( rHelper.getDocumentFactory(), rHelper.getFilterType(), rHelper.getBiff(), rHelper.getBaseFilter().isImportFilter() ), + ApiOpCodes( getOpCodes() ), + WorkbookHelper( rHelper ) +{ +} + +// ---------------------------------------------------------------------------- + +OUString FormulaProcessorBase::generateAddress2dString( const CellAddress& rAddress, bool bAbsolute ) +{ + return generateAddress2dString( BinAddress( rAddress ), bAbsolute ); +} + +OUString FormulaProcessorBase::generateAddress2dString( const BinAddress& rAddress, bool bAbsolute ) +{ + OUStringBuffer aBuffer; + // column + for( sal_Int32 nTemp = rAddress.mnCol; nTemp >= 0; (nTemp /= 26) -= 1 ) + aBuffer.insert( 0, sal_Unicode( 'A' + (nTemp % 26) ) ); + if( bAbsolute ) + aBuffer.insert( 0, sal_Unicode( '$' ) ); + // row + if( bAbsolute ) + aBuffer.append( sal_Unicode( '$' ) ); + aBuffer.append( static_cast< sal_Int32 >( rAddress.mnRow + 1 ) ); + return aBuffer.makeStringAndClear(); +} + +OUString FormulaProcessorBase::generateRange2dString( const CellRangeAddress& rRange, bool bAbsolute ) +{ + return generateRange2dString( BinRange( rRange ), bAbsolute ); +} + +OUString FormulaProcessorBase::generateRange2dString( const BinRange& rRange, bool bAbsolute ) +{ + OUStringBuffer aBuffer( generateAddress2dString( rRange.maFirst, bAbsolute ) ); + if( (rRange.getColCount() > 1) || (rRange.getRowCount() > 1) ) + aBuffer.append( sal_Unicode( ':' ) ).append( generateAddress2dString( rRange.maLast, bAbsolute ) ); + return aBuffer.makeStringAndClear(); +} + +OUString FormulaProcessorBase::generateRangeList2dString( const ApiCellRangeList& rRanges, + bool bAbsolute, sal_Unicode cSeparator, bool bEncloseMultiple ) +{ + OUStringBuffer aBuffer; + for( ApiCellRangeList::const_iterator aIt = rRanges.begin(), aEnd = rRanges.end(); aIt != aEnd; ++aIt ) + { + if( aBuffer.getLength() > 0 ) + aBuffer.append( cSeparator ); + aBuffer.append( generateRange2dString( *aIt, bAbsolute ) ); + } + if( bEncloseMultiple && (rRanges.size() > 1) ) + aBuffer.insert( 0, sal_Unicode( '(' ) ).append( sal_Unicode( ')' ) ); + return aBuffer.makeStringAndClear(); +} + +// ---------------------------------------------------------------------------- + +OUString FormulaProcessorBase::generateApiAddressString( const CellAddress& rAddress ) const +{ + OUString aCellName; + PropertySet aCellProp( getCellFromDoc( rAddress ) ); + aCellProp.getProperty( aCellName, PROP_AbsoluteName ); + OSL_ENSURE( aCellName.getLength() > 0, "FormulaProcessorBase::generateApiAddressString - cannot create cell address string" ); + return aCellName; +} + +OUString FormulaProcessorBase::generateApiRangeString( const CellRangeAddress& rRange ) const +{ + OUString aRangeName; + PropertySet aRangeProp( getCellRangeFromDoc( rRange ) ); + aRangeProp.getProperty( aRangeName, PROP_AbsoluteName ); + OSL_ENSURE( aRangeName.getLength() > 0, "FormulaProcessorBase::generateApiRangeString - cannot create cell range string" ); + return aRangeName; +} + +OUString FormulaProcessorBase::generateApiRangeListString( const ApiCellRangeList& rRanges ) const +{ + OUStringBuffer aBuffer; + for( ApiCellRangeList::const_iterator aIt = rRanges.begin(), aEnd = rRanges.end(); aIt != aEnd; ++aIt ) + { + OUString aRangeName = generateApiRangeString( *aIt ); + if( aRangeName.getLength() > 0 ) + { + if( aBuffer.getLength() > 0 ) + aBuffer.append( API_TOKEN_SEP ); + aBuffer.append( aRangeName ); + } + } + return aBuffer.makeStringAndClear(); +} + +OUString FormulaProcessorBase::generateApiString( const OUString& rString ) +{ + OUString aRetString = rString; + sal_Int32 nQuotePos = aRetString.getLength(); + while( (nQuotePos = aRetString.lastIndexOf( '"', nQuotePos )) >= 0 ) + aRetString = aRetString.replaceAt( nQuotePos, 1, CREATE_OUSTRING( "\"\"" ) ); + return OUStringBuffer().append( sal_Unicode( '"' ) ).append( aRetString ).append( sal_Unicode( '"' ) ).makeStringAndClear(); +} + +OUString FormulaProcessorBase::generateApiArray( const Matrix< Any >& rMatrix ) +{ + OSL_ENSURE( !rMatrix.empty(), "FormulaProcessorBase::generateApiArray - missing matrix values" ); + OUStringBuffer aBuffer; + aBuffer.append( API_TOKEN_ARRAY_OPEN ); + for( size_t nRow = 0, nHeight = rMatrix.height(); nRow < nHeight; ++nRow ) + { + if( nRow > 0 ) + aBuffer.append( API_TOKEN_ARRAY_ROWSEP ); + for( Matrix< Any >::const_iterator aBeg = rMatrix.row_begin( nRow ), aIt = aBeg, aEnd = rMatrix.row_end( nRow ); aIt != aEnd; ++aIt ) + { + double fValue = 0.0; + OUString aString; + if( aIt != aBeg ) + aBuffer.append( API_TOKEN_ARRAY_COLSEP ); + if( *aIt >>= fValue ) + aBuffer.append( fValue ); + else if( *aIt >>= aString ) + aBuffer.append( generateApiString( aString ) ); + else + aBuffer.appendAscii( "\"\"" ); + } + } + aBuffer.append( API_TOKEN_ARRAY_CLOSE ); + return aBuffer.makeStringAndClear(); +} + +// ---------------------------------------------------------------------------- + +Any FormulaProcessorBase::extractReference( const ApiTokenSequence& rTokens ) const +{ + ApiTokenIterator aTokenIt( rTokens, OPCODE_SPACES, true ); + if( aTokenIt.is() && (aTokenIt->OpCode == OPCODE_PUSH) ) + { + Any aRefAny = aTokenIt->Data; + if( !(++aTokenIt).is() && (aRefAny.has< SingleReference >() || aRefAny.has< ComplexReference >()) ) + return aRefAny; + } + return Any(); +} + +bool FormulaProcessorBase::extractCellAddress( CellAddress& orAddress, + const ApiTokenSequence& rTokens, bool bAllowRelative ) const +{ + CellRangeAddress aRange; + if( extractCellRange( aRange, rTokens, bAllowRelative ) && (aRange.StartColumn == aRange.EndColumn) && (aRange.StartRow == aRange.EndRow) ) + { + orAddress.Sheet = aRange.Sheet; + orAddress.Column = aRange.StartColumn; + orAddress.Row = aRange.StartRow; + return true; + } + return false; +} + +bool FormulaProcessorBase::extractCellRange( CellRangeAddress& orRange, + const ApiTokenSequence& rTokens, bool bAllowRelative ) const +{ + ApiCellRangeList aRanges; + lclProcessRef( aRanges, extractReference( rTokens ), bAllowRelative, -1 ); + if( !aRanges.empty() ) + { + orRange = aRanges.front(); + return true; + } + return false; +} + +void FormulaProcessorBase::extractCellRangeList( ApiCellRangeList& orRanges, + const ApiTokenSequence& rTokens, bool bAllowRelative, sal_Int32 nFilterBySheet ) const +{ + orRanges.clear(); + TokenToRangeListState eState = STATE_OPEN; + sal_Int32 nParenLevel = 0; + for( ApiTokenIterator aIt( rTokens, OPCODE_SPACES, true ); aIt.is() && (eState != STATE_ERROR); ++aIt ) + { + sal_Int32 nOpCode = aIt->OpCode; + switch( eState ) + { + // #i107275# accept OPCODE_SEP and OPCODE_LIST as separator token + case STATE_REF: + if( nOpCode == OPCODE_SEP ) eState = STATE_SEP; + else if( nOpCode == OPCODE_LIST ) eState = STATE_SEP; + else if( nOpCode == OPCODE_CLOSE ) eState = lclProcessClose( nParenLevel ); + else eState = STATE_ERROR; + break; + case STATE_SEP: + if( nOpCode == OPCODE_PUSH ) eState = lclProcessRef( orRanges, aIt->Data, bAllowRelative, nFilterBySheet ); + else if( nOpCode == OPCODE_SEP ) eState = STATE_SEP; + else if( nOpCode == OPCODE_LIST ) eState = STATE_SEP; + else if( nOpCode == OPCODE_OPEN ) eState = lclProcessOpen( nParenLevel ); + else if( nOpCode == OPCODE_CLOSE ) eState = lclProcessClose( nParenLevel ); + else eState = STATE_ERROR; + break; + case STATE_OPEN: + if( nOpCode == OPCODE_PUSH ) eState = lclProcessRef( orRanges, aIt->Data, bAllowRelative, nFilterBySheet ); + else if( nOpCode == OPCODE_SEP ) eState = STATE_SEP; + else if( nOpCode == OPCODE_LIST ) eState = STATE_SEP; + else if( nOpCode == OPCODE_OPEN ) eState = lclProcessOpen( nParenLevel ); + else if( nOpCode == OPCODE_CLOSE ) eState = lclProcessClose( nParenLevel ); + else eState = STATE_ERROR; + break; + case STATE_CLOSE: + if( nOpCode == OPCODE_SEP ) eState = STATE_SEP; + else if( nOpCode == OPCODE_LIST ) eState = STATE_SEP; + else if( nOpCode == OPCODE_CLOSE ) eState = lclProcessClose( nParenLevel ); + else eState = STATE_ERROR; + break; + default:; + } + } + + if( eState == STATE_ERROR ) + orRanges.clear(); + else + getAddressConverter().validateCellRangeList( orRanges, false ); +} + +bool FormulaProcessorBase::extractString( OUString& orString, const ApiTokenSequence& rTokens ) const +{ + ApiTokenIterator aTokenIt( rTokens, OPCODE_SPACES, true ); + return aTokenIt.is() && (aTokenIt->OpCode == OPCODE_PUSH) && (aTokenIt->Data >>= orString) && !(++aTokenIt).is(); +} + +void FormulaProcessorBase::convertStringToStringList( + ApiTokenSequence& orTokens, sal_Unicode cStringSep, bool bTrimLeadingSpaces ) const +{ + OUString aString; + if( extractString( aString, orTokens ) && (aString.getLength() > 0) ) + { + ::std::vector< ApiToken > aNewTokens; + sal_Int32 nPos = 0; + sal_Int32 nLen = aString.getLength(); + while( (0 <= nPos) && (nPos < nLen) ) + { + OUString aEntry = aString.getToken( 0, cStringSep, nPos ); + if( bTrimLeadingSpaces ) + { + sal_Int32 nStart = 0; + while( (nStart < aEntry.getLength()) && (aEntry[ nStart ] == ' ') ) ++nStart; + aEntry = aEntry.copy( nStart ); + } + if( !aNewTokens.empty() ) + aNewTokens.push_back( ApiToken( OPCODE_SEP, Any() ) ); + aNewTokens.push_back( ApiToken( OPCODE_PUSH, Any( aEntry ) ) ); + } + orTokens = ContainerHelper::vectorToSequence( aNewTokens ); + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/formulaparser.cxx b/oox/source/xls/formulaparser.cxx new file mode 100644 index 000000000000..12deadecd695 --- /dev/null +++ b/oox/source/xls/formulaparser.cxx @@ -0,0 +1,2861 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/formulaparser.hxx" +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/sheet/ComplexReference.hpp> +#include <com/sun/star/sheet/ExternalReference.hpp> +#include <com/sun/star/sheet/FormulaToken.hpp> +#include <com/sun/star/sheet/ReferenceFlags.hpp> +#include <com/sun/star/sheet/SingleReference.hpp> +#include "properties.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/defnamesbuffer.hxx" +#include "oox/xls/externallinkbuffer.hxx" +#include "oox/xls/tablebuffer.hxx" +#include "oox/xls/worksheethelper.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::ComplexReference; +using ::com::sun::star::sheet::ExternalReference; +using ::com::sun::star::sheet::SingleReference; +using ::com::sun::star::sheet::XFormulaParser; +using namespace ::com::sun::star::sheet::ReferenceFlags; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +sal_uInt16 lclReadFmlaSize( BiffInputStream& rStrm, BiffType eBiff, const sal_uInt16* pnFmlaSize ) +{ + return pnFmlaSize ? *pnFmlaSize : ((eBiff == BIFF2) ? rStrm.readuInt8() : rStrm.readuInt16()); +} + +} // namespace + +// formula finalizer ========================================================== + +FormulaFinalizer::FormulaFinalizer( const OpCodeProvider& rOpCodeProv ) : + OpCodeProvider( rOpCodeProv ), + ApiOpCodes( getOpCodes() ) +{ + maTokens.reserve( 0x2000 ); +} + +ApiTokenSequence FormulaFinalizer::finalizeTokenArray( const ApiTokenSequence& rTokens ) +{ + maTokens.clear(); + if( rTokens.hasElements() ) + { + const ApiToken* pToken = rTokens.getConstArray(); + processTokens( pToken, pToken + rTokens.getLength() ); + } + return ContainerHelper::vectorToSequence( maTokens ); +} + +const FunctionInfo* FormulaFinalizer::resolveBadFuncName( const OUString& ) const +{ + return 0; +} + +OUString FormulaFinalizer::resolveDefinedName( sal_Int32 ) const +{ + return OUString(); +} + +const FunctionInfo* FormulaFinalizer::getFunctionInfo( ApiToken& orFuncToken ) +{ + // first, try to find a regular function info from token op-code + if( const FunctionInfo* pRegFuncInfo = getFuncInfoFromApiToken( orFuncToken ) ) + return pRegFuncInfo; + + // try to recognize a function from an external library + if( (orFuncToken.OpCode == OPCODE_BAD) && orFuncToken.Data.has< OUString >() ) + { + // virtual call to resolveBadFuncName() + if( const FunctionInfo* pLibFuncInfo = resolveBadFuncName( orFuncToken.Data.get< OUString >() ) ) + { + // write function op-code to the OPCODE_BAD token + orFuncToken.OpCode = pLibFuncInfo->mnApiOpCode; + // if it is an external function, insert programmatic function name + if( (orFuncToken.OpCode == OPCODE_EXTERNAL) && (pLibFuncInfo->maExtProgName.getLength() > 0) ) + orFuncToken.Data <<= pLibFuncInfo->maExtProgName; + else + orFuncToken.Data.clear(); // clear string from OPCODE_BAD + return pLibFuncInfo; + } + } + + // no success - return null + return 0; + +} + +const FunctionInfo* FormulaFinalizer::getExternCallInfo( ApiToken& orFuncToken, const ApiToken& rECToken ) +{ + // try to resolve the passed token to a supported sheet function + if( const FunctionInfo* pFuncInfo = getFuncInfoFromApiToken( rECToken ) ) + { + orFuncToken.OpCode = pFuncInfo->mnApiOpCode; + // programmatic add-in function name + if( (pFuncInfo->mnApiOpCode == OPCODE_EXTERNAL) && (pFuncInfo->maExtProgName.getLength() > 0) ) + orFuncToken.Data <<= pFuncInfo->maExtProgName; + // name of unsupported function, convert to OPCODE_BAD to preserve the name + else if( (pFuncInfo->mnApiOpCode == OPCODE_BAD) && (pFuncInfo->maOoxFuncName.getLength() > 0) ) + orFuncToken.Data <<= pFuncInfo->maOoxFuncName; + return pFuncInfo; + } + + // macro call or unknown function name, move data to function token + if( (rECToken.OpCode == OPCODE_MACRO) || (rECToken.OpCode == OPCODE_BAD) ) + orFuncToken = rECToken; + + // defined name used as function call, convert to OPCODE_BAD to preserve the name + if( (rECToken.OpCode == OPCODE_NAME) && rECToken.Data.has< sal_Int32 >() ) + { + OUString aDefName = resolveDefinedName( rECToken.Data.get< sal_Int32 >() ); + if( aDefName.getLength() > 0 ) + { + orFuncToken.OpCode = OPCODE_BAD; + orFuncToken.Data <<= aDefName; + } + } + + return 0; +} + +void FormulaFinalizer::processTokens( const ApiToken* pToken, const ApiToken* pTokenEnd ) +{ + while( pToken < pTokenEnd ) + { + // push the current token into the vector + bool bValid = appendFinalToken( *pToken ); + // try to process a function + if( const FunctionInfo* pFuncInfo = bValid ? getFunctionInfo( maTokens.back() ) : 0 ) + pToken = processParameters( *pFuncInfo, pToken + 1, pTokenEnd ); + // otherwise, go to next token + else + ++pToken; + } +} + +const ApiToken* FormulaFinalizer::processParameters( + const FunctionInfo& rFuncInfo, const ApiToken* pToken, const ApiToken* pTokenEnd ) +{ + // remember position of the token containing the function op-code + size_t nFuncNameIdx = maTokens.size() - 1; + + // process a function, if an OPCODE_OPEN token is following + OSL_ENSURE( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_OPEN), "FormulaFinalizer::processParameters - OPCODE_OPEN expected" ); + if( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_OPEN) ) + { + // append the OPCODE_OPEN token to the vector + maTokens.append( OPCODE_OPEN ); + + // store positions of OPCODE_OPEN, parameter separators, and OPCODE_CLOSE + ParameterPosVector aParams; + pToken = findParameters( aParams, pToken, pTokenEnd ); + OSL_ENSURE( aParams.size() >= 2, "FormulaFinalizer::processParameters - missing tokens" ); + size_t nParamCount = aParams.size() - 1; + + if( (nParamCount == 1) && isEmptyParameter( aParams[ 0 ] + 1, aParams[ 1 ] ) ) + { + /* Empty pair of parentheses -> function call without parameters, + process parameter, there might be spaces between parentheses. */ + processTokens( aParams[ 0 ] + 1, aParams[ 1 ] ); + } + else + { + const FunctionInfo* pRealFuncInfo = &rFuncInfo; + ParameterPosVector::const_iterator aPosIt = aParams.begin(); + + /* Preprocess EXTERN.CALL functions. The actual function name is + contained as reference to a defined name in the first (hidden) + parameter. */ + if( rFuncInfo.mnBiffFuncId == BIFF_FUNC_EXTERNCALL ) + { + ApiToken& rFuncToken = maTokens[ nFuncNameIdx ]; + rFuncToken.OpCode = OPCODE_NONAME; + + // try to initialize function token from first parameter + if( const ApiToken* pECToken = getSingleToken( *aPosIt + 1, *(aPosIt + 1) ) ) + if( const FunctionInfo* pECFuncInfo = getExternCallInfo( rFuncToken, *pECToken ) ) + pRealFuncInfo = pECFuncInfo; + + /* On success (something has been inserted into rFuncToken), + skip the first parameter. */ + if( rFuncToken.OpCode != OPCODE_NONAME ) + { + --nParamCount; + ++aPosIt; + } + } + + // process all parameters + FunctionParamInfoIterator aParamInfoIt( *pRealFuncInfo ); + size_t nLastValidSize = maTokens.size(); + size_t nLastValidCount = 0; + for( size_t nParam = 0; nParam < nParamCount; ++nParam, ++aPosIt, ++aParamInfoIt ) + { + // add embedded Calc-only parameters + if( aParamInfoIt.isCalcOnlyParam() ) + { + appendCalcOnlyParameter( *pRealFuncInfo, nParam ); + while( aParamInfoIt.isCalcOnlyParam() ) ++aParamInfoIt; + } + + const ApiToken* pParamBegin = *aPosIt + 1; + const ApiToken* pParamEnd = *(aPosIt + 1); + bool bIsEmpty = isEmptyParameter( pParamBegin, pParamEnd ); + + if( !aParamInfoIt.isExcelOnlyParam() ) + { + // replace empty second and third parameter in IF function with zeros + if( (pRealFuncInfo->mnOobFuncId == OOBIN_FUNC_IF) && ((nParam == 1) || (nParam == 2)) && bIsEmpty ) + { + maTokens.append< double >( OPCODE_PUSH, 0.0 ); + bIsEmpty = false; + } + else + { + // process all tokens of the parameter + processTokens( pParamBegin, pParamEnd ); + } + // append parameter separator token + maTokens.append( OPCODE_SEP ); + } + + /* #84453# Update size of new token sequence with valid parameters + to be able to remove trailing optional empty parameters. */ + if( !bIsEmpty || (nParam < pRealFuncInfo->mnMinParamCount) ) + { + nLastValidSize = maTokens.size(); + nLastValidCount = nParam + 1; + } + } + + // #84453# remove trailing optional empty parameters + maTokens.resize( nLastValidSize ); + + // add trailing Calc-only parameters + if( aParamInfoIt.isCalcOnlyParam() ) + appendCalcOnlyParameter( *pRealFuncInfo, nLastValidCount ); + + // add optional parameters that are required in Calc + appendRequiredParameters( *pRealFuncInfo, nLastValidCount ); + + // remove last parameter separator token + if( maTokens.back().OpCode == OPCODE_SEP ) + maTokens.pop_back(); + } + + /* Append the OPCODE_CLOSE token to the vector, but only if there is + no OPCODE_BAD token at the end, this token already contains the + trailing closing parentheses. */ + if( (pTokenEnd - 1)->OpCode != OPCODE_BAD ) + maTokens.append( OPCODE_CLOSE ); + } + + /* Replace OPCODE_EXTERNAL with OPCODE_NONAME to get #NAME! error in cell, + if no matching add-in function was found. */ + ApiToken& rFuncNameToken = maTokens[ nFuncNameIdx ]; + if( (rFuncNameToken.OpCode == OPCODE_EXTERNAL) && !rFuncNameToken.Data.hasValue() ) + rFuncNameToken.OpCode = OPCODE_NONAME; + + return pToken; +} + +bool FormulaFinalizer::isEmptyParameter( const ApiToken* pToken, const ApiToken* pTokenEnd ) const +{ + while( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_SPACES) ) ++pToken; + if( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_MISSING) ) ++pToken; + while( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_SPACES) ) ++pToken; + return pToken == pTokenEnd; +} + +const ApiToken* FormulaFinalizer::getSingleToken( const ApiToken* pToken, const ApiToken* pTokenEnd ) const +{ + const ApiToken* pSingleToken = 0; + // skip leading whitespace tokens + while( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_SPACES) ) ++pToken; + // remember first non-whitespace token + if( pToken < pTokenEnd ) pSingleToken = pToken++; + // skip trailing whitespace tokens + while( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_SPACES) ) ++pToken; + // return null, if other non-whitespace tokens follow + return (pToken == pTokenEnd) ? pSingleToken : 0; +} + +const ApiToken* FormulaFinalizer::skipParentheses( const ApiToken* pToken, const ApiToken* pTokenEnd ) const +{ + // skip tokens between OPCODE_OPEN and OPCODE_CLOSE + OSL_ENSURE( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_OPEN), "skipParentheses - OPCODE_OPEN expected" ); + ++pToken; + while( (pToken < pTokenEnd) && (pToken->OpCode != OPCODE_CLOSE) ) + { + if( pToken->OpCode == OPCODE_OPEN ) + pToken = skipParentheses( pToken, pTokenEnd ); + else + ++pToken; + } + // skip the OPCODE_CLOSE token + OSL_ENSURE( ((pToken < pTokenEnd) && (pToken->OpCode == OPCODE_CLOSE)) || ((pTokenEnd - 1)->OpCode == OPCODE_BAD), "skipParentheses - OPCODE_CLOSE expected" ); + return (pToken < pTokenEnd) ? (pToken + 1) : pTokenEnd; +} + +const ApiToken* FormulaFinalizer::findParameters( ParameterPosVector& rParams, + const ApiToken* pToken, const ApiToken* pTokenEnd ) const +{ + // push position of OPCODE_OPEN + OSL_ENSURE( (pToken < pTokenEnd) && (pToken->OpCode == OPCODE_OPEN), "FormulaFinalizer::findParameters - OPCODE_OPEN expected" ); + rParams.push_back( pToken++ ); + + // find positions of parameter separators + while( (pToken < pTokenEnd) && (pToken->OpCode != OPCODE_CLOSE) ) + { + if( pToken->OpCode == OPCODE_OPEN ) + pToken = skipParentheses( pToken, pTokenEnd ); + else if( pToken->OpCode == OPCODE_SEP ) + rParams.push_back( pToken++ ); + else + ++pToken; + } + + // push position of OPCODE_CLOSE + OSL_ENSURE( ((pToken < pTokenEnd) && (pToken->OpCode == OPCODE_CLOSE)) || ((pTokenEnd - 1)->OpCode == OPCODE_BAD), "FormulaFinalizer::findParameters - OPCODE_CLOSE expected" ); + rParams.push_back( pToken ); + return (pToken < pTokenEnd) ? (pToken + 1) : pTokenEnd; +} + +void FormulaFinalizer::appendCalcOnlyParameter( const FunctionInfo& rFuncInfo, size_t nParam ) +{ + (void)nParam; // prevent 'unused' warning + switch( rFuncInfo.mnOobFuncId ) + { + case OOBIN_FUNC_FLOOR: + case OOBIN_FUNC_CEILING: + OSL_ENSURE( nParam == 2, "FormulaFinalizer::appendCalcOnlyParameter - unexpected parameter index" ); + maTokens.append< double >( OPCODE_PUSH, 1.0 ); + maTokens.append( OPCODE_SEP ); + break; + } +} + +void FormulaFinalizer::appendRequiredParameters( const FunctionInfo& rFuncInfo, size_t nParamCount ) +{ + switch( rFuncInfo.mnOobFuncId ) + { + case OOBIN_FUNC_WEEKNUM: + if( nParamCount == 1 ) + { + maTokens.append< double >( OPCODE_PUSH, 1.0 ); + maTokens.append( OPCODE_SEP ); + } + break; + } +} + +bool FormulaFinalizer::appendFinalToken( const ApiToken& rToken ) +{ + // replace OPCODE_MACRO without macro name with #NAME? error code + bool bValid = (rToken.OpCode != OPCODE_MACRO) || rToken.Data.hasValue(); + if( bValid ) + { + maTokens.push_back( rToken ); + } + else + { + maTokens.append( OPCODE_ARRAY_OPEN ); + maTokens.append( OPCODE_PUSH, BiffHelper::calcDoubleFromError( BIFF_ERR_NAME ) ); + maTokens.append( OPCODE_ARRAY_CLOSE ); + } + return bValid; +} + +// parser implementation base ================================================= + +class FormulaParserImpl : public FormulaFinalizer, public WorkbookHelper +{ +public: + explicit FormulaParserImpl( const FormulaParser& rParent ); + + /** Converts an XML formula string. */ + virtual void importOoxFormula( + FormulaContext& rContext, + const OUString& rFormulaString ); + + /** Imports and converts a OOBIN token array from the passed stream. */ + virtual void importOobFormula( + FormulaContext& rContext, + RecordInputStream& rStrm ); + + /** Imports and converts a BIFF token array from the passed stream. */ + virtual void importBiffFormula( + FormulaContext& rContext, + BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize ); + + /** Finalizes the passed token array after import (e.g. adjusts function + parameters) and sets the formula using the passed context. */ + void setFormula( + FormulaContext& rContext, + const ApiTokenSequence& rTokens ); + + /** Tries to resolve the passed ref-id to an OLE target URL. */ + OUString resolveOleTarget( sal_Int32 nRefId ) const; + +protected: + typedef ::std::pair< sal_Int32, bool > WhiteSpace; + typedef ::std::vector< WhiteSpace > WhiteSpaceVec; + + /** Sets the current formula context used for import. */ + inline FormulaContext& getFormulaContext() const { return *mpContext; } + + /** Sets the current formula context used for import. */ + void initializeImport( FormulaContext& rContext ); + /** Finalizes the passed token array after import. */ + void finalizeImport( const ApiTokenSequence& rTokens ); + /** Finalizes the internal token storage after import. */ + void finalizeImport(); + + /** Inserts a shared formula using the current formula context and passed base address. */ + void setSharedFormula( const BinAddress& rBaseAddr ); + + // token array ------------------------------------------------------------ + + bool resetSpaces(); + static void appendSpaces( WhiteSpaceVec& orSpaces, sal_Int32 nCount, bool bLineFeed ); + void appendLeadingSpaces( sal_Int32 nCount, bool bLineFeed ); + void appendOpeningSpaces( sal_Int32 nCount, bool bLineFeed ); + void appendClosingSpaces( sal_Int32 nCount, bool bLineFeed ); + + size_t getFormulaSize() const; + Any& appendRawToken( sal_Int32 nOpCode ); + Any& insertRawToken( sal_Int32 nOpCode, size_t nIndexFromEnd ); + size_t appendWhiteSpaceTokens( const WhiteSpaceVec* pSpaces ); + size_t insertWhiteSpaceTokens( const WhiteSpaceVec* pSpaces, size_t nIndexFromEnd ); + + size_t getOperandSize( size_t nOpCountFromEnd, size_t nOpIndex ) const; + void pushOperandSize( size_t nSize ); + size_t popOperandSize(); + + ApiToken& getOperandToken( size_t nOpCountFromEnd, size_t nOpIndex, size_t nTokenIndex ); + void removeOperand( size_t nOpCountFromEnd, size_t nOpIndex ); + void removeLastOperands( size_t nOpCountFromEnd ); + + bool pushOperandToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces = 0 ); + bool pushAnyOperandToken( const Any& rAny, sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces = 0 ); + template< typename Type > + bool pushValueOperandToken( const Type& rValue, sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces = 0 ); + template< typename Type > + inline bool pushValueOperandToken( const Type& rValue, const WhiteSpaceVec* pSpaces = 0 ) + { return pushValueOperandToken( rValue, OPCODE_PUSH, pSpaces ); } + bool pushParenthesesOperandToken( const WhiteSpaceVec* pOpeningSpaces = 0, const WhiteSpaceVec* pClosingSpaces = 0 ); + bool pushUnaryPreOperatorToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces = 0 ); + bool pushUnaryPostOperatorToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces = 0 ); + bool pushBinaryOperatorToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces = 0 ); + bool pushParenthesesOperatorToken( const WhiteSpaceVec* pOpeningSpaces = 0, const WhiteSpaceVec* pClosingSpaces = 0 ); + bool pushFunctionOperatorToken( sal_Int32 nOpCode, size_t nParamCount, const WhiteSpaceVec* pLeadingSpaces = 0, const WhiteSpaceVec* pClosingSpaces = 0 ); + bool pushFunctionOperatorToken( const FunctionInfo& rFuncInfo, size_t nParamCount, const WhiteSpaceVec* pLeadingSpaces = 0, const WhiteSpaceVec* pClosingSpaces = 0 ); + + bool pushOperand( sal_Int32 nOpCode ); + bool pushAnyOperand( const Any& rAny, sal_Int32 nOpCode ); + template< typename Type > + bool pushValueOperand( const Type& rValue, sal_Int32 nOpCode ); + template< typename Type > + inline bool pushValueOperand( const Type& rValue ) + { return pushValueOperand( rValue, OPCODE_PUSH ); } + bool pushBoolOperand( bool bValue ); + bool pushErrorOperand( double fEncodedError ); + bool pushBiffBoolOperand( sal_uInt8 nValue ); + bool pushBiffErrorOperand( sal_uInt8 nErrorCode ); + bool pushParenthesesOperand(); + bool pushReferenceOperand( const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ); + bool pushReferenceOperand( const BinComplexRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ); + template< typename Type > + bool pushReferenceOperand( const LinkSheetRange& rSheetRange, const Type& rApiRef ); + bool pushReferenceOperand( const LinkSheetRange& rSheetRange, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ); + bool pushReferenceOperand( const LinkSheetRange& rSheetRange, const BinComplexRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ); + bool pushNlrOperand( const BinSingleRef2d& rRef ); + bool pushEmbeddedRefOperand( const DefinedNameBase& rName, bool bPushBadToken ); + bool pushDefinedNameOperand( const DefinedNameRef& rxDefName ); + bool pushExternalFuncOperand( const FunctionInfo& rFuncInfo ); + bool pushDdeLinkOperand( const OUString& rDdeServer, const OUString& rDdeTopic, const OUString& rDdeItem ); + bool pushExternalNameOperand( const ExternalNameRef& rxExtName, const ExternalLink& rExtLink ); + + bool pushUnaryPreOperator( sal_Int32 nOpCode ); + bool pushUnaryPostOperator( sal_Int32 nOpCode ); + bool pushBinaryOperator( sal_Int32 nOpCode ); + bool pushParenthesesOperator(); + bool pushFunctionOperator( sal_Int32 nOpCode, size_t nParamCount ); + bool pushFunctionOperator( const FunctionInfo& rFuncInfo, size_t nParamCount ); + +private: + // reference conversion --------------------------------------------------- + + void initReference2d( SingleReference& orApiRef ) const; + void initReference3d( SingleReference& orApiRef, sal_Int32 nSheet, bool bSameSheet ) const; + void convertColRow( SingleReference& orApiRef, const BinSingleRef2d& rRef, bool bRelativeAsOffset ) const; + void convertReference( SingleReference& orApiRef, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) const; + void convertReference( ComplexReference& orApiRef, const BinSingleRef2d& rRef1, const BinSingleRef2d& rRef2, bool bDeleted, bool bRelativeAsOffset ) const; + void convertReference2d( SingleReference& orApiRef, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) const; + void convertReference2d( ComplexReference& orApiRef, const BinSingleRef2d& rRef1, const BinSingleRef2d& rRef2, bool bDeleted, bool bRelativeAsOffset ) const; + void convertReference3d( SingleReference& orApiRef, sal_Int32 nSheet, bool bSameSheet, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) const; + void convertReference3d( ComplexReference& orApiRef, const LinkSheetRange& rSheetRange, const BinSingleRef2d& rRef1, const BinSingleRef2d& rRef2, bool bDeleted, bool bRelativeAsOffset ) const; + +private: + // finalize token sequence ------------------------------------------------ + + virtual const FunctionInfo* resolveBadFuncName( const OUString& rTokenData ) const; + virtual ::rtl::OUString resolveDefinedName( sal_Int32 nTokenIndex ) const; + +protected: + const sal_Int32 mnMaxApiCol; /// Maximum column index in own document. + const sal_Int32 mnMaxApiRow; /// Maximum row index in own document. + const sal_Int32 mnMaxXlsCol; /// Maximum column index in imported document. + const sal_Int32 mnMaxXlsRow; /// Maximum row index in imported document. + +private: + typedef ::std::vector< size_t > SizeTypeVector; + + ApiTokenVector maTokenStorage; /// Raw unordered token storage. + SizeTypeVector maTokenIndexes; /// Indexes into maTokenStorage. + SizeTypeVector maOperandSizeStack; /// Stack with token sizes per operand. + WhiteSpaceVec maLeadingSpaces; /// List of whitespaces before next token. + WhiteSpaceVec maOpeningSpaces; /// List of whitespaces before opening parenthesis. + WhiteSpaceVec maClosingSpaces; /// List of whitespaces before closing parenthesis. + FormulaContext* mpContext; /// Current formula context. +}; + +// ---------------------------------------------------------------------------- + +FormulaParserImpl::FormulaParserImpl( const FormulaParser& rParent ) : + FormulaFinalizer( rParent ), + WorkbookHelper( rParent ), + mnMaxApiCol( rParent.getAddressConverter().getMaxApiAddress().Column ), + mnMaxApiRow( rParent.getAddressConverter().getMaxApiAddress().Row ), + mnMaxXlsCol( rParent.getAddressConverter().getMaxXlsAddress().Column ), + mnMaxXlsRow( rParent.getAddressConverter().getMaxXlsAddress().Row ), + mpContext( 0 ) +{ + // reserve enough space to make resize(), push_back() etc. cheap + maTokenStorage.reserve( 0x2000 ); + maTokenIndexes.reserve( 0x2000 ); + maOperandSizeStack.reserve( 256 ); + maLeadingSpaces.reserve( 256 ); + maOpeningSpaces.reserve( 256 ); + maClosingSpaces.reserve( 256 ); +} + +void FormulaParserImpl::importOoxFormula( FormulaContext&, const OUString& ) +{ + OSL_ENSURE( false, "FormulaParserImpl::importOoxFormula - not implemented" ); +} + +void FormulaParserImpl::importOobFormula( FormulaContext&, RecordInputStream& ) +{ + OSL_ENSURE( false, "FormulaParserImpl::importOobFormula - not implemented" ); +} + +void FormulaParserImpl::importBiffFormula( FormulaContext&, BiffInputStream&, const sal_uInt16* ) +{ + OSL_ENSURE( false, "FormulaParserImpl::importBiffFormula - not implemented" ); +} + +void FormulaParserImpl::setFormula( FormulaContext& rContext, const ApiTokenSequence& rTokens ) +{ + initializeImport( rContext ); + finalizeImport( rTokens ); +} + +OUString FormulaParserImpl::resolveOleTarget( sal_Int32 nRefId ) const +{ + const ExternalLink* pExtLink = getExternalLinks().getExternalLink( nRefId ).get(); + OSL_ENSURE( pExtLink && (pExtLink->getLinkType() == LINKTYPE_OLE), "FormulaParserImpl::resolveOleTarget - missing or wrong link" ); + if( pExtLink && (pExtLink->getLinkType() == LINKTYPE_OLE) ) + return getBaseFilter().getAbsoluteUrl( pExtLink->getTargetUrl() ); + return OUString(); +} + +void FormulaParserImpl::initializeImport( FormulaContext& rContext ) +{ + maTokenStorage.clear(); + maTokenIndexes.clear(); + maOperandSizeStack.clear(); + mpContext = &rContext; +} + +void FormulaParserImpl::finalizeImport( const ApiTokenSequence& rTokens ) +{ + ApiTokenSequence aFinalTokens = finalizeTokenArray( rTokens ); + if( aFinalTokens.hasElements() ) + mpContext->setTokens( aFinalTokens ); +} + +void FormulaParserImpl::finalizeImport() +{ + ApiTokenSequence aTokens( static_cast< sal_Int32 >( maTokenIndexes.size() ) ); + if( aTokens.hasElements() ) + { + ApiToken* pToken = aTokens.getArray(); + for( SizeTypeVector::const_iterator aIt = maTokenIndexes.begin(), aEnd = maTokenIndexes.end(); aIt != aEnd; ++aIt, ++pToken ) + *pToken = maTokenStorage[ *aIt ]; + } + finalizeImport( aTokens ); +} + +void FormulaParserImpl::setSharedFormula( const BinAddress& rBaseAddr ) +{ + CellAddress aApiBaseAddr; + if( getAddressConverter().convertToCellAddress( aApiBaseAddr, rBaseAddr, mpContext->getBaseAddress().Sheet, false ) ) + mpContext->setSharedFormula( aApiBaseAddr ); +} + +// token array ---------------------------------------------------------------- + +bool FormulaParserImpl::resetSpaces() +{ + maLeadingSpaces.clear(); + maOpeningSpaces.clear(); + maClosingSpaces.clear(); + return true; +} + +void FormulaParserImpl::appendSpaces( WhiteSpaceVec& orSpaces, sal_Int32 nCount, bool bLineFeed ) +{ + OSL_ENSURE( nCount >= 0, "FormulaParserImpl::appendSpaces - negative count" ); + if( nCount > 0 ) + orSpaces.push_back( WhiteSpace( nCount, bLineFeed ) ); +} + +void FormulaParserImpl::appendLeadingSpaces( sal_Int32 nCount, bool bLineFeed ) +{ + appendSpaces( maLeadingSpaces, nCount, bLineFeed ); +} + +void FormulaParserImpl::appendOpeningSpaces( sal_Int32 nCount, bool bLineFeed ) +{ + appendSpaces( maOpeningSpaces, nCount, bLineFeed ); +} + +void FormulaParserImpl::appendClosingSpaces( sal_Int32 nCount, bool bLineFeed ) +{ + appendSpaces( maClosingSpaces, nCount, bLineFeed ); +} + +size_t FormulaParserImpl::getFormulaSize() const +{ + return maTokenIndexes.size(); +} + +Any& FormulaParserImpl::appendRawToken( sal_Int32 nOpCode ) +{ + maTokenIndexes.push_back( maTokenStorage.size() ); + return maTokenStorage.append( nOpCode ); +} + +Any& FormulaParserImpl::insertRawToken( sal_Int32 nOpCode, size_t nIndexFromEnd ) +{ + maTokenIndexes.insert( maTokenIndexes.end() - nIndexFromEnd, maTokenStorage.size() ); + return maTokenStorage.append( nOpCode ); +} + +size_t FormulaParserImpl::appendWhiteSpaceTokens( const WhiteSpaceVec* pSpaces ) +{ + if( pSpaces && !pSpaces->empty() ) + for( WhiteSpaceVec::const_iterator aIt = pSpaces->begin(), aEnd = pSpaces->end(); aIt != aEnd; ++aIt ) + appendRawToken( OPCODE_SPACES ) <<= aIt->first; + return pSpaces ? pSpaces->size() : 0; +} + +size_t FormulaParserImpl::insertWhiteSpaceTokens( const WhiteSpaceVec* pSpaces, size_t nIndexFromEnd ) +{ + if( pSpaces && !pSpaces->empty() ) + for( WhiteSpaceVec::const_iterator aIt = pSpaces->begin(), aEnd = pSpaces->end(); aIt != aEnd; ++aIt ) + insertRawToken( OPCODE_SPACES, nIndexFromEnd ) <<= aIt->first; + return pSpaces ? pSpaces->size() : 0; +} + +size_t FormulaParserImpl::getOperandSize( size_t nOpCountFromEnd, size_t nOpIndex ) const +{ + OSL_ENSURE( (nOpIndex < nOpCountFromEnd) && (nOpCountFromEnd <= maOperandSizeStack.size()), + "FormulaParserImpl::getOperandSize - invalid parameters" ); + return maOperandSizeStack[ maOperandSizeStack.size() - nOpCountFromEnd + nOpIndex ]; +} + +void FormulaParserImpl::pushOperandSize( size_t nSize ) +{ + maOperandSizeStack.push_back( nSize ); +} + +size_t FormulaParserImpl::popOperandSize() +{ + OSL_ENSURE( !maOperandSizeStack.empty(), "FormulaParserImpl::popOperandSize - invalid call" ); + size_t nOpSize = maOperandSizeStack.back(); + maOperandSizeStack.pop_back(); + return nOpSize; +} + +ApiToken& FormulaParserImpl::getOperandToken( size_t nOpCountFromEnd, size_t nOpIndex, size_t nTokenIndex ) +{ + OSL_ENSURE( getOperandSize( nOpCountFromEnd, nOpIndex ) > nTokenIndex, + "FormulaParserImpl::getOperandToken - invalid parameters" ); + SizeTypeVector::const_iterator aIndexIt = maTokenIndexes.end(); + for( SizeTypeVector::const_iterator aEnd = maOperandSizeStack.end(), aIt = aEnd - nOpCountFromEnd + nOpIndex; aIt != aEnd; ++aIt ) + aIndexIt -= *aIt; + return maTokenStorage[ *(aIndexIt + nTokenIndex) ]; +} + +void FormulaParserImpl::removeOperand( size_t nOpCountFromEnd, size_t nOpIndex ) +{ + OSL_ENSURE( (nOpIndex < nOpCountFromEnd) && (nOpCountFromEnd <= maOperandSizeStack.size()), + "FormulaParserImpl::removeOperand - invalid parameters" ); + // remove indexes into token storage, but do not touch storage itself + SizeTypeVector::iterator aSizeEnd = maOperandSizeStack.end(); + SizeTypeVector::iterator aSizeIt = aSizeEnd - nOpCountFromEnd + nOpIndex; + size_t nRemainingSize = 0; + for( SizeTypeVector::iterator aIt = aSizeIt + 1; aIt != aSizeEnd; ++aIt ) + nRemainingSize += *aIt; + maTokenIndexes.erase( maTokenIndexes.end() - nRemainingSize - *aSizeIt, maTokenIndexes.end() - nRemainingSize ); + maOperandSizeStack.erase( aSizeIt ); +} + +void FormulaParserImpl::removeLastOperands( size_t nOpCountFromEnd ) +{ + for( size_t nOpIndex = 0; nOpIndex < nOpCountFromEnd; ++nOpIndex ) + removeOperand( 1, 0 ); +} + +bool FormulaParserImpl::pushOperandToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces ) +{ + size_t nSpacesSize = appendWhiteSpaceTokens( pSpaces ); + appendRawToken( nOpCode ); + pushOperandSize( nSpacesSize + 1 ); + return true; +} + +bool FormulaParserImpl::pushAnyOperandToken( const Any& rAny, sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces ) +{ + size_t nSpacesSize = appendWhiteSpaceTokens( pSpaces ); + appendRawToken( nOpCode ) = rAny; + pushOperandSize( nSpacesSize + 1 ); + return true; +} + +template< typename Type > +bool FormulaParserImpl::pushValueOperandToken( const Type& rValue, sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces ) +{ + size_t nSpacesSize = appendWhiteSpaceTokens( pSpaces ); + appendRawToken( nOpCode ) <<= rValue; + pushOperandSize( nSpacesSize + 1 ); + return true; +} + +bool FormulaParserImpl::pushParenthesesOperandToken( const WhiteSpaceVec* pOpeningSpaces, const WhiteSpaceVec* pClosingSpaces ) +{ + size_t nSpacesSize = appendWhiteSpaceTokens( pOpeningSpaces ); + appendRawToken( OPCODE_OPEN ); + nSpacesSize += appendWhiteSpaceTokens( pClosingSpaces ); + appendRawToken( OPCODE_CLOSE ); + pushOperandSize( nSpacesSize + 2 ); + return true; +} + +bool FormulaParserImpl::pushUnaryPreOperatorToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces ) +{ + bool bOk = maOperandSizeStack.size() >= 1; + if( bOk ) + { + size_t nOpSize = popOperandSize(); + size_t nSpacesSize = insertWhiteSpaceTokens( pSpaces, nOpSize ); + insertRawToken( nOpCode, nOpSize ); + pushOperandSize( nOpSize + nSpacesSize + 1 ); + } + return bOk; +} + +bool FormulaParserImpl::pushUnaryPostOperatorToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces ) +{ + bool bOk = maOperandSizeStack.size() >= 1; + if( bOk ) + { + size_t nOpSize = popOperandSize(); + size_t nSpacesSize = appendWhiteSpaceTokens( pSpaces ); + appendRawToken( nOpCode ); + pushOperandSize( nOpSize + nSpacesSize + 1 ); + } + return bOk; +} + +bool FormulaParserImpl::pushBinaryOperatorToken( sal_Int32 nOpCode, const WhiteSpaceVec* pSpaces ) +{ + bool bOk = maOperandSizeStack.size() >= 2; + if( bOk ) + { + size_t nOp2Size = popOperandSize(); + size_t nOp1Size = popOperandSize(); + size_t nSpacesSize = insertWhiteSpaceTokens( pSpaces, nOp2Size ); + insertRawToken( nOpCode, nOp2Size ); + pushOperandSize( nOp1Size + nSpacesSize + 1 + nOp2Size ); + } + return bOk; +} + +bool FormulaParserImpl::pushParenthesesOperatorToken( const WhiteSpaceVec* pOpeningSpaces, const WhiteSpaceVec* pClosingSpaces ) +{ + bool bOk = maOperandSizeStack.size() >= 1; + if( bOk ) + { + size_t nOpSize = popOperandSize(); + size_t nSpacesSize = insertWhiteSpaceTokens( pOpeningSpaces, nOpSize ); + insertRawToken( OPCODE_OPEN, nOpSize ); + nSpacesSize += appendWhiteSpaceTokens( pClosingSpaces ); + appendRawToken( OPCODE_CLOSE ); + pushOperandSize( nOpSize + nSpacesSize + 2 ); + } + return bOk; +} + +bool FormulaParserImpl::pushFunctionOperatorToken( sal_Int32 nOpCode, size_t nParamCount, const WhiteSpaceVec* pLeadingSpaces, const WhiteSpaceVec* pClosingSpaces ) +{ + /* #i70925# if there are not enough tokens available on token stack, do + not exit with error, but reduce parameter count. */ + nParamCount = ::std::min( maOperandSizeStack.size(), nParamCount ); + + // convert all parameters on stack to a single operand separated with OPCODE_SEP + bool bOk = true; + for( size_t nParam = 1; bOk && (nParam < nParamCount); ++nParam ) + bOk = pushBinaryOperatorToken( OPCODE_SEP ); + + // add function parentheses and function name + return bOk && + ((nParamCount > 0) ? pushParenthesesOperatorToken( 0, pClosingSpaces ) : pushParenthesesOperandToken( 0, pClosingSpaces )) && + pushUnaryPreOperatorToken( nOpCode, pLeadingSpaces ); +} + +bool FormulaParserImpl::pushFunctionOperatorToken( const FunctionInfo& rFuncInfo, size_t nParamCount, const WhiteSpaceVec* pLeadingSpaces, const WhiteSpaceVec* pClosingSpaces ) +{ + bool bOk = pushFunctionOperatorToken( rFuncInfo.mnApiOpCode, nParamCount, pLeadingSpaces, pClosingSpaces ); + if( bOk ) + { + // create an external add-in call for the passed built-in function + if( (rFuncInfo.mnApiOpCode == OPCODE_EXTERNAL) && (rFuncInfo.maExtProgName.getLength() > 0) ) + getOperandToken( 1, 0, 0 ).Data <<= rFuncInfo.maExtProgName; + // create a bad token with unsupported function name + else if( (rFuncInfo.mnApiOpCode == OPCODE_BAD) && (rFuncInfo.maOoxFuncName.getLength() > 0) ) + getOperandToken( 1, 0, 0 ).Data <<= rFuncInfo.maOoxFuncName; + } + return bOk; +} + +bool FormulaParserImpl::pushOperand( sal_Int32 nOpCode ) +{ + return pushOperandToken( nOpCode, &maLeadingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushAnyOperand( const Any& rAny, sal_Int32 nOpCode ) +{ + return pushAnyOperandToken( rAny, nOpCode, &maLeadingSpaces ) && resetSpaces(); +} + +template< typename Type > +bool FormulaParserImpl::pushValueOperand( const Type& rValue, sal_Int32 nOpCode ) +{ + return pushValueOperandToken( rValue, nOpCode, &maLeadingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushBoolOperand( bool bValue ) +{ + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOobFuncId( bValue ? OOBIN_FUNC_TRUE : OOBIN_FUNC_FALSE ) ) + return pushFunctionOperator( pFuncInfo->mnApiOpCode, 0 ); + return pushValueOperand< double >( bValue ? 1.0 : 0.0 ); +} + +bool FormulaParserImpl::pushErrorOperand( double fEncodedError ) +{ + // HACK: enclose all error codes into an 1x1 matrix + // start token array with opening brace and leading spaces + pushOperand( OPCODE_ARRAY_OPEN ); + size_t nOpSize = popOperandSize(); + size_t nOldArraySize = maTokenIndexes.size(); + // push a double containing the Calc error code + appendRawToken( OPCODE_PUSH ) <<= fEncodedError; + // close token array and set resulting operand size + appendRawToken( OPCODE_ARRAY_CLOSE ); + pushOperandSize( nOpSize + maTokenIndexes.size() - nOldArraySize ); + return true; +} + +bool FormulaParserImpl::pushBiffBoolOperand( sal_uInt8 nValue ) +{ + return pushBoolOperand( nValue != BIFF_TOK_BOOL_FALSE ); +} + +bool FormulaParserImpl::pushBiffErrorOperand( sal_uInt8 nErrorCode ) +{ + return pushErrorOperand( BiffHelper::calcDoubleFromError( nErrorCode ) ); +} + +bool FormulaParserImpl::pushParenthesesOperand() +{ + return pushParenthesesOperandToken( &maOpeningSpaces, &maClosingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushReferenceOperand( const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) +{ + SingleReference aApiRef; + convertReference2d( aApiRef, rRef, bDeleted, bRelativeAsOffset ); + return pushValueOperand( aApiRef ); +} + +bool FormulaParserImpl::pushReferenceOperand( const BinComplexRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) +{ + ComplexReference aApiRef; + convertReference2d( aApiRef, rRef.maRef1, rRef.maRef2, bDeleted, bRelativeAsOffset ); + return pushValueOperand( aApiRef ); +} + +template< typename Type > +bool FormulaParserImpl::pushReferenceOperand( const LinkSheetRange& rSheetRange, const Type& rApiRef ) +{ + if( rSheetRange.isExternal() ) + { + ExternalReference aApiExtRef; + aApiExtRef.Index = rSheetRange.getDocLinkIndex(); + aApiExtRef.Reference <<= rApiRef; + return pushValueOperand( aApiExtRef ); + } + return pushValueOperand( rApiRef ); +} + +bool FormulaParserImpl::pushReferenceOperand( const LinkSheetRange& rSheetRange, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) +{ + if( rSheetRange.is3dRange() ) + { + // single-cell-range over several sheets, needs to create a ComplexReference + ComplexReference aApiRef; + convertReference3d( aApiRef, rSheetRange, rRef, rRef, bDeleted, bRelativeAsOffset ); + return pushReferenceOperand( rSheetRange, aApiRef ); + } + SingleReference aApiRef; + convertReference3d( aApiRef, rSheetRange.getFirstSheet(), rSheetRange.isSameSheet(), rRef, bDeleted, bRelativeAsOffset ); + return pushReferenceOperand( rSheetRange, aApiRef ); +} + +bool FormulaParserImpl::pushReferenceOperand( const LinkSheetRange& rSheetRange, const BinComplexRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) +{ + ComplexReference aApiRef; + convertReference3d( aApiRef, rSheetRange, rRef.maRef1, rRef.maRef2, bDeleted, bRelativeAsOffset ); + return pushReferenceOperand( rSheetRange, aApiRef ); +} + +bool FormulaParserImpl::pushNlrOperand( const BinSingleRef2d& rRef ) +{ + SingleReference aApiRef; + convertReference2d( aApiRef, rRef, false, false ); + return pushValueOperand( aApiRef, OPCODE_NLR ); +} + +bool FormulaParserImpl::pushEmbeddedRefOperand( const DefinedNameBase& rName, bool bPushBadToken ) +{ + Any aRefAny = rName.getReference( mpContext->getBaseAddress() ); + if( aRefAny.hasValue() ) + return pushAnyOperand( aRefAny, OPCODE_PUSH ); + if( bPushBadToken && (rName.getModelName().getLength() > 0) && (rName.getModelName()[ 0 ] >= ' ') ) + return pushValueOperand( rName.getModelName(), OPCODE_BAD ); + return pushBiffErrorOperand( BIFF_ERR_NAME ); +} + +bool FormulaParserImpl::pushDefinedNameOperand( const DefinedNameRef& rxDefName ) +{ + if( !rxDefName || (rxDefName->getModelName().getLength() == 0) ) + return pushBiffErrorOperand( BIFF_ERR_NAME ); + if( rxDefName->isMacroFunction() ) + return pushValueOperand( rxDefName->getModelName(), OPCODE_MACRO ); + if( rxDefName->getTokenIndex() >= 0 ) + return pushValueOperand( rxDefName->getTokenIndex(), OPCODE_NAME ); + return pushEmbeddedRefOperand( *rxDefName, true ); +} + +bool FormulaParserImpl::pushExternalFuncOperand( const FunctionInfo& rFuncInfo ) +{ + return (rFuncInfo.mnApiOpCode == OPCODE_EXTERNAL) ? + pushValueOperand( rFuncInfo.maExtProgName, OPCODE_EXTERNAL ) : + pushOperand( rFuncInfo.mnApiOpCode ); +} + +bool FormulaParserImpl::pushDdeLinkOperand( const OUString& rDdeServer, const OUString& rDdeTopic, const OUString& rDdeItem ) +{ + // create the function call DDE("server";"topic";"item") + return + pushValueOperandToken( rDdeServer ) && + pushValueOperandToken( rDdeTopic ) && + pushValueOperandToken( rDdeItem ) && + pushFunctionOperator( OPCODE_DDE, 3 ); +} + +bool FormulaParserImpl::pushExternalNameOperand( const ExternalNameRef& rxExtName, const ExternalLink& rExtLink ) +{ + if( rxExtName.get() ) switch( rExtLink.getLinkType() ) + { + case LINKTYPE_INTERNAL: + case LINKTYPE_EXTERNAL: + return pushEmbeddedRefOperand( *rxExtName, false ); + + case LINKTYPE_ANALYSIS: + // TODO: need support for localized addin function names + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOoxFuncName( rxExtName->getUpcaseModelName() ) ) + return pushExternalFuncOperand( *pFuncInfo ); + break; + + case LINKTYPE_LIBRARY: + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOoxFuncName( rxExtName->getUpcaseModelName() ) ) + if( (pFuncInfo->meFuncLibType != FUNCLIB_UNKNOWN) && (pFuncInfo->meFuncLibType == rExtLink.getFuncLibraryType()) ) + return pushExternalFuncOperand( *pFuncInfo ); + break; + + case LINKTYPE_DDE: + { + OUString aDdeServer, aDdeTopic, aDdeItem; + if( rxExtName->getDdeLinkData( aDdeServer, aDdeTopic, aDdeItem ) ) + return pushDdeLinkOperand( aDdeServer, aDdeTopic, aDdeItem ); + } + break; + + default: + OSL_ENSURE( rExtLink.getLinkType() != LINKTYPE_SELF, "FormulaParserImpl::pushExternalNameOperand - invalid call" ); + } + return pushBiffErrorOperand( BIFF_ERR_NAME ); +} + +bool FormulaParserImpl::pushUnaryPreOperator( sal_Int32 nOpCode ) +{ + return pushUnaryPreOperatorToken( nOpCode, &maLeadingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushUnaryPostOperator( sal_Int32 nOpCode ) +{ + return pushUnaryPostOperatorToken( nOpCode, &maLeadingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushBinaryOperator( sal_Int32 nOpCode ) +{ + return pushBinaryOperatorToken( nOpCode, &maLeadingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushParenthesesOperator() +{ + return pushParenthesesOperatorToken( &maOpeningSpaces, &maClosingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushFunctionOperator( sal_Int32 nOpCode, size_t nParamCount ) +{ + return pushFunctionOperatorToken( nOpCode, nParamCount, &maLeadingSpaces, &maClosingSpaces ) && resetSpaces(); +} + +bool FormulaParserImpl::pushFunctionOperator( const FunctionInfo& rFuncInfo, size_t nParamCount ) +{ + return pushFunctionOperatorToken( rFuncInfo, nParamCount, &maLeadingSpaces, &maClosingSpaces ) && resetSpaces(); +} + +// reference conversion ------------------------------------------------------- + +void FormulaParserImpl::initReference2d( SingleReference& orApiRef ) const +{ + if( mpContext->is2dRefsAs3dRefs() ) + { + initReference3d( orApiRef, mpContext->getBaseAddress().Sheet, false ); + } + else + { + orApiRef.Flags = SHEET_RELATIVE; + // #i10184# absolute sheet index needed for relative references in shared formulas + orApiRef.Sheet = mpContext->getBaseAddress().Sheet; + orApiRef.RelativeSheet = 0; + } +} + +void FormulaParserImpl::initReference3d( SingleReference& orApiRef, sal_Int32 nSheet, bool bSameSheet ) const +{ + orApiRef.Flags = SHEET_3D; + if( nSheet < 0 ) + { + orApiRef.Sheet = 0; + orApiRef.Flags |= SHEET_DELETED; + } + else if( bSameSheet ) + { + OSL_ENSURE( nSheet == 0, "FormulaParserImpl::initReference3d - invalid sheet index" ); + orApiRef.Flags |= SHEET_RELATIVE; + orApiRef.RelativeSheet = 0; + } + else + { + orApiRef.Sheet = nSheet; + } +} + +void FormulaParserImpl::convertReference( SingleReference& orApiRef, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) const +{ + if( bDeleted ) + { + orApiRef.Column = 0; + orApiRef.Row = 0; + // no explicit information about whether row or column is deleted + orApiRef.Flags |= COLUMN_DELETED | ROW_DELETED; + } + else + { + // column/row indexes and flags + setFlag( orApiRef.Flags, COLUMN_RELATIVE, rRef.mbColRel ); + setFlag( orApiRef.Flags, ROW_RELATIVE, rRef.mbRowRel ); + (rRef.mbColRel ? orApiRef.RelativeColumn : orApiRef.Column) = rRef.mnCol; + (rRef.mbRowRel ? orApiRef.RelativeRow : orApiRef.Row) = rRef.mnRow; + // convert absolute indexes to relative offsets used in API + if( !bRelativeAsOffset ) + { + if( rRef.mbColRel ) + orApiRef.RelativeColumn -= mpContext->getBaseAddress().Column; + if( rRef.mbRowRel ) + orApiRef.RelativeRow -= mpContext->getBaseAddress().Row; + } + } +} + +void FormulaParserImpl::convertReference( ComplexReference& orApiRef, const BinSingleRef2d& rRef1, const BinSingleRef2d& rRef2, bool bDeleted, bool bRelativeAsOffset ) const +{ + convertReference( orApiRef.Reference1, rRef1, bDeleted, bRelativeAsOffset ); + convertReference( orApiRef.Reference2, rRef2, bDeleted, bRelativeAsOffset ); + /* Handle references to complete rows or columns (e.g. $1:$2 or C:D), + need to expand or shrink to limits of own document. */ + if( !bDeleted && !rRef1.mbColRel && !rRef2.mbColRel && (orApiRef.Reference1.Column == 0) && (orApiRef.Reference2.Column == mnMaxXlsCol) ) + orApiRef.Reference2.Column = mnMaxApiCol; + if( !bDeleted && !rRef1.mbRowRel && !rRef2.mbRowRel && (orApiRef.Reference1.Row == 0) && (orApiRef.Reference2.Row == mnMaxXlsRow) ) + orApiRef.Reference2.Row = mnMaxApiRow; +} + +void FormulaParserImpl::convertReference2d( SingleReference& orApiRef, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) const +{ + initReference2d( orApiRef ); + convertReference( orApiRef, rRef, bDeleted, bRelativeAsOffset ); +} + +void FormulaParserImpl::convertReference2d( ComplexReference& orApiRef, const BinSingleRef2d& rRef1, const BinSingleRef2d& rRef2, bool bDeleted, bool bRelativeAsOffset ) const +{ + initReference2d( orApiRef.Reference1 ); + initReference2d( orApiRef.Reference2 ); + convertReference( orApiRef, rRef1, rRef2, bDeleted, bRelativeAsOffset ); + // remove sheet name from second part of reference + setFlag( orApiRef.Reference2.Flags, SHEET_3D, false ); +} + +void FormulaParserImpl::convertReference3d( SingleReference& orApiRef, sal_Int32 nSheet, bool bSameSheet, const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) const +{ + initReference3d( orApiRef, nSheet, bSameSheet ); + convertReference( orApiRef, rRef, bDeleted, bRelativeAsOffset ); +} + +void FormulaParserImpl::convertReference3d( ComplexReference& orApiRef, const LinkSheetRange& rSheetRange, const BinSingleRef2d& rRef1, const BinSingleRef2d& rRef2, bool bDeleted, bool bRelativeAsOffset ) const +{ + bool bSameSheet = rSheetRange.isSameSheet(); + initReference3d( orApiRef.Reference1, rSheetRange.getFirstSheet(), bSameSheet ); + initReference3d( orApiRef.Reference2, rSheetRange.getLastSheet(), bSameSheet ); + convertReference( orApiRef, rRef1, rRef2, bDeleted, bRelativeAsOffset ); + // remove sheet name from second part of reference + setFlag( orApiRef.Reference2.Flags, SHEET_3D, rSheetRange.is3dRange() ); +} + +// finalize token sequence ---------------------------------------------------- + +const FunctionInfo* FormulaParserImpl::resolveBadFuncName( const OUString& rTokenData ) const +{ + /* Try to parse calls to library functions. The format of such a function + call is "[n]!funcname", n>0 being the link identifier of the function + library spreadsheet file. */ + sal_Int32 nBracketOpen = rTokenData.indexOf( '[' ); + sal_Int32 nBracketClose = rTokenData.indexOf( ']' ); + sal_Int32 nExclamation = rTokenData.indexOf( '!' ); + if( (0 == nBracketOpen) && (nBracketOpen + 1 < nBracketClose) && (nBracketClose + 1 == nExclamation) && (nExclamation + 1 < rTokenData.getLength()) ) + { + sal_Int32 nRefId = rTokenData.copy( nBracketOpen + 1, nBracketClose - nBracketOpen - 1 ).toInt32(); + const ExternalLink* pExtLink = getExternalLinks().getExternalLink( nRefId ).get(); + if( pExtLink && (pExtLink->getLinkType() == LINKTYPE_LIBRARY) ) + { + OUString aFuncName = rTokenData.copy( nExclamation + 1 ).toAsciiUpperCase(); + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOoxFuncName( aFuncName ) ) + if( (pFuncInfo->meFuncLibType != FUNCLIB_UNKNOWN) && (pFuncInfo->meFuncLibType == pExtLink->getFuncLibraryType()) ) + return pFuncInfo; + } + } + return 0; +} + +OUString FormulaParserImpl::resolveDefinedName( sal_Int32 nTokenIndex ) const +{ + if( const DefinedName* pDefName = getDefinedNames().getByTokenIndex( nTokenIndex ).get() ) + return pDefName->getCalcName(); + return OUString(); +} + +// OOX parser implementation ================================================== + +class OoxFormulaParserImpl : public FormulaParserImpl +{ +public: + explicit OoxFormulaParserImpl( const FormulaParser& rParent ); + + virtual void importOoxFormula( + FormulaContext& rContext, + const OUString& rFormulaString ); + + virtual void importOobFormula( + FormulaContext& rContext, + RecordInputStream& rStrm ); + +private: + // import token contents and create API formula token --------------------- + + bool importAttrToken( RecordInputStream& rStrm ); + bool importSpaceToken( RecordInputStream& rStrm ); + bool importTableToken( RecordInputStream& rStrm ); + bool importArrayToken( RecordInputStream& rStrm ); + bool importRefToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importAreaToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importRef3dToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importArea3dToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importMemAreaToken( RecordInputStream& rStrm, bool bAddData ); + bool importMemFuncToken( RecordInputStream& rStrm ); + bool importNameToken( RecordInputStream& rStrm ); + bool importNameXToken( RecordInputStream& rStrm ); + bool importFuncToken( RecordInputStream& rStrm ); + bool importFuncVarToken( RecordInputStream& rStrm ); + bool importExpToken( RecordInputStream& rStrm ); + + LinkSheetRange readSheetRange( RecordInputStream& rStrm ); + + void swapStreamPosition( RecordInputStream& rStrm ); + void skipMemAreaAddData( RecordInputStream& rStrm ); + + // convert BIN token and push API operand or operator --------------------- + + bool pushOobName( sal_Int32 nNameId ); + bool pushOobExtName( sal_Int32 nRefId, sal_Int32 nNameId ); + bool pushOobFunction( sal_uInt16 nFuncId ); + bool pushOobFunction( sal_uInt16 nFuncId, sal_uInt8 nParamCount ); + +private: + ApiParserWrapper maApiParser; /// Wrapper for the API formula parser object. + sal_Int64 mnAddDataPos; /// Current stream position for additional data (tExp, tArray, tMemArea). + bool mbNeedExtRefs; /// True = parser needs initialization of external reference info. +}; + +// ---------------------------------------------------------------------------- + +OoxFormulaParserImpl::OoxFormulaParserImpl( const FormulaParser& rParent ) : + FormulaParserImpl( rParent ), + maApiParser( rParent.getDocumentFactory(), rParent ), + mnAddDataPos( 0 ), + mbNeedExtRefs( true ) +{ +} + +void OoxFormulaParserImpl::importOoxFormula( FormulaContext& rContext, const OUString& rFormulaString ) +{ + if( mbNeedExtRefs ) + { + maApiParser.getParserProperties().setProperty( PROP_ExternalLinks, getExternalLinks().getLinkInfos() ); + mbNeedExtRefs = false; + } + initializeImport( rContext ); + finalizeImport( maApiParser.parseFormula( rFormulaString, rContext.getBaseAddress() ) ); +} + +void OoxFormulaParserImpl::importOobFormula( FormulaContext& rContext, RecordInputStream& rStrm ) +{ + initializeImport( rContext ); + + sal_Int32 nFmlaSize = rStrm.readInt32(); + sal_Int64 nFmlaPos = rStrm.tell(); + sal_Int64 nFmlaEndPos = nFmlaPos + nFmlaSize; + + rStrm.seek( nFmlaEndPos ); + sal_Int32 nAddDataSize = rStrm.readInt32(); + mnAddDataPos = rStrm.tell(); + sal_Int64 nAddDataEndPos = mnAddDataPos + nAddDataSize; + rStrm.seek( nFmlaPos ); + + bool bOk = (nFmlaSize >= 0) && (nAddDataSize >= 0); + bool bRelativeAsOffset = getFormulaContext().isRelativeAsOffset(); + + while( bOk && !rStrm.isEof() && (rStrm.tell() < nFmlaEndPos) ) + { + sal_uInt8 nTokenId; + rStrm >> nTokenId; + sal_uInt8 nTokenClass = nTokenId & BIFF_TOKCLASS_MASK; + sal_uInt8 nBaseId = nTokenId & BIFF_TOKID_MASK; + + if( nTokenClass == BIFF_TOKCLASS_NONE ) + { + // base tokens + switch( nBaseId ) + { + case BIFF_TOKID_EXP: bOk = importExpToken( rStrm ); break; + case BIFF_TOKID_ADD: bOk = pushBinaryOperator( OPCODE_ADD ); break; + case BIFF_TOKID_SUB: bOk = pushBinaryOperator( OPCODE_SUB ); break; + case BIFF_TOKID_MUL: bOk = pushBinaryOperator( OPCODE_MULT ); break; + case BIFF_TOKID_DIV: bOk = pushBinaryOperator( OPCODE_DIV ); break; + case BIFF_TOKID_POWER: bOk = pushBinaryOperator( OPCODE_POWER ); break; + case BIFF_TOKID_CONCAT: bOk = pushBinaryOperator( OPCODE_CONCAT ); break; + case BIFF_TOKID_LT: bOk = pushBinaryOperator( OPCODE_LESS ); break; + case BIFF_TOKID_LE: bOk = pushBinaryOperator( OPCODE_LESS_EQUAL ); break; + case BIFF_TOKID_EQ: bOk = pushBinaryOperator( OPCODE_EQUAL ); break; + case BIFF_TOKID_GE: bOk = pushBinaryOperator( OPCODE_GREATER_EQUAL ); break; + case BIFF_TOKID_GT: bOk = pushBinaryOperator( OPCODE_GREATER ); break; + case BIFF_TOKID_NE: bOk = pushBinaryOperator( OPCODE_NOT_EQUAL ); break; + case BIFF_TOKID_ISECT: bOk = pushBinaryOperator( OPCODE_INTERSECT ); break; + case BIFF_TOKID_LIST: bOk = pushBinaryOperator( OPCODE_LIST ); break; + case BIFF_TOKID_RANGE: bOk = pushBinaryOperator( OPCODE_RANGE ); break; + case BIFF_TOKID_UPLUS: bOk = pushUnaryPreOperator( OPCODE_PLUS_SIGN ); break; + case BIFF_TOKID_UMINUS: bOk = pushUnaryPreOperator( OPCODE_MINUS_SIGN ); break; + case BIFF_TOKID_PERCENT: bOk = pushUnaryPostOperator( OPCODE_PERCENT ); break; + case BIFF_TOKID_PAREN: bOk = pushParenthesesOperator(); break; + case BIFF_TOKID_MISSARG: bOk = pushOperand( OPCODE_MISSING ); break; + case BIFF_TOKID_STR: bOk = pushValueOperand( rStrm.readString( false ) ); break; + case BIFF_TOKID_NLR: bOk = importTableToken( rStrm ); break; + case BIFF_TOKID_ATTR: bOk = importAttrToken( rStrm ); break; + case BIFF_TOKID_ERR: bOk = pushBiffErrorOperand( rStrm.readuInt8() ); break; + case BIFF_TOKID_BOOL: bOk = pushBiffBoolOperand( rStrm.readuInt8() ); break; + case BIFF_TOKID_INT: bOk = pushValueOperand< double >( rStrm.readuInt16() ); break; + case BIFF_TOKID_NUM: bOk = pushValueOperand( rStrm.readDouble() ); break; + default: bOk = false; + } + } + else + { + // classified tokens + switch( nBaseId ) + { + case BIFF_TOKID_ARRAY: bOk = importArrayToken( rStrm ); break; + case BIFF_TOKID_FUNC: bOk = importFuncToken( rStrm ); break; + case BIFF_TOKID_FUNCVAR: bOk = importFuncVarToken( rStrm ); break; + case BIFF_TOKID_NAME: bOk = importNameToken( rStrm ); break; + case BIFF_TOKID_REF: bOk = importRefToken( rStrm, false, false ); break; + case BIFF_TOKID_AREA: bOk = importAreaToken( rStrm, false, false ); break; + case BIFF_TOKID_MEMAREA: bOk = importMemAreaToken( rStrm, true ); break; + case BIFF_TOKID_MEMERR: bOk = importMemAreaToken( rStrm, false ); break; + case BIFF_TOKID_MEMNOMEM: bOk = importMemAreaToken( rStrm, false ); break; + case BIFF_TOKID_MEMFUNC: bOk = importMemFuncToken( rStrm ); break; + case BIFF_TOKID_REFERR: bOk = importRefToken( rStrm, true, false ); break; + case BIFF_TOKID_AREAERR: bOk = importAreaToken( rStrm, true, false ); break; + case BIFF_TOKID_REFN: bOk = importRefToken( rStrm, false, true ); break; + case BIFF_TOKID_AREAN: bOk = importAreaToken( rStrm, false, true ); break; + case BIFF_TOKID_MEMAREAN: bOk = importMemFuncToken( rStrm ); break; + case BIFF_TOKID_MEMNOMEMN: bOk = importMemFuncToken( rStrm ); break; + case BIFF_TOKID_NAMEX: bOk = importNameXToken( rStrm ); break; + case BIFF_TOKID_REF3D: bOk = importRef3dToken( rStrm, false, bRelativeAsOffset ); break; + case BIFF_TOKID_AREA3D: bOk = importArea3dToken( rStrm, false, bRelativeAsOffset ); break; + case BIFF_TOKID_REFERR3D: bOk = importRef3dToken( rStrm, true, bRelativeAsOffset ); break; + case BIFF_TOKID_AREAERR3D: bOk = importArea3dToken( rStrm, true, bRelativeAsOffset ); break; + default: bOk = false; + } + } + } + + // build and finalize the token sequence + if( bOk && (rStrm.tell() == nFmlaEndPos) && (mnAddDataPos == nAddDataEndPos) ) + finalizeImport(); + + // seek behind token array + if( (nFmlaSize >= 0) && (nAddDataSize >= 0) ) + rStrm.seek( nAddDataEndPos ); +} + +// import token contents and create API formula token ------------------------- + +bool OoxFormulaParserImpl::importAttrToken( RecordInputStream& rStrm ) +{ + bool bOk = true; + sal_uInt8 nType; + rStrm >> nType; + // equal flags in BIFF and OOBIN + switch( nType ) + { + case 0: // sometimes, tAttrSkip tokens miss the type flag + case OOBIN_TOK_ATTR_VOLATILE: + case OOBIN_TOK_ATTR_IF: + case OOBIN_TOK_ATTR_SKIP: + case OOBIN_TOK_ATTR_ASSIGN: + case OOBIN_TOK_ATTR_IFERROR: + rStrm.skip( 2 ); + break; + case OOBIN_TOK_ATTR_CHOOSE: + rStrm.skip( 2 * rStrm.readuInt16() + 2 ); + break; + case OOBIN_TOK_ATTR_SUM: + rStrm.skip( 2 ); + bOk = pushOobFunction( OOBIN_FUNC_SUM, 1 ); + break; + case OOBIN_TOK_ATTR_SPACE: + case OOBIN_TOK_ATTR_SPACE_VOLATILE: + bOk = importSpaceToken( rStrm ); + break; + default: + bOk = false; + } + return bOk; +} + +bool OoxFormulaParserImpl::importSpaceToken( RecordInputStream& rStrm ) +{ + // equal constants in BIFF and OOX + sal_uInt8 nType, nCount; + rStrm >> nType >> nCount; + switch( nType ) + { + case BIFF_TOK_ATTR_SPACE_SP: + appendLeadingSpaces( nCount, false ); + break; + case BIFF_TOK_ATTR_SPACE_BR: + appendLeadingSpaces( nCount, true ); + break; + case BIFF_TOK_ATTR_SPACE_SP_OPEN: + appendOpeningSpaces( nCount, false ); + break; + case BIFF_TOK_ATTR_SPACE_BR_OPEN: + appendOpeningSpaces( nCount, true ); + break; + case BIFF_TOK_ATTR_SPACE_SP_CLOSE: + appendClosingSpaces( nCount, false ); + break; + case BIFF_TOK_ATTR_SPACE_BR_CLOSE: + appendClosingSpaces( nCount, true ); + break; + } + return true; +} + +bool OoxFormulaParserImpl::importTableToken( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags, nTableId, nCol1, nCol2; + rStrm.skip( 3 ); + rStrm >> nFlags >> nTableId; + rStrm.skip( 2 ); + rStrm >> nCol1 >> nCol2; + TableRef xTable = getTables().getTable( nTableId ); + sal_Int32 nTokenIndex = xTable.get() ? xTable->getTokenIndex() : -1; + if( nTokenIndex >= 0 ) + { + sal_Int32 nWidth = xTable->getWidth(); + sal_Int32 nHeight = xTable->getHeight(); + sal_Int32 nStartCol = 0; + sal_Int32 nEndCol = nWidth - 1; + sal_Int32 nStartRow = 0; + sal_Int32 nEndRow = nHeight - 1; + bool bFixedStartRow = true; + bool bFixedHeight = false; + + bool bSingleCol = getFlag( nFlags, OOBIN_TOK_TABLE_COLUMN ); + bool bColRange = getFlag( nFlags, OOBIN_TOK_TABLE_COLRANGE ); + bool bValidRef = !bSingleCol || !bColRange; + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - illegal combination of single column and column range" ); + if( bValidRef ) + { + if( bSingleCol ) + nStartCol = nEndCol = nCol1; + else if( bColRange ) + { nStartCol = nCol1; nEndCol = nCol2; } + bValidRef = (nStartCol <= nEndCol) && (nEndCol < nWidth); + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - invalid column range" ); + } + + if( bValidRef ) + { + bool bAllRows = getFlag( nFlags, OOBIN_TOK_TABLE_ALL ); + bool bHeaderRows = getFlag( nFlags, OOBIN_TOK_TABLE_HEADERS ); + bool bDataRows = getFlag( nFlags, OOBIN_TOK_TABLE_DATA ); + bool bTotalsRows = getFlag( nFlags, OOBIN_TOK_TABLE_TOTALS ); + bool bThisRow = getFlag( nFlags, OOBIN_TOK_TABLE_THISROW ); + + sal_Int32 nStartDataRow = xTable->getHeaderRows(); + sal_Int32 nEndDataRow = nEndRow - xTable->getTotalsRows(); + bValidRef = (nStartRow <= nStartDataRow) && (nStartDataRow <= nEndDataRow) && (nEndDataRow <= nEndRow); + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - invalid data row range" ); + if( bValidRef ) + { + if( bAllRows ) + { + bValidRef = !bHeaderRows && !bDataRows && !bTotalsRows && !bThisRow; + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - unexpected flags in [#All] table token" ); + } + else if( bHeaderRows ) + { + bValidRef = !bTotalsRows && !bThisRow; + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - unexpected flags in [#Headers] table token" ); + nEndRow = bDataRows ? nEndDataRow : (nStartDataRow - 1); + bFixedHeight = !bDataRows; + } + else if( bDataRows ) + { + bValidRef = !bThisRow; + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - unexpected flags in [#Data] table token" ); + nStartRow = nStartDataRow; + if( !bTotalsRows ) nEndRow = nEndDataRow; + } + else if( bTotalsRows ) + { + bValidRef = !bThisRow; + OSL_ENSURE( bValidRef, "OoxFormulaParserImpl::importTableToken - unexpected flags in [#Totals] table token" ); + nStartRow = nEndDataRow + 1; + bFixedStartRow = false; + bFixedHeight = !bDataRows; + } + else if( bThisRow ) + { + nStartRow = nEndRow = getFormulaContext().getBaseAddress().Row - xTable->getRange().StartRow; + bFixedHeight = true; + } + else + { + // nothing is the same as [#Data] + nStartRow = nStartDataRow; + nEndRow = nEndDataRow; + } + } + if( bValidRef ) + bValidRef = (0 <= nStartRow) && (nStartRow <= nEndRow) && (nEndRow < nHeight); + } + if( bValidRef ) + { + // push single database area token, if table token refers to entire table + if( (nStartCol == 0) && (nEndCol + 1 == nWidth) && (nStartRow == 0) && (nEndRow + 1 == nHeight) ) + return pushValueOperand( nTokenIndex, OPCODE_DBAREA ); + // create an OFFSET function call to refer to a subrange of the table + const FunctionInfo* pRowsInfo = getFuncInfoFromOobFuncId( OOBIN_FUNC_ROWS ); + const FunctionInfo* pColumnsInfo = getFuncInfoFromOobFuncId( OOBIN_FUNC_COLUMNS ); + return + pRowsInfo && pColumnsInfo && + pushValueOperandToken( nTokenIndex, OPCODE_DBAREA ) && + (bFixedStartRow ? + pushValueOperandToken< double >( nStartRow ) : + (pushValueOperandToken( nTokenIndex, OPCODE_DBAREA ) && + pushFunctionOperatorToken( *pRowsInfo, 1 ) && + pushValueOperandToken< double >( nHeight - nStartRow ) && + pushBinaryOperatorToken( OPCODE_SUB ))) && + pushValueOperandToken< double >( nStartCol ) && + (bFixedHeight ? + pushValueOperandToken< double >( nEndRow - nStartRow + 1 ) : + (pushValueOperandToken( nTokenIndex, OPCODE_DBAREA ) && + pushFunctionOperatorToken( *pRowsInfo, 1 ) && + (((nStartRow == 0) && (nEndRow + 1 == nHeight)) || + (pushValueOperandToken< double >( nHeight - (nEndRow - nStartRow + 1) ) && + pushBinaryOperatorToken( OPCODE_SUB ))))) && + (((nStartCol == 0) && (nEndCol + 1 == nWidth)) ? + (pushValueOperandToken( nTokenIndex, OPCODE_DBAREA ) && + pushFunctionOperatorToken( *pColumnsInfo, 1 )) : + pushValueOperandToken< double >( nEndCol - nStartCol + 1 )) && + pushOobFunction( OOBIN_FUNC_OFFSET, 5 ); + } + } + return pushBiffErrorOperand( BIFF_ERR_REF ); +} + +bool OoxFormulaParserImpl::importArrayToken( RecordInputStream& rStrm ) +{ + rStrm.skip( 14 ); + + // start token array with opening brace and leading spaces + pushOperand( OPCODE_ARRAY_OPEN ); + size_t nOpSize = popOperandSize(); + size_t nOldArraySize = getFormulaSize(); + + // read array size + swapStreamPosition( rStrm ); + sal_Int32 nRows = rStrm.readInt32(); + sal_Int32 nCols = rStrm.readInt32(); + OSL_ENSURE( (nCols > 0) && (nRows > 0), "OoxFormulaParserImpl::importArrayToken - empty array" ); + + // read array values and build token array + for( sal_Int32 nRow = 0; !rStrm.isEof() && (nRow < nRows); ++nRow ) + { + if( nRow > 0 ) + appendRawToken( OPCODE_ARRAY_ROWSEP ); + for( sal_Int32 nCol = 0; !rStrm.isEof() && (nCol < nCols); ++nCol ) + { + if( nCol > 0 ) + appendRawToken( OPCODE_ARRAY_COLSEP ); + switch( rStrm.readuInt8() ) + { + case OOBIN_TOK_ARRAY_DOUBLE: + appendRawToken( OPCODE_PUSH ) <<= rStrm.readDouble(); + break; + case OOBIN_TOK_ARRAY_STRING: + appendRawToken( OPCODE_PUSH ) <<= rStrm.readString( false ); + break; + case OOBIN_TOK_ARRAY_BOOL: + appendRawToken( OPCODE_PUSH ) <<= static_cast< double >( (rStrm.readuInt8() == BIFF_TOK_BOOL_FALSE) ? 0.0 : 1.0 ); + break; + case OOBIN_TOK_ARRAY_ERROR: + appendRawToken( OPCODE_PUSH ) <<= BiffHelper::calcDoubleFromError( rStrm.readuInt8() ); + rStrm.skip( 3 ); + break; + default: + OSL_ENSURE( false, "OoxFormulaParserImpl::importArrayToken - unknown data type" ); + appendRawToken( OPCODE_PUSH ) <<= BiffHelper::calcDoubleFromError( BIFF_ERR_NA ); + } + } + } + swapStreamPosition( rStrm ); + + // close token array and set resulting operand size + appendRawToken( OPCODE_ARRAY_CLOSE ); + pushOperandSize( nOpSize + getFormulaSize() - nOldArraySize ); + return true; +} + +bool OoxFormulaParserImpl::importRefToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + BinSingleRef2d aRef; + aRef.readOobData( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aRef, bDeleted, bRelativeAsOffset ); +} + +bool OoxFormulaParserImpl::importAreaToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + BinComplexRef2d aRef; + aRef.readOobData( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aRef, bDeleted, bRelativeAsOffset ); +} + +bool OoxFormulaParserImpl::importRef3dToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + LinkSheetRange aSheetRange = readSheetRange( rStrm ); + BinSingleRef2d aRef; + aRef.readOobData( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aSheetRange, aRef, bDeleted, bRelativeAsOffset ); +} + +bool OoxFormulaParserImpl::importArea3dToken( RecordInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + LinkSheetRange aSheetRange = readSheetRange( rStrm ); + BinComplexRef2d aRef; + aRef.readOobData( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aSheetRange, aRef, bDeleted, bRelativeAsOffset ); +} + +bool OoxFormulaParserImpl::importMemAreaToken( RecordInputStream& rStrm, bool bAddData ) +{ + rStrm.skip( 6 ); + if( bAddData ) + skipMemAreaAddData( rStrm ); + return true; +} + +bool OoxFormulaParserImpl::importMemFuncToken( RecordInputStream& rStrm ) +{ + rStrm.skip( 2 ); + return true; +} + +bool OoxFormulaParserImpl::importNameToken( RecordInputStream& rStrm ) +{ + return pushOobName( rStrm.readInt32() ); +} + +bool OoxFormulaParserImpl::importNameXToken( RecordInputStream& rStrm ) +{ + sal_Int32 nRefId = rStrm.readInt16(); + sal_Int32 nNameId = rStrm.readInt32(); + return pushOobExtName( nRefId, nNameId ); +} + +bool OoxFormulaParserImpl::importFuncToken( RecordInputStream& rStrm ) +{ + sal_uInt16 nFuncId; + rStrm >> nFuncId; + return pushOobFunction( nFuncId ); +} + +bool OoxFormulaParserImpl::importFuncVarToken( RecordInputStream& rStrm ) +{ + sal_uInt8 nParamCount; + sal_uInt16 nFuncId; + rStrm >> nParamCount >> nFuncId; + return pushOobFunction( nFuncId, nParamCount ); +} + +bool OoxFormulaParserImpl::importExpToken( RecordInputStream& rStrm ) +{ + BinAddress aBaseAddr; + rStrm >> aBaseAddr.mnRow; + swapStreamPosition( rStrm ); + rStrm >> aBaseAddr.mnCol; + swapStreamPosition( rStrm ); + setSharedFormula( aBaseAddr ); + // formula has been set, exit parser by returning false + return false; +} + +LinkSheetRange OoxFormulaParserImpl::readSheetRange( RecordInputStream& rStrm ) +{ + return getExternalLinks().getSheetRange( rStrm.readInt16() ); +} + +void OoxFormulaParserImpl::swapStreamPosition( RecordInputStream& rStrm ) +{ + sal_Int64 nRecPos = rStrm.tell(); + rStrm.seek( mnAddDataPos ); + mnAddDataPos = nRecPos; +} + +void OoxFormulaParserImpl::skipMemAreaAddData( RecordInputStream& rStrm ) +{ + swapStreamPosition( rStrm ); + rStrm.skip( 16 * rStrm.readInt32() ); + swapStreamPosition( rStrm ); +} + +// convert BIN token and push API operand or operator ------------------------- + +bool OoxFormulaParserImpl::pushOobName( sal_Int32 nNameId ) +{ + // one-based in OOBIN formulas + return pushDefinedNameOperand( getDefinedNames().getByIndex( nNameId - 1 ) ); +} + +bool OoxFormulaParserImpl::pushOobExtName( sal_Int32 nRefId, sal_Int32 nNameId ) +{ + if( const ExternalLink* pExtLink = getExternalLinks().getExternalLink( nRefId ).get() ) + { + if( pExtLink->getLinkType() == LINKTYPE_SELF ) + return pushOobName( nNameId ); + // external name indexes are one-based in OOBIN + ExternalNameRef xExtName = pExtLink->getNameByIndex( nNameId - 1 ); + return pushExternalNameOperand( xExtName, *pExtLink ); + } + return pushBiffErrorOperand( BIFF_ERR_NAME ); +} + +bool OoxFormulaParserImpl::pushOobFunction( sal_uInt16 nFuncId ) +{ + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOobFuncId( nFuncId ) ) + if( pFuncInfo->mnMinParamCount == pFuncInfo->mnMaxParamCount ) + return pushFunctionOperator( *pFuncInfo, pFuncInfo->mnMinParamCount ); + return pushFunctionOperator( OPCODE_NONAME, 0 ); +} + +bool OoxFormulaParserImpl::pushOobFunction( sal_uInt16 nFuncId, sal_uInt8 nParamCount ) +{ + if( getFlag( nFuncId, BIFF_TOK_FUNCVAR_CMD ) ) + nParamCount &= BIFF_TOK_FUNCVAR_COUNTMASK; + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOobFuncId( nFuncId ) ) + return pushFunctionOperator( *pFuncInfo, nParamCount ); + return pushFunctionOperator( OPCODE_NONAME, nParamCount ); +} + +// BIFF parser implementation ================================================= + +namespace { + +/** A natural language reference struct with relative flag. */ +struct BiffNlr +{ + sal_Int32 mnCol; /// Column index. + sal_Int32 mnRow; /// Row index. + bool mbRel; /// True = relative column/row reference. + + explicit BiffNlr(); + + void readBiff8Data( BiffInputStream& rStrm ); +}; + +BiffNlr::BiffNlr() : + mnCol( 0 ), + mnRow( 0 ), + mbRel( false ) +{ +} + +void BiffNlr::readBiff8Data( BiffInputStream& rStrm ) +{ + sal_uInt16 nRow, nCol; + rStrm >> nRow >> nCol; + mnCol = nCol & BIFF_TOK_NLR_MASK; + mnRow = nRow; + mbRel = getFlag( nCol, BIFF_TOK_NLR_REL ); +} + +bool lclIsValidNlrStack( const BinAddress& rAddr1, const BinAddress& rAddr2, bool bRow ) +{ + return bRow ? + ((rAddr1.mnRow == rAddr2.mnRow) && (rAddr1.mnCol + 1 == rAddr2.mnCol)) : + ((rAddr1.mnCol == rAddr2.mnCol) && (rAddr1.mnRow + 1 == rAddr2.mnRow)); +} + +bool lclIsValidNlrRange( const BiffNlr& rNlr, const BinRange& rRange, bool bRow ) +{ + return bRow ? + ((rNlr.mnRow == rRange.maFirst.mnRow) && (rNlr.mnCol + 1 == rRange.maFirst.mnCol) && (rRange.maFirst.mnRow == rRange.maLast.mnRow)) : + ((rNlr.mnCol == rRange.maFirst.mnCol) && (rNlr.mnRow + 1 == rRange.maFirst.mnRow) && (rRange.maFirst.mnCol == rRange.maLast.mnCol)); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +class BiffFormulaParserImpl : public FormulaParserImpl +{ +public: + explicit BiffFormulaParserImpl( const FormulaParser& rParent ); + + virtual void importBiffFormula( + FormulaContext& rContext, + BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize ); + +private: + // import token contents and create API formula token --------------------- + + bool importTokenNotAvailable( BiffInputStream& rStrm ); + bool importRefTokenNotAvailable( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importStrToken2( BiffInputStream& rStrm ); + bool importStrToken8( BiffInputStream& rStrm ); + bool importAttrToken( BiffInputStream& rStrm ); + bool importSpaceToken3( BiffInputStream& rStrm ); + bool importSpaceToken4( BiffInputStream& rStrm ); + bool importSheetToken2( BiffInputStream& rStrm ); + bool importSheetToken3( BiffInputStream& rStrm ); + bool importEndSheetToken2( BiffInputStream& rStrm ); + bool importEndSheetToken3( BiffInputStream& rStrm ); + bool importNlrToken( BiffInputStream& rStrm ); + bool importArrayToken( BiffInputStream& rStrm ); + bool importRefToken2( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importRefToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importAreaToken2( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importAreaToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importRef3dToken5( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importRef3dToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importArea3dToken5( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importArea3dToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ); + bool importMemAreaToken( BiffInputStream& rStrm, bool bAddData ); + bool importMemFuncToken( BiffInputStream& rStrm ); + bool importNameToken( BiffInputStream& rStrm ); + bool importNameXToken( BiffInputStream& rStrm ); + bool importFuncToken2( BiffInputStream& rStrm ); + bool importFuncToken4( BiffInputStream& rStrm ); + bool importFuncVarToken2( BiffInputStream& rStrm ); + bool importFuncVarToken4( BiffInputStream& rStrm ); + bool importFuncCEToken( BiffInputStream& rStrm ); + bool importExpToken5( BiffInputStream& rStrm ); + + bool importNlrAddrToken( BiffInputStream& rStrm, bool bRow ); + bool importNlrRangeToken( BiffInputStream& rStrm ); + bool importNlrSAddrToken( BiffInputStream& rStrm, bool bRow ); + bool importNlrSRangeToken( BiffInputStream& rStrm ); + bool importNlrErrToken( BiffInputStream& rStrm, sal_uInt16 nSkip ); + + sal_Int32 readRefId( BiffInputStream& rStrm ); + sal_uInt16 readNameId( BiffInputStream& rStrm ); + LinkSheetRange readSheetRange5( BiffInputStream& rStrm ); + LinkSheetRange readSheetRange8( BiffInputStream& rStrm ); + + void swapStreamPosition( BiffInputStream& rStrm ); + void skipMemAreaAddData( BiffInputStream& rStrm ); + bool readNlrSAddrAddData( BiffNlr& orNlr, BiffInputStream& rStrm, bool bRow ); + bool readNlrSRangeAddData( BiffNlr& orNlr, bool& orbIsRow, BiffInputStream& rStrm ); + + // convert BIFF token and push API operand or operator -------------------- + + bool pushBiffReference( const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ); + bool pushBiffReference( const BinComplexRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ); + bool pushBiffNlrAddr( const BiffNlr& rNlr, bool bRow ); + bool pushBiffNlrRange( const BiffNlr& rNlr, const BinRange& rRange ); + bool pushBiffNlrSAddr( const BiffNlr& rNlr, bool bRow ); + bool pushBiffNlrSRange( const BiffNlr& rNlr, const BinRange& rRange, bool bRow ); + bool pushBiffName( sal_uInt16 nNameId ); + bool pushBiffExtName( sal_Int32 nRefId, sal_uInt16 nNameId ); + bool pushBiffFunction( sal_uInt16 nFuncId ); + bool pushBiffFunction( sal_uInt16 nFuncId, sal_uInt8 nParamCount ); + + // ------------------------------------------------------------------------ +private: + typedef bool (BiffFormulaParserImpl::*ImportTokenFunc)( BiffInputStream& ); + typedef bool (BiffFormulaParserImpl::*ImportRefTokenFunc)( BiffInputStream&, bool, bool ); + + ImportTokenFunc mpImportStrToken; /// Pointer to tStr import function (string constant). + ImportTokenFunc mpImportSpaceToken; /// Pointer to tAttrSpace import function (spaces/line breaks). + ImportTokenFunc mpImportSheetToken; /// Pointer to tSheet import function (external reference). + ImportTokenFunc mpImportEndSheetToken; /// Pointer to tEndSheet import function (end of external reference). + ImportTokenFunc mpImportNlrToken; /// Pointer to tNlr import function (natural language reference). + ImportRefTokenFunc mpImportRefToken; /// Pointer to tRef import function (2d cell reference). + ImportRefTokenFunc mpImportAreaToken; /// Pointer to tArea import function (2d area reference). + ImportRefTokenFunc mpImportRef3dToken; /// Pointer to tRef3d import function (3d cell reference). + ImportRefTokenFunc mpImportArea3dToken; /// Pointer to tArea3d import function (3d area reference). + ImportTokenFunc mpImportNameXToken; /// Pointer to tNameX import function (external name). + ImportTokenFunc mpImportFuncToken; /// Pointer to tFunc import function (function with fixed parameter count). + ImportTokenFunc mpImportFuncVarToken; /// Pointer to tFuncVar import function (function with variable parameter count). + ImportTokenFunc mpImportFuncCEToken; /// Pointer to tFuncCE import function (command macro call). + ImportTokenFunc mpImportExpToken; /// Pointer to tExp import function (array/shared formula). + sal_Int64 mnAddDataPos; /// Current stream position for additional data (tArray, tMemArea, tNlr). + sal_Int32 mnCurrRefId; /// Current ref-id from tSheet token (BIFF2-BIFF4 only). + sal_uInt16 mnAttrDataSize; /// Size of one tAttr data element. + sal_uInt16 mnArraySize; /// Size of tArray data. + sal_uInt16 mnNameSize; /// Size of tName data. + sal_uInt16 mnMemAreaSize; /// Size of tMemArea data. + sal_uInt16 mnMemFuncSize; /// Size of tMemFunc data. + sal_uInt16 mnRefIdSize; /// Size of unused data following a reference identifier. +}; + +// ---------------------------------------------------------------------------- + +BiffFormulaParserImpl::BiffFormulaParserImpl( const FormulaParser& rParent ) : + FormulaParserImpl( rParent ), + mnAddDataPos( 0 ), + mnCurrRefId( 0 ) +{ + switch( getBiff() ) + { + case BIFF2: + mpImportStrToken = &BiffFormulaParserImpl::importStrToken2; + mpImportSpaceToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportSheetToken = &BiffFormulaParserImpl::importSheetToken2; + mpImportEndSheetToken = &BiffFormulaParserImpl::importEndSheetToken2; + mpImportNlrToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportRefToken = &BiffFormulaParserImpl::importRefToken2; + mpImportAreaToken = &BiffFormulaParserImpl::importAreaToken2; + mpImportRef3dToken = &BiffFormulaParserImpl::importRefTokenNotAvailable; + mpImportArea3dToken = &BiffFormulaParserImpl::importRefTokenNotAvailable; + mpImportNameXToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportFuncToken = &BiffFormulaParserImpl::importFuncToken2; + mpImportFuncVarToken = &BiffFormulaParserImpl::importFuncVarToken2; + mpImportFuncCEToken = &BiffFormulaParserImpl::importFuncCEToken; + mpImportExpToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mnAttrDataSize = 1; + mnArraySize = 6; + mnNameSize = 5; + mnMemAreaSize = 4; + mnMemFuncSize = 1; + mnRefIdSize = 1; + break; + case BIFF3: + mpImportStrToken = &BiffFormulaParserImpl::importStrToken2; + mpImportSpaceToken = &BiffFormulaParserImpl::importSpaceToken3; + mpImportSheetToken = &BiffFormulaParserImpl::importSheetToken3; + mpImportEndSheetToken = &BiffFormulaParserImpl::importEndSheetToken3; + mpImportNlrToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportRefToken = &BiffFormulaParserImpl::importRefToken2; + mpImportAreaToken = &BiffFormulaParserImpl::importAreaToken2; + mpImportRef3dToken = &BiffFormulaParserImpl::importRefTokenNotAvailable; + mpImportArea3dToken = &BiffFormulaParserImpl::importRefTokenNotAvailable; + mpImportNameXToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportFuncToken = &BiffFormulaParserImpl::importFuncToken2; + mpImportFuncVarToken = &BiffFormulaParserImpl::importFuncVarToken2; + mpImportFuncCEToken = &BiffFormulaParserImpl::importFuncCEToken; + mpImportExpToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mnAttrDataSize = 2; + mnArraySize = 7; + mnNameSize = 8; + mnMemAreaSize = 6; + mnMemFuncSize = 2; + mnRefIdSize = 2; + break; + case BIFF4: + mpImportStrToken = &BiffFormulaParserImpl::importStrToken2; + mpImportSpaceToken = &BiffFormulaParserImpl::importSpaceToken4; + mpImportSheetToken = &BiffFormulaParserImpl::importSheetToken3; + mpImportEndSheetToken = &BiffFormulaParserImpl::importEndSheetToken3; + mpImportNlrToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportRefToken = &BiffFormulaParserImpl::importRefToken2; + mpImportAreaToken = &BiffFormulaParserImpl::importAreaToken2; + mpImportRef3dToken = &BiffFormulaParserImpl::importRefTokenNotAvailable; + mpImportArea3dToken = &BiffFormulaParserImpl::importRefTokenNotAvailable; + mpImportNameXToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportFuncToken = &BiffFormulaParserImpl::importFuncToken4; + mpImportFuncVarToken = &BiffFormulaParserImpl::importFuncVarToken4; + mpImportFuncCEToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportExpToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mnAttrDataSize = 2; + mnArraySize = 7; + mnNameSize = 8; + mnMemAreaSize = 6; + mnMemFuncSize = 2; + mnRefIdSize = 2; + break; + case BIFF5: + mpImportStrToken = &BiffFormulaParserImpl::importStrToken2; + mpImportSpaceToken = &BiffFormulaParserImpl::importSpaceToken4; + mpImportSheetToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportEndSheetToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportNlrToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportRefToken = &BiffFormulaParserImpl::importRefToken2; + mpImportAreaToken = &BiffFormulaParserImpl::importAreaToken2; + mpImportRef3dToken = &BiffFormulaParserImpl::importRef3dToken5; + mpImportArea3dToken = &BiffFormulaParserImpl::importArea3dToken5; + mpImportNameXToken = &BiffFormulaParserImpl::importNameXToken; + mpImportFuncToken = &BiffFormulaParserImpl::importFuncToken4; + mpImportFuncVarToken = &BiffFormulaParserImpl::importFuncVarToken4; + mpImportFuncCEToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportExpToken = &BiffFormulaParserImpl::importExpToken5; + mnAttrDataSize = 2; + mnArraySize = 7; + mnNameSize = 12; + mnMemAreaSize = 6; + mnMemFuncSize = 2; + mnRefIdSize = 8; + break; + case BIFF8: + mpImportStrToken = &BiffFormulaParserImpl::importStrToken8; + mpImportSpaceToken = &BiffFormulaParserImpl::importSpaceToken4; + mpImportSheetToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportEndSheetToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportNlrToken = &BiffFormulaParserImpl::importNlrToken; + mpImportRefToken = &BiffFormulaParserImpl::importRefToken8; + mpImportAreaToken = &BiffFormulaParserImpl::importAreaToken8; + mpImportRef3dToken = &BiffFormulaParserImpl::importRef3dToken8; + mpImportArea3dToken = &BiffFormulaParserImpl::importArea3dToken8; + mpImportNameXToken = &BiffFormulaParserImpl::importNameXToken; + mpImportFuncToken = &BiffFormulaParserImpl::importFuncToken4; + mpImportFuncVarToken = &BiffFormulaParserImpl::importFuncVarToken4; + mpImportFuncCEToken = &BiffFormulaParserImpl::importTokenNotAvailable; + mpImportExpToken = &BiffFormulaParserImpl::importExpToken5; + mnAttrDataSize = 2; + mnArraySize = 7; + mnNameSize = 2; + mnMemAreaSize = 6; + mnMemFuncSize = 2; + mnRefIdSize = 0; + break; + case BIFF_UNKNOWN: break; + } +} + +void BiffFormulaParserImpl::importBiffFormula( FormulaContext& rContext, + BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize ) +{ + initializeImport( rContext ); + mnCurrRefId = 0; + + sal_uInt16 nFmlaSize = lclReadFmlaSize( rStrm, getBiff(), pnFmlaSize ); + sal_Int64 nEndPos = mnAddDataPos = rStrm.tell() + nFmlaSize; + bool bRelativeAsOffset = getFormulaContext().isRelativeAsOffset(); + + bool bOk = true; + while( bOk && !rStrm.isEof() && (rStrm.tell() < nEndPos) ) + { + sal_uInt8 nTokenId; + rStrm >> nTokenId; + sal_uInt8 nTokenClass = nTokenId & BIFF_TOKCLASS_MASK; + sal_uInt8 nBaseId = nTokenId & BIFF_TOKID_MASK; + + bOk = !getFlag( nTokenId, BIFF_TOKFLAG_INVALID ); + if( bOk ) + { + if( nTokenClass == BIFF_TOKCLASS_NONE ) + { + // base tokens + switch( nBaseId ) + { + case BIFF_TOKID_EXP: bOk = (this->*mpImportExpToken)( rStrm ); break; + case BIFF_TOKID_TBL: bOk = false; /* multiple op. will be set externally */ break; + case BIFF_TOKID_ADD: bOk = pushBinaryOperator( OPCODE_ADD ); break; + case BIFF_TOKID_SUB: bOk = pushBinaryOperator( OPCODE_SUB ); break; + case BIFF_TOKID_MUL: bOk = pushBinaryOperator( OPCODE_MULT ); break; + case BIFF_TOKID_DIV: bOk = pushBinaryOperator( OPCODE_DIV ); break; + case BIFF_TOKID_POWER: bOk = pushBinaryOperator( OPCODE_POWER ); break; + case BIFF_TOKID_CONCAT: bOk = pushBinaryOperator( OPCODE_CONCAT ); break; + case BIFF_TOKID_LT: bOk = pushBinaryOperator( OPCODE_LESS ); break; + case BIFF_TOKID_LE: bOk = pushBinaryOperator( OPCODE_LESS_EQUAL ); break; + case BIFF_TOKID_EQ: bOk = pushBinaryOperator( OPCODE_EQUAL ); break; + case BIFF_TOKID_GE: bOk = pushBinaryOperator( OPCODE_GREATER_EQUAL ); break; + case BIFF_TOKID_GT: bOk = pushBinaryOperator( OPCODE_GREATER ); break; + case BIFF_TOKID_NE: bOk = pushBinaryOperator( OPCODE_NOT_EQUAL ); break; + case BIFF_TOKID_ISECT: bOk = pushBinaryOperator( OPCODE_INTERSECT ); break; + case BIFF_TOKID_LIST: bOk = pushBinaryOperator( OPCODE_LIST ); break; + case BIFF_TOKID_RANGE: bOk = pushBinaryOperator( OPCODE_RANGE ); break; + case BIFF_TOKID_UPLUS: bOk = pushUnaryPreOperator( OPCODE_PLUS_SIGN ); break; + case BIFF_TOKID_UMINUS: bOk = pushUnaryPreOperator( OPCODE_MINUS_SIGN ); break; + case BIFF_TOKID_PERCENT: bOk = pushUnaryPostOperator( OPCODE_PERCENT ); break; + case BIFF_TOKID_PAREN: bOk = pushParenthesesOperator(); break; + case BIFF_TOKID_MISSARG: bOk = pushOperand( OPCODE_MISSING ); break; + case BIFF_TOKID_STR: bOk = (this->*mpImportStrToken)( rStrm ); break; + case BIFF_TOKID_NLR: bOk = (this->*mpImportNlrToken)( rStrm ); break; + case BIFF_TOKID_ATTR: bOk = importAttrToken( rStrm ); break; + case BIFF_TOKID_SHEET: bOk = (this->*mpImportSheetToken)( rStrm ); break; + case BIFF_TOKID_ENDSHEET: bOk = (this->*mpImportEndSheetToken)( rStrm ); break; + case BIFF_TOKID_ERR: bOk = pushBiffErrorOperand( rStrm.readuInt8() ); break; + case BIFF_TOKID_BOOL: bOk = pushBiffBoolOperand( rStrm.readuInt8() ); break; + case BIFF_TOKID_INT: bOk = pushValueOperand< double >( rStrm.readuInt16() ); break; + case BIFF_TOKID_NUM: bOk = pushValueOperand( rStrm.readDouble() ); break; + default: bOk = false; + } + } + else + { + // classified tokens + switch( nBaseId ) + { + case BIFF_TOKID_ARRAY: bOk = importArrayToken( rStrm ); break; + case BIFF_TOKID_FUNC: bOk = (this->*mpImportFuncToken)( rStrm ); break; + case BIFF_TOKID_FUNCVAR: bOk = (this->*mpImportFuncVarToken)( rStrm ); break; + case BIFF_TOKID_NAME: bOk = importNameToken( rStrm ); break; + case BIFF_TOKID_REF: bOk = (this->*mpImportRefToken)( rStrm, false, false ); break; + case BIFF_TOKID_AREA: bOk = (this->*mpImportAreaToken)( rStrm, false, false ); break; + case BIFF_TOKID_MEMAREA: bOk = importMemAreaToken( rStrm, true ); break; + case BIFF_TOKID_MEMERR: bOk = importMemAreaToken( rStrm, false ); break; + case BIFF_TOKID_MEMNOMEM: bOk = importMemAreaToken( rStrm, false ); break; + case BIFF_TOKID_MEMFUNC: bOk = importMemFuncToken( rStrm ); break; + case BIFF_TOKID_REFERR: bOk = (this->*mpImportRefToken)( rStrm, true, false ); break; + case BIFF_TOKID_AREAERR: bOk = (this->*mpImportAreaToken)( rStrm, true, false ); break; + case BIFF_TOKID_REFN: bOk = (this->*mpImportRefToken)( rStrm, false, true ); break; + case BIFF_TOKID_AREAN: bOk = (this->*mpImportAreaToken)( rStrm, false, true ); break; + case BIFF_TOKID_MEMAREAN: bOk = importMemFuncToken( rStrm ); break; + case BIFF_TOKID_MEMNOMEMN: bOk = importMemFuncToken( rStrm ); break; + case BIFF_TOKID_FUNCCE: bOk = (this->*mpImportFuncCEToken)( rStrm ); break; + case BIFF_TOKID_NAMEX: bOk = (this->*mpImportNameXToken)( rStrm ); break; + case BIFF_TOKID_REF3D: bOk = (this->*mpImportRef3dToken)( rStrm, false, bRelativeAsOffset ); break; + case BIFF_TOKID_AREA3D: bOk = (this->*mpImportArea3dToken)( rStrm, false, bRelativeAsOffset ); break; + case BIFF_TOKID_REFERR3D: bOk = (this->*mpImportRef3dToken)( rStrm, true, bRelativeAsOffset ); break; + case BIFF_TOKID_AREAERR3D: bOk = (this->*mpImportArea3dToken)( rStrm, true, bRelativeAsOffset ); break; + default: bOk = false; + } + } + } + } + + // build and finalize the token sequence + if( bOk && (rStrm.tell() == nEndPos) ) + finalizeImport(); + + // seek behind additional token data of tArray, tMemArea, tNlr tokens + rStrm.seek( mnAddDataPos ); +} + +// import token contents and create API formula token ------------------------- + +bool BiffFormulaParserImpl::importTokenNotAvailable( BiffInputStream& ) +{ + // dummy function for pointer-to-member-function + return false; +} + +bool BiffFormulaParserImpl::importRefTokenNotAvailable( BiffInputStream&, bool, bool ) +{ + // dummy function for pointer-to-member-function + return false; +} + +bool BiffFormulaParserImpl::importStrToken2( BiffInputStream& rStrm ) +{ + return pushValueOperand( rStrm.readByteStringUC( false, getTextEncoding(), getFormulaContext().isNulCharsAllowed() ) ); +} + +bool BiffFormulaParserImpl::importStrToken8( BiffInputStream& rStrm ) +{ + // read flags field for empty strings also + return pushValueOperand( rStrm.readUniStringBody( rStrm.readuInt8(), getFormulaContext().isNulCharsAllowed() ) ); +} + +bool BiffFormulaParserImpl::importAttrToken( BiffInputStream& rStrm ) +{ + bool bOk = true; + sal_uInt8 nType; + rStrm >> nType; + switch( nType ) + { + case 0: // sometimes, tAttrSkip tokens miss the type flag + case BIFF_TOK_ATTR_VOLATILE: + case BIFF_TOK_ATTR_IF: + case BIFF_TOK_ATTR_SKIP: + case BIFF_TOK_ATTR_ASSIGN: + rStrm.skip( mnAttrDataSize ); + break; + case BIFF_TOK_ATTR_CHOOSE: + rStrm.skip( mnAttrDataSize * (1 + ((getBiff() == BIFF2) ? rStrm.readuInt8() : rStrm.readuInt16())) ); + break; + case BIFF_TOK_ATTR_SUM: + rStrm.skip( mnAttrDataSize ); + bOk = pushBiffFunction( BIFF_FUNC_SUM, 1 ); + break; + case BIFF_TOK_ATTR_SPACE: + case BIFF_TOK_ATTR_SPACE_VOLATILE: + bOk = (this->*mpImportSpaceToken)( rStrm ); + break; + default: + bOk = false; + } + return bOk; +} + +bool BiffFormulaParserImpl::importSpaceToken3( BiffInputStream& rStrm ) +{ + rStrm.skip( 2 ); + return true; +} + +bool BiffFormulaParserImpl::importSpaceToken4( BiffInputStream& rStrm ) +{ + sal_uInt8 nType, nCount; + rStrm >> nType >> nCount; + switch( nType ) + { + case BIFF_TOK_ATTR_SPACE_SP: + appendLeadingSpaces( nCount, false ); + break; + case BIFF_TOK_ATTR_SPACE_BR: + appendLeadingSpaces( nCount, true ); + break; + case BIFF_TOK_ATTR_SPACE_SP_OPEN: + appendOpeningSpaces( nCount, false ); + break; + case BIFF_TOK_ATTR_SPACE_BR_OPEN: + appendOpeningSpaces( nCount, true ); + break; + case BIFF_TOK_ATTR_SPACE_SP_CLOSE: + appendClosingSpaces( nCount, false ); + break; + case BIFF_TOK_ATTR_SPACE_BR_CLOSE: + appendClosingSpaces( nCount, true ); + break; + } + return true; +} + +bool BiffFormulaParserImpl::importSheetToken2( BiffInputStream& rStrm ) +{ + rStrm.skip( 4 ); + mnCurrRefId = readRefId( rStrm ); + return true; +} + +bool BiffFormulaParserImpl::importSheetToken3( BiffInputStream& rStrm ) +{ + rStrm.skip( 6 ); + mnCurrRefId = readRefId( rStrm ); + return true; +} + +bool BiffFormulaParserImpl::importEndSheetToken2( BiffInputStream& rStrm ) +{ + rStrm.skip( 3 ); + mnCurrRefId = 0; + return true; +} + +bool BiffFormulaParserImpl::importEndSheetToken3( BiffInputStream& rStrm ) +{ + rStrm.skip( 4 ); + mnCurrRefId = 0; + return true; +} + +bool BiffFormulaParserImpl::importNlrToken( BiffInputStream& rStrm ) +{ + bool bOk = true; + sal_uInt8 nNlrType; + rStrm >> nNlrType; + switch( nNlrType ) + { + case BIFF_TOK_NLR_ERR: bOk = importNlrErrToken( rStrm, 4 ); break; + case BIFF_TOK_NLR_ROWR: bOk = importNlrAddrToken( rStrm, true ); break; + case BIFF_TOK_NLR_COLR: bOk = importNlrAddrToken( rStrm, false ); break; + case BIFF_TOK_NLR_ROWV: bOk = importNlrAddrToken( rStrm, true ); break; + case BIFF_TOK_NLR_COLV: bOk = importNlrAddrToken( rStrm, false ); break; + case BIFF_TOK_NLR_RANGE: bOk = importNlrRangeToken( rStrm ); break; + case BIFF_TOK_NLR_SRANGE: bOk = importNlrSRangeToken( rStrm ); break; + case BIFF_TOK_NLR_SROWR: bOk = importNlrSAddrToken( rStrm, true ); break; + case BIFF_TOK_NLR_SCOLR: bOk = importNlrSAddrToken( rStrm, false ); break; + case BIFF_TOK_NLR_SROWV: bOk = importNlrSAddrToken( rStrm, true ); break; + case BIFF_TOK_NLR_SCOLV: bOk = importNlrSAddrToken( rStrm, false ); break; + case BIFF_TOK_NLR_RANGEERR: bOk = importNlrErrToken( rStrm, 13 ); break; + case BIFF_TOK_NLR_SXNAME: bOk = importNlrErrToken( rStrm, 4 ); break; + default: bOk = false; + } + return bOk; +} + +bool BiffFormulaParserImpl::importArrayToken( BiffInputStream& rStrm ) +{ + rStrm.skip( mnArraySize ); + + // start token array with opening brace and leading spaces + pushOperand( OPCODE_ARRAY_OPEN ); + size_t nOpSize = popOperandSize(); + size_t nOldArraySize = getFormulaSize(); + bool bBiff8 = getBiff() == BIFF8; + bool bNulChars = getFormulaContext().isNulCharsAllowed(); + + // read array size + swapStreamPosition( rStrm ); + sal_uInt16 nCols = rStrm.readuInt8(); + sal_uInt16 nRows = rStrm.readuInt16(); + if( bBiff8 ) { ++nCols; ++nRows; } else if( nCols == 0 ) nCols = 256; + OSL_ENSURE( (nCols > 0) && (nRows > 0), "BiffFormulaParserImpl::importArrayToken - empty array" ); + + // read array values and build token array + for( sal_uInt16 nRow = 0; !rStrm.isEof() && (nRow < nRows); ++nRow ) + { + if( nRow > 0 ) + appendRawToken( OPCODE_ARRAY_ROWSEP ); + for( sal_uInt16 nCol = 0; !rStrm.isEof() && (nCol < nCols); ++nCol ) + { + if( nCol > 0 ) + appendRawToken( OPCODE_ARRAY_COLSEP ); + switch( rStrm.readuInt8() ) + { + case BIFF_DATATYPE_EMPTY: + appendRawToken( OPCODE_PUSH ) <<= OUString(); + rStrm.skip( 8 ); + break; + case BIFF_DATATYPE_DOUBLE: + appendRawToken( OPCODE_PUSH ) <<= rStrm.readDouble(); + break; + case BIFF_DATATYPE_STRING: + appendRawToken( OPCODE_PUSH ) <<= bBiff8 ? + rStrm.readUniString( bNulChars ) : + rStrm.readByteStringUC( false, getTextEncoding(), bNulChars ); + break; + case BIFF_DATATYPE_BOOL: + appendRawToken( OPCODE_PUSH ) <<= static_cast< double >( (rStrm.readuInt8() == BIFF_TOK_BOOL_FALSE) ? 0.0 : 1.0 ); + rStrm.skip( 7 ); + break; + case BIFF_DATATYPE_ERROR: + appendRawToken( OPCODE_PUSH ) <<= BiffHelper::calcDoubleFromError( rStrm.readuInt8() ); + rStrm.skip( 7 ); + break; + default: + OSL_ENSURE( false, "BiffFormulaParserImpl::importArrayToken - unknown data type" ); + appendRawToken( OPCODE_PUSH ) <<= BiffHelper::calcDoubleFromError( BIFF_ERR_NA ); + } + } + } + swapStreamPosition( rStrm ); + + // close token array and set resulting operand size + appendRawToken( OPCODE_ARRAY_CLOSE ); + pushOperandSize( nOpSize + getFormulaSize() - nOldArraySize ); + return true; +} + +bool BiffFormulaParserImpl::importRefToken2( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + BinSingleRef2d aRef; + aRef.readBiff2Data( rStrm, bRelativeAsOffset ); + return pushBiffReference( aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importRefToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + BinSingleRef2d aRef; + aRef.readBiff8Data( rStrm, bRelativeAsOffset ); + return pushBiffReference( aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importAreaToken2( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + BinComplexRef2d aRef; + aRef.readBiff2Data( rStrm, bRelativeAsOffset ); + return pushBiffReference( aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importAreaToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + BinComplexRef2d aRef; + aRef.readBiff8Data( rStrm, bRelativeAsOffset ); + return pushBiffReference( aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importRef3dToken5( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + LinkSheetRange aSheetRange = readSheetRange5( rStrm ); + BinSingleRef2d aRef; + aRef.readBiff2Data( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aSheetRange, aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importRef3dToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + LinkSheetRange aSheetRange = readSheetRange8( rStrm ); + BinSingleRef2d aRef; + aRef.readBiff8Data( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aSheetRange, aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importArea3dToken5( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + LinkSheetRange aSheetRange = readSheetRange5( rStrm ); + BinComplexRef2d aRef; + aRef.readBiff2Data( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aSheetRange, aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importArea3dToken8( BiffInputStream& rStrm, bool bDeleted, bool bRelativeAsOffset ) +{ + LinkSheetRange aSheetRange = readSheetRange8( rStrm ); + BinComplexRef2d aRef; + aRef.readBiff8Data( rStrm, bRelativeAsOffset ); + return pushReferenceOperand( aSheetRange, aRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::importMemAreaToken( BiffInputStream& rStrm, bool bAddData ) +{ + rStrm.skip( mnMemAreaSize ); + if( bAddData ) + skipMemAreaAddData( rStrm ); + return true; +} + +bool BiffFormulaParserImpl::importMemFuncToken( BiffInputStream& rStrm ) +{ + rStrm.skip( mnMemFuncSize ); + return true; +} + +bool BiffFormulaParserImpl::importNameToken( BiffInputStream& rStrm ) +{ + sal_uInt16 nNameId = readNameId( rStrm ); + return (mnCurrRefId > 0) ? pushBiffExtName( mnCurrRefId, nNameId ) : pushBiffName( nNameId ); +} + +bool BiffFormulaParserImpl::importNameXToken( BiffInputStream& rStrm ) +{ + sal_Int32 nRefId = readRefId( rStrm ); + sal_uInt16 nNameId = readNameId( rStrm ); + return pushBiffExtName( nRefId, nNameId ); +} + +bool BiffFormulaParserImpl::importFuncToken2( BiffInputStream& rStrm ) +{ + sal_uInt8 nFuncId; + rStrm >> nFuncId; + return pushBiffFunction( nFuncId ); +} + +bool BiffFormulaParserImpl::importFuncToken4( BiffInputStream& rStrm ) +{ + sal_uInt16 nFuncId; + rStrm >> nFuncId; + return pushBiffFunction( nFuncId ); +} + +bool BiffFormulaParserImpl::importFuncVarToken2( BiffInputStream& rStrm ) +{ + sal_uInt8 nParamCount, nFuncId; + rStrm >> nParamCount >> nFuncId; + return pushBiffFunction( nFuncId, nParamCount ); +} + +bool BiffFormulaParserImpl::importFuncVarToken4( BiffInputStream& rStrm ) +{ + sal_uInt8 nParamCount; + sal_uInt16 nFuncId; + rStrm >> nParamCount >> nFuncId; + return pushBiffFunction( nFuncId, nParamCount & BIFF_TOK_FUNCVAR_COUNTMASK ); +} + +bool BiffFormulaParserImpl::importFuncCEToken( BiffInputStream& rStrm ) +{ + sal_uInt8 nParamCount, nFuncId; + rStrm >> nParamCount >> nFuncId; + sal_uInt16 nCmdId = nFuncId; + setFlag( nCmdId, BIFF_TOK_FUNCVAR_CMD ); + return pushBiffFunction( nCmdId, nParamCount ); +} + +bool BiffFormulaParserImpl::importExpToken5( BiffInputStream& rStrm ) +{ + BinAddress aBaseAddr; + aBaseAddr.read( rStrm ); + setSharedFormula( aBaseAddr ); + // formula has been set, exit parser by returning false + return false; +} + +bool BiffFormulaParserImpl::importNlrAddrToken( BiffInputStream& rStrm, bool bRow ) +{ + BiffNlr aNlr; + aNlr.readBiff8Data( rStrm ); + return pushBiffNlrAddr( aNlr, bRow ); +} + +bool BiffFormulaParserImpl::importNlrRangeToken( BiffInputStream& rStrm ) +{ + BiffNlr aNlr; + aNlr.readBiff8Data( rStrm ); + rStrm.skip( 1 ); + BinRange aRange; + rStrm >> aRange; + return pushBiffNlrRange( aNlr, aRange ); +} + +bool BiffFormulaParserImpl::importNlrSAddrToken( BiffInputStream& rStrm, bool bRow ) +{ + rStrm.skip( 4 ); + BiffNlr aNlr; + return readNlrSAddrAddData( aNlr, rStrm, bRow ) ? pushBiffNlrSAddr( aNlr, bRow ) : pushBiffErrorOperand( BIFF_ERR_REF ); +} + +bool BiffFormulaParserImpl::importNlrSRangeToken( BiffInputStream& rStrm ) +{ + rStrm.skip( 5 ); + BinRange aRange; + rStrm >> aRange; + BiffNlr aNlr; + bool bRow; + return readNlrSRangeAddData( aNlr, bRow, rStrm ) ? pushBiffNlrSRange( aNlr, aRange, bRow ) : pushBiffErrorOperand( BIFF_ERR_REF ); +} + +bool BiffFormulaParserImpl::importNlrErrToken( BiffInputStream& rStrm, sal_uInt16 nIgnore ) +{ + rStrm.skip( nIgnore ); + return pushBiffErrorOperand( BIFF_ERR_NAME ); +} + +sal_Int32 BiffFormulaParserImpl::readRefId( BiffInputStream& rStrm ) +{ + sal_Int16 nRefId; + rStrm >> nRefId; + rStrm.skip( mnRefIdSize ); + return nRefId; +} + +sal_uInt16 BiffFormulaParserImpl::readNameId( BiffInputStream& rStrm ) +{ + sal_uInt16 nNameId; + rStrm >> nNameId; + rStrm.skip( mnNameSize ); + return nNameId; +} + +LinkSheetRange BiffFormulaParserImpl::readSheetRange5( BiffInputStream& rStrm ) +{ + sal_Int32 nRefId = readRefId( rStrm ); + sal_Int16 nTab1, nTab2; + rStrm >> nTab1 >> nTab2; + return getExternalLinks().getSheetRange( nRefId, nTab1, nTab2 ); +} + +LinkSheetRange BiffFormulaParserImpl::readSheetRange8( BiffInputStream& rStrm ) +{ + return getExternalLinks().getSheetRange( readRefId( rStrm ) ); +} + +void BiffFormulaParserImpl::swapStreamPosition( BiffInputStream& rStrm ) +{ + sal_Int64 nRecPos = rStrm.tell(); + rStrm.seek( mnAddDataPos ); + mnAddDataPos = nRecPos; +} + +void BiffFormulaParserImpl::skipMemAreaAddData( BiffInputStream& rStrm ) +{ + swapStreamPosition( rStrm ); + sal_Int32 nCount = rStrm.readuInt16(); + rStrm.skip( ((getBiff() == BIFF8) ? 8 : 6) * nCount ); + swapStreamPosition( rStrm ); +} + +bool BiffFormulaParserImpl::readNlrSAddrAddData( BiffNlr& orNlr, BiffInputStream& rStrm, bool bRow ) +{ + bool bIsRow; + return readNlrSRangeAddData( orNlr, bIsRow, rStrm ) && (bIsRow == bRow); +} + +bool BiffFormulaParserImpl::readNlrSRangeAddData( BiffNlr& orNlr, bool& orbIsRow, BiffInputStream& rStrm ) +{ + swapStreamPosition( rStrm ); + // read number of cell addresses and relative flag + sal_uInt32 nCount; + rStrm >> nCount; + bool bRel = getFlag( nCount, BIFF_TOK_NLR_ADDREL ); + nCount &= BIFF_TOK_NLR_ADDMASK; + sal_Int64 nEndPos = rStrm.tell() + 4 * nCount; + // read list of cell addresses + bool bValid = false; + if( nCount >= 2 ) + { + // detect column/row orientation + BinAddress aAddr1, aAddr2; + rStrm >> aAddr1 >> aAddr2; + orbIsRow = aAddr1.mnRow == aAddr2.mnRow; + bValid = lclIsValidNlrStack( aAddr1, aAddr2, orbIsRow ); + // read and verify additional cell positions + for( sal_uInt32 nIndex = 2; bValid && (nIndex < nCount); ++nIndex ) + { + aAddr1 = aAddr2; + rStrm >> aAddr2; + bValid = !rStrm.isEof() && lclIsValidNlrStack( aAddr1, aAddr2, orbIsRow ); + } + // check that last imported position (aAddr2) is not at the end of the sheet + bValid = bValid && (orbIsRow ? (aAddr2.mnCol < mnMaxApiCol) : (aAddr2.mnRow < mnMaxApiRow)); + // fill the NLR struct with the last imported position + if( bValid ) + { + orNlr.mnCol = aAddr2.mnCol; + orNlr.mnRow = aAddr2.mnRow; + orNlr.mbRel = bRel; + } + } + // seek to end of additional data for this token + rStrm.seek( nEndPos ); + swapStreamPosition( rStrm ); + + return bValid; +} + +// convert BIFF token and push API operand or operator ------------------------ + +bool BiffFormulaParserImpl::pushBiffReference( const BinSingleRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) +{ + return (mnCurrRefId > 0) ? + pushReferenceOperand( getExternalLinks().getSheetRange( mnCurrRefId, 0, 0 ), rRef, bDeleted, bRelativeAsOffset ) : + pushReferenceOperand( rRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::pushBiffReference( const BinComplexRef2d& rRef, bool bDeleted, bool bRelativeAsOffset ) +{ + return (mnCurrRefId > 0) ? + pushReferenceOperand( getExternalLinks().getSheetRange( mnCurrRefId, 0, 0 ), rRef, bDeleted, bRelativeAsOffset ) : + pushReferenceOperand( rRef, bDeleted, bRelativeAsOffset ); +} + +bool BiffFormulaParserImpl::pushBiffNlrAddr( const BiffNlr& rNlr, bool bRow ) +{ + BinSingleRef2d aRef; + aRef.mnCol = rNlr.mnCol; + aRef.mnRow = rNlr.mnRow; + aRef.mbColRel = !bRow; + aRef.mbRowRel = bRow; + return pushNlrOperand( aRef ); +} + +bool BiffFormulaParserImpl::pushBiffNlrRange( const BiffNlr& rNlr, const BinRange& rRange ) +{ + bool bRow = rNlr.mnRow == rRange.maFirst.mnRow; + return lclIsValidNlrRange( rNlr, rRange, bRow ) ? + pushBiffNlrAddr( rNlr, bRow ) : pushBiffErrorOperand( BIFF_ERR_REF ); +} + +bool BiffFormulaParserImpl::pushBiffNlrSAddr( const BiffNlr& rNlr, bool bRow ) +{ + BinRange aRange; + aRange.maFirst.mnCol = rNlr.mnCol + (bRow ? 1 : 0); + aRange.maFirst.mnRow = rNlr.mnRow + (bRow ? 0 : 1); + aRange.maLast.mnCol = bRow ? mnMaxApiCol : rNlr.mnCol; + aRange.maLast.mnRow = bRow ? rNlr.mnRow : mnMaxApiRow; + return pushBiffNlrSRange( rNlr, aRange, bRow ); +} + +bool BiffFormulaParserImpl::pushBiffNlrSRange( const BiffNlr& rNlr, const BinRange& rRange, bool bRow ) +{ + if( lclIsValidNlrRange( rNlr, rRange, bRow ) ) + { + BinComplexRef2d aRef; + aRef.maRef1.mnCol = rRange.maFirst.mnCol; + aRef.maRef1.mnRow = rRange.maFirst.mnRow; + aRef.maRef2.mnCol = rRange.maLast.mnCol; + aRef.maRef2.mnRow = rRange.maLast.mnRow; + aRef.maRef1.mbColRel = aRef.maRef2.mbColRel = !bRow && rNlr.mbRel; + aRef.maRef1.mbRowRel = aRef.maRef2.mbRowRel = bRow && rNlr.mbRel; + return pushReferenceOperand( aRef, false, false ); + } + return pushBiffErrorOperand( BIFF_ERR_REF ); +} + +bool BiffFormulaParserImpl::pushBiffName( sal_uInt16 nNameId ) +{ + // one-based in BIFF formulas + return pushDefinedNameOperand( getDefinedNames().getByIndex( static_cast< sal_Int32 >( nNameId ) - 1 ) ); +} + +bool BiffFormulaParserImpl::pushBiffExtName( sal_Int32 nRefId, sal_uInt16 nNameId ) +{ + if( const ExternalLink* pExtLink = getExternalLinks().getExternalLink( nRefId ).get() ) + { + if( pExtLink->getLinkType() == LINKTYPE_SELF ) + return pushBiffName( nNameId ); + // external name indexes are one-based in BIFF + ExternalNameRef xExtName = pExtLink->getNameByIndex( static_cast< sal_Int32 >( nNameId ) - 1 ); + return pushExternalNameOperand( xExtName, *pExtLink ); + } + return pushBiffErrorOperand( BIFF_ERR_NAME ); +} + +bool BiffFormulaParserImpl::pushBiffFunction( sal_uInt16 nFuncId ) +{ + if( const FunctionInfo* pFuncInfo = getFuncInfoFromBiffFuncId( nFuncId ) ) + if( pFuncInfo->mnMinParamCount == pFuncInfo->mnMaxParamCount ) + return pushFunctionOperator( *pFuncInfo, pFuncInfo->mnMinParamCount ); + return pushFunctionOperator( OPCODE_NONAME, 0 ); +} + +bool BiffFormulaParserImpl::pushBiffFunction( sal_uInt16 nFuncId, sal_uInt8 nParamCount ) +{ + if( getFlag( nFuncId, BIFF_TOK_FUNCVAR_CMD ) ) + nParamCount &= BIFF_TOK_FUNCVAR_COUNTMASK; + if( const FunctionInfo* pFuncInfo = getFuncInfoFromBiffFuncId( nFuncId ) ) + return pushFunctionOperator( *pFuncInfo, nParamCount ); + return pushFunctionOperator( OPCODE_NONAME, nParamCount ); +} + +// ============================================================================ + +FormulaParser::FormulaParser( const WorkbookHelper& rHelper ) : + FormulaProcessorBase( rHelper ) +{ + switch( getFilterType() ) + { + case FILTER_OOX: mxImpl.reset( new OoxFormulaParserImpl( *this ) ); break; + case FILTER_BIFF: mxImpl.reset( new BiffFormulaParserImpl( *this ) ); break; + case FILTER_UNKNOWN: break; + } +} + +FormulaParser::~FormulaParser() +{ +} + +void FormulaParser::importFormula( FormulaContext& rContext, const OUString& rFormulaString ) const +{ + mxImpl->importOoxFormula( rContext, rFormulaString ); +} + +void FormulaParser::importFormula( FormulaContext& rContext, RecordInputStream& rStrm ) const +{ + mxImpl->importOobFormula( rContext, rStrm ); +} + +void FormulaParser::importFormula( FormulaContext& rContext, BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize ) const +{ + mxImpl->importBiffFormula( rContext, rStrm, pnFmlaSize ); +} + +void FormulaParser::convertErrorToFormula( FormulaContext& rContext, sal_uInt8 nErrorCode ) const +{ + ApiTokenSequence aTokens( 3 ); + // HACK: enclose all error codes into an 1x1 matrix + aTokens[ 0 ].OpCode = OPCODE_ARRAY_OPEN; + aTokens[ 1 ].OpCode = OPCODE_PUSH; + aTokens[ 1 ].Data <<= BiffHelper::calcDoubleFromError( nErrorCode ); + aTokens[ 2 ].OpCode = OPCODE_ARRAY_CLOSE; + mxImpl->setFormula( rContext, aTokens ); +} + +void FormulaParser::convertNameToFormula( FormulaContext& rContext, sal_Int32 nTokenIndex ) const +{ + if( nTokenIndex >= 0 ) + { + ApiTokenSequence aTokens( 1 ); + aTokens[ 0 ].OpCode = OPCODE_NAME; + aTokens[ 0 ].Data <<= nTokenIndex; + mxImpl->setFormula( rContext, aTokens ); + } + else + convertErrorToFormula( rContext, BIFF_ERR_REF ); +} + +void FormulaParser::convertNumberToHyperlink( FormulaContext& rContext, const OUString& rUrl, double fValue ) const +{ + OSL_ENSURE( rUrl.getLength() > 0, "FormulaParser::convertNumberToHyperlink - missing URL" ); + if( const FunctionInfo* pFuncInfo = getFuncInfoFromOobFuncId( OOBIN_FUNC_HYPERLINK ) ) + { + ApiTokenSequence aTokens( 6 ); + aTokens[ 0 ].OpCode = pFuncInfo->mnApiOpCode; + aTokens[ 1 ].OpCode = OPCODE_OPEN; + aTokens[ 2 ].OpCode = OPCODE_PUSH; + aTokens[ 2 ].Data <<= rUrl; + aTokens[ 3 ].OpCode = OPCODE_SEP; + aTokens[ 4 ].OpCode = OPCODE_PUSH; + aTokens[ 4 ].Data <<= fValue; + aTokens[ 5 ].OpCode = OPCODE_CLOSE; + mxImpl->setFormula( rContext, aTokens ); + } +} + +OUString FormulaParser::importOleTargetLink( const OUString& rFormulaString ) +{ + // obviously, this would overburden our formula parser, so we parse it manually + OUString aTargetLink; + sal_Int32 nFmlaLen = rFormulaString.getLength(); + if( (nFmlaLen >= 8) && (rFormulaString[ 0 ] == '[') ) + { + // passed string is trimmed already + sal_Int32 nBracketClose = rFormulaString.indexOf( ']' ); + sal_Int32 nExclamation = rFormulaString.indexOf( '!' ); + if( (nBracketClose >= 2) && + (nBracketClose + 1 == nExclamation) && + (rFormulaString[ nExclamation + 1 ] == '\'') && + (rFormulaString[ nFmlaLen - 1 ] == '\'') ) + { + sal_Int32 nRefId = rFormulaString.copy( 1, nBracketClose - 1 ).toInt32(); + aTargetLink = mxImpl->resolveOleTarget( nRefId ); + } + } + return aTargetLink; +} + +OUString FormulaParser::importOleTargetLink( RecordInputStream& rStrm ) +{ + OUString aTargetLink; + sal_Int32 nFmlaSize = rStrm.readInt32(); + sal_Int64 nFmlaEndPos = rStrm.tell() + ::std::max< sal_Int32 >( nFmlaSize, 0 ); + if( (nFmlaSize == 7) && (rStrm.getRemaining() >= 7) ) + { + sal_uInt8 nToken; + sal_Int16 nRefId; + sal_Int32 nNameId; + rStrm >> nToken >> nRefId >> nNameId; + if( nToken == (BIFF_TOKCLASS_VAL|BIFF_TOKID_NAMEX) ) + aTargetLink = mxImpl->resolveOleTarget( nRefId ); + } + rStrm.seek( nFmlaEndPos ); + return aTargetLink; +} + +OUString FormulaParser::importOleTargetLink( BiffInputStream& rStrm, const sal_uInt16* pnFmlaSize ) const +{ + OUString aTargetLink; + sal_uInt16 nFmlaSize = lclReadFmlaSize( rStrm, getBiff(), pnFmlaSize ); + rStrm.skip( nFmlaSize ); + return aTargetLink; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/makefile.mk b/oox/source/xls/makefile.mk new file mode 100644 index 000000000000..b5ede953bbfe --- /dev/null +++ b/oox/source/xls/makefile.mk @@ -0,0 +1,101 @@ +#************************************************************************* +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# Copyright 2000, 2010 Oracle and/or its affiliates. +# +# OpenOffice.org - a multi-platform office productivity suite +# +# This file is part of OpenOffice.org. +# +# OpenOffice.org is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# only, as published by the Free Software Foundation. +# +# OpenOffice.org is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License version 3 for more details +# (a copy is included in the LICENSE file that accompanied this code). +# +# You should have received a copy of the GNU Lesser General Public License +# version 3 along with OpenOffice.org. If not, see +# <http://www.openoffice.org/license.html> +# for a copy of the LGPLv3 License. +# +#************************************************************************* + +PRJ=..$/.. + +PRJNAME=oox +TARGET=xls +AUTOSEG=true + +ENABLE_EXCEPTIONS=TRUE + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk +.INCLUDE: $(PRJ)$/util$/makefile.pmk + +# --- Files -------------------------------------------------------- + +SLOFILES = \ + $(SLO)$/addressconverter.obj \ + $(SLO)$/autofiltercontext.obj \ + $(SLO)$/biffcodec.obj \ + $(SLO)$/biffdetector.obj \ + $(SLO)$/biffhelper.obj \ + $(SLO)$/biffinputstream.obj \ + $(SLO)$/biffoutputstream.obj \ + $(SLO)$/chartsheetfragment.obj \ + $(SLO)$/commentsbuffer.obj \ + $(SLO)$/commentsfragment.obj \ + $(SLO)$/condformatbuffer.obj \ + $(SLO)$/condformatcontext.obj \ + $(SLO)$/connectionsfragment.obj \ + $(SLO)$/defnamesbuffer.obj \ + $(SLO)$/drawingfragment.obj \ + $(SLO)$/excelchartconverter.obj \ + $(SLO)$/excelfilter.obj \ + $(SLO)$/excelhandlers.obj \ + $(SLO)$/excelvbaproject.obj \ + $(SLO)$/externallinkbuffer.obj \ + $(SLO)$/externallinkfragment.obj \ + $(SLO)$/formulabase.obj \ + $(SLO)$/formulaparser.obj \ + $(SLO)$/numberformatsbuffer.obj \ + $(SLO)$/ooxformulaparser.obj \ + $(SLO)$/pagesettings.obj \ + $(SLO)$/pivotcachebuffer.obj \ + $(SLO)$/pivotcachefragment.obj \ + $(SLO)$/pivottablebuffer.obj \ + $(SLO)$/pivottablefragment.obj \ + $(SLO)$/querytablefragment.obj \ + $(SLO)$/richstring.obj \ + $(SLO)$/richstringcontext.obj \ + $(SLO)$/scenariobuffer.obj \ + $(SLO)$/scenariocontext.obj \ + $(SLO)$/sharedformulabuffer.obj \ + $(SLO)$/sharedstringsbuffer.obj \ + $(SLO)$/sharedstringsfragment.obj \ + $(SLO)$/sheetdatacontext.obj \ + $(SLO)$/stylesbuffer.obj \ + $(SLO)$/stylesfragment.obj \ + $(SLO)$/tablebuffer.obj \ + $(SLO)$/tablefragment.obj \ + $(SLO)$/themebuffer.obj \ + $(SLO)$/unitconverter.obj \ + $(SLO)$/viewsettings.obj \ + $(SLO)$/webquerybuffer.obj \ + $(SLO)$/workbookfragment.obj \ + $(SLO)$/workbookhelper.obj \ + $(SLO)$/workbooksettings.obj \ + $(SLO)$/worksheetbuffer.obj \ + $(SLO)$/worksheetfragment.obj \ + $(SLO)$/worksheethelper.obj \ + $(SLO)$/worksheetsettings.obj + +# --- Targets ------------------------------------------------------- + +.INCLUDE : target.mk diff --git a/oox/source/xls/numberformatsbuffer.cxx b/oox/source/xls/numberformatsbuffer.cxx new file mode 100644 index 000000000000..4dfb00abeb59 --- /dev/null +++ b/oox/source/xls/numberformatsbuffer.cxx @@ -0,0 +1,2123 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/numberformatsbuffer.hxx" +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormats.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/i18n/NumberFormatIndex.hpp> +#include <osl/thread.h> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertymap.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OString; +using ::rtl::OStringBuffer; +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::rtl::OStringToOUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::lang::Locale; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::com::sun::star::util::XNumberFormats; +using ::com::sun::star::util::XNumberFormatTypes; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +/** Stores the number format used in Calc for an Excel built-in number format. */ +struct BuiltinFormat +{ + sal_Int32 mnNumFmtId; /// Built-in number format index. + const sal_Char* mpcFmtCode; /// Format string, UTF-8, may be 0 (mnPredefId is used then). + sal_Int16 mnPredefId; /// Predefined format index, if mpcFmtCode is 0. + sal_Int32 mnReuseId; /// Use this format, if mpcFmtCode is 0 and mnPredefId is -1. +}; + +/** Defines a literal built-in number format. */ +#define NUMFMT_STRING( INDEX, FORMATCODE ) \ + { INDEX, FORMATCODE, -1, -1 } + +/** Defines a built-in number format that maps to an own predefined format. */ +#define NUMFMT_PREDEF( INDEX, PREDEFINED ) \ + { INDEX, 0, ::com::sun::star::i18n::NumberFormatIndex::PREDEFINED, -1 } + +/** Defines a built-in number format that is the same as the specified in nReuseId. */ +#define NUMFMT_REUSE( INDEX, REUSED_INDEX ) \ + { INDEX, 0, -1, REUSED_INDEX } + +/** Terminates a built-in number format table. */ +#define NUMFMT_ENDTABLE() \ + { -1, 0, -1, -1 } + +/** Defines builtin date and time formats 14...22. + @param SYSTEMDATE Complete short system date (for formats 14 and 22). + @param DAY Day format (for formats 15 and 16). + @param DAYSEP Separator between day and month (for formats 15 and 16). + @param MONTH Month format (for formats 15...17). + @param MONTHSEP Separator between month and year (for formats 15 and 17). + @param YEAR Year format (for formats 15 and 17). + @param HOUR12 Hour format for 12-hour AM/PM formats (formats 18 and 19). + @param HOUR24 Hour format for 24-hour formats (formats 20...22). */ +#define NUMFMT_ALLDATETIMES( SYSTEMDATE, DAY, DAYSEP, MONTH, MONTHSEP, YEAR, HOUR12, HOUR24 ) \ + NUMFMT_STRING( 14, SYSTEMDATE ), \ + NUMFMT_STRING( 15, DAY DAYSEP MONTH MONTHSEP YEAR ), \ + NUMFMT_STRING( 16, DAY DAYSEP MONTH ), \ + NUMFMT_STRING( 17, MONTH MONTHSEP YEAR ), \ + NUMFMT_STRING( 18, HOUR12 ":mm AM/PM" ), \ + NUMFMT_STRING( 19, HOUR12 ":mm:ss AM/PM" ), \ + NUMFMT_STRING( 20, HOUR24 ":mm" ), \ + NUMFMT_STRING( 21, HOUR24 ":mm:ss" ), \ + NUMFMT_STRING( 22, SYSTEMDATE " " HOUR24 ":mm" ) + +/** Defines builtin time formats INDEX and INDEX+1 for CJK locales. + @param INDEX First number format index. + @param HOURFORMAT Hour format. + @param HOUR Hour symbol. + @param MINUTE Minute symbol. + @param SECOND Second symbol. */ +#define NUMFMT_TIME_CJK( INDEX, HOURFORMAT, HOUR, MINUTE, SECOND ) \ + NUMFMT_STRING( INDEX + 0, HOURFORMAT "\"" HOUR "\"mm\"" MINUTE "\"" ), \ + NUMFMT_STRING( INDEX + 1, HOURFORMAT "\"" HOUR "\"mm\"" MINUTE "\"ss\"" SECOND "\"" ) + +/** Defines builtin time formats 32...35 for CJK locales. + @param HOUR12 Hour format for 12-hour AM/PM formats (formats 34 and 35). + @param HOUR24 Hour format for 24-hour formats (formats 32 and 33). + @param HOUR Hour symbol. + @param MINUTE Minute symbol. + @param SECOND Second symbol. */ +#define NUMFMT_ALLTIMES_CJK( HOUR12, HOUR24, HOUR, MINUTE, SECOND ) \ + NUMFMT_TIME_CJK( 32, HOUR24, HOUR, MINUTE, SECOND ), \ + NUMFMT_TIME_CJK( 34, "AM/PM" HOUR12, HOUR, MINUTE, SECOND ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "symbol, [minus], number". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_SYMBOL_MINUS_NUMBER( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF SYMBOL SPACE "#,##0;" MODIF SYMBOL SPACE "-#,##0" ), \ + NUMFMT_STRING( INDEX + 1, MODIF SYMBOL SPACE "#,##0;" "[RED]" MODIF SYMBOL SPACE "-#,##0" ), \ + NUMFMT_STRING( INDEX + 2, MODIF SYMBOL SPACE "#,##0.00;" MODIF SYMBOL SPACE "-#,##0.00" ), \ + NUMFMT_STRING( INDEX + 3, MODIF SYMBOL SPACE "#,##0.00;" "[RED]" MODIF SYMBOL SPACE "-#,##0.00" ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following format: + "symbol, [minus], number". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ACCOUNTING_SYMBOL_MINUS_NUMBER( INDEX, SYMBOL, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_ " "* #,##0_ ;" "_ " "* -#,##0_ ;" "_ " "* \"-\"_ ;" "_ @_ " ), \ + NUMFMT_STRING( INDEX + 1, "_ " SYMBOL SPACE "* #,##0_ ;" "_ " SYMBOL SPACE "* -#,##0_ ;" "_ " SYMBOL SPACE "* \"-\"_ ;" "_ @_ " ), \ + NUMFMT_STRING( INDEX + 2, "_ " "* #,##0.00_ ;" "_ " "* -#,##0.00_ ;" "_ " "* \"-\"?\?_ ;" "_ @_ " ), \ + NUMFMT_STRING( INDEX + 3, "_ " SYMBOL SPACE "* #,##0.00_ ;" "_ " SYMBOL SPACE "* -#,##0.00_ ;" "_ " SYMBOL SPACE "* \"-\"?\?_ ;" "_ @_ " ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following format: + "symbol, [minus], number". + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( SYMBOL, SPACE ) \ + NUMFMT_CURRENCY_SYMBOL_MINUS_NUMBER( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_SYMBOL_MINUS_NUMBER( 37, "", "", "" ), \ + NUMFMT_ACCOUNTING_SYMBOL_MINUS_NUMBER( 41, SYMBOL, SPACE ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "symbol, number, [minus]". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_SYMBOL_NUMBER_MINUS( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF SYMBOL SPACE "#,##0_-;" MODIF SYMBOL SPACE "#,##0-" ), \ + NUMFMT_STRING( INDEX + 1, MODIF SYMBOL SPACE "#,##0_-;" "[RED]" MODIF SYMBOL SPACE "#,##0-" ), \ + NUMFMT_STRING( INDEX + 2, MODIF SYMBOL SPACE "#,##0.00_-;" MODIF SYMBOL SPACE "#,##0.00-" ), \ + NUMFMT_STRING( INDEX + 3, MODIF SYMBOL SPACE "#,##0.00_-;" "[RED]" MODIF SYMBOL SPACE "#,##0.00-" ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following format: + "symbol, number, [minus]". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ACCOUNTING_SYMBOL_NUMBER_MINUS( INDEX, SYMBOL, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_-" "* #,##0_-;" "_-" "* #,##0-;" "_-" "* \"-\"_-;" "_-@_-" ), \ + NUMFMT_STRING( INDEX + 1, "_-" SYMBOL SPACE "* #,##0_-;" "_-" SYMBOL SPACE "* #,##0-;" "_-" SYMBOL SPACE "* \"-\"_-;" "_-@_-" ), \ + NUMFMT_STRING( INDEX + 2, "_-" "* #,##0.00_-;" "_-" "* #,##0.00-;" "_-" "* \"-\"?\?_-;" "_-@_-" ), \ + NUMFMT_STRING( INDEX + 3, "_-" SYMBOL SPACE "* #,##0.00_-;" "_-" SYMBOL SPACE "* #,##0.00-;" "_-" SYMBOL SPACE "* \"-\"?\?_-;" "_-@_-" ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following format: + "symbol, number, [minus]". + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( SYMBOL, SPACE ) \ + NUMFMT_CURRENCY_SYMBOL_NUMBER_MINUS( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_SYMBOL_NUMBER_MINUS( 37, "", "", "" ), \ + NUMFMT_ACCOUNTING_SYMBOL_NUMBER_MINUS( 41, SYMBOL, SPACE ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "number, symbol, [minus]". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between number and currency symbol. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_NUMBER_SYMBOL_MINUS( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF "#,##0" SPACE SYMBOL "_-;" MODIF "#,##0" SPACE SYMBOL "-" ), \ + NUMFMT_STRING( INDEX + 1, MODIF "#,##0" SPACE SYMBOL "_-;" "[RED]" MODIF "#,##0" SPACE SYMBOL "-" ), \ + NUMFMT_STRING( INDEX + 2, MODIF "#,##0.00" SPACE SYMBOL "_-;" MODIF "#,##0.00" SPACE SYMBOL "-" ), \ + NUMFMT_STRING( INDEX + 3, MODIF "#,##0.00" SPACE SYMBOL "_-;" "[RED]" MODIF "#,##0.00" SPACE SYMBOL "-" ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following format: + "number, symbol, [minus]". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param BLINDS Blind currency symbol. + @param SPACE Space character(s) between number and currency symbol. */ +#define NUMFMT_ACCOUNTING_NUMBER_SYMBOL_MINUS( INDEX, SYMBOL, BLINDS, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_-* #,##0" SPACE BLINDS "_-;_-* #,##0" SPACE BLINDS "-;_-* \"-\"" SPACE BLINDS "_-;_-@_-" ), \ + NUMFMT_STRING( INDEX + 1, "_-* #,##0" SPACE SYMBOL "_-;_-* #,##0" SPACE SYMBOL "-;_-* \"-\"" SPACE SYMBOL "_-;_-@_-" ), \ + NUMFMT_STRING( INDEX + 2, "_-* #,##0.00" SPACE BLINDS "_-;_-* #,##0.00" SPACE BLINDS "-;_-* \"-\"?\?" SPACE BLINDS "_-;_-@_-" ), \ + NUMFMT_STRING( INDEX + 3, "_-* #,##0.00" SPACE SYMBOL "_-;_-* #,##0.00" SPACE SYMBOL "-;_-* \"-\"?\?" SPACE SYMBOL "_-;_-@_-" ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following format: + "number, symbol, [minus]". + @param SYMBOL Currency symbol. + @param BLINDS Blind currency symbol. + @param SPACE Space character(s) between number and currency symbol. */ +#define NUMFMT_ALLCURRENCIES_NUMBER_SYMBOL_MINUS( SYMBOL, BLINDS, SPACE ) \ + NUMFMT_CURRENCY_NUMBER_SYMBOL_MINUS( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_NUMBER_SYMBOL_MINUS( 37, BLINDS, SPACE, "" ), \ + NUMFMT_ACCOUNTING_NUMBER_SYMBOL_MINUS( 41, SYMBOL, BLINDS, SPACE ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "[minus], symbol, number". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_MINUS_SYMBOL_NUMBER( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF SYMBOL SPACE "#,##0;" MODIF "-" SYMBOL SPACE "#,##0" ), \ + NUMFMT_STRING( INDEX + 1, MODIF SYMBOL SPACE "#,##0;" "[RED]" MODIF "-" SYMBOL SPACE "#,##0" ), \ + NUMFMT_STRING( INDEX + 2, MODIF SYMBOL SPACE "#,##0.00;" MODIF "-" SYMBOL SPACE "#,##0.00" ), \ + NUMFMT_STRING( INDEX + 3, MODIF SYMBOL SPACE "#,##0.00;" "[RED]" MODIF "-" SYMBOL SPACE "#,##0.00" ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following order: + "[minus], symbol, number". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ACCOUNTING_MINUS_SYMBOL_NUMBER( INDEX, SYMBOL, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_-" "* #,##0_-;" "-" "* #,##0_-;" "_-" "* \"-\"_-;" "_-@_-" ), \ + NUMFMT_STRING( INDEX + 1, "_-" SYMBOL SPACE "* #,##0_-;" "-" SYMBOL SPACE "* #,##0_-;" "_-" SYMBOL SPACE "* \"-\"_-;" "_-@_-" ), \ + NUMFMT_STRING( INDEX + 2, "_-" "* #,##0.00_-;" "-" "* #,##0.00_-;" "_-" "* \"-\"?\?_-;" "_-@_-" ), \ + NUMFMT_STRING( INDEX + 3, "_-" SYMBOL SPACE "* #,##0.00_-;" "-" SYMBOL SPACE "* #,##0.00_-;" "_-" SYMBOL SPACE "* \"-\"?\?_-;" "_-@_-" ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following order: + "[minus], symbol, number". + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( SYMBOL, SPACE ) \ + NUMFMT_CURRENCY_MINUS_SYMBOL_NUMBER( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_MINUS_SYMBOL_NUMBER( 37, "", "", "" ), \ + NUMFMT_ACCOUNTING_MINUS_SYMBOL_NUMBER( 41, SYMBOL, SPACE ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "[minus], number, symbol". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between number and currency symbol. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_MINUS_NUMBER_SYMBOL( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF "#,##0" SPACE SYMBOL ";" MODIF "-#,##0" SPACE SYMBOL ), \ + NUMFMT_STRING( INDEX + 1, MODIF "#,##0" SPACE SYMBOL ";" "[RED]" MODIF "-#,##0" SPACE SYMBOL ), \ + NUMFMT_STRING( INDEX + 2, MODIF "#,##0.00" SPACE SYMBOL ";" MODIF "-#,##0.00" SPACE SYMBOL ), \ + NUMFMT_STRING( INDEX + 3, MODIF "#,##0.00" SPACE SYMBOL ";" "[RED]" MODIF "-#,##0.00" SPACE SYMBOL ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following format: + "[minus], number, symbol". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param BLINDS Blind currency symbol. + @param SPACE Space character(s) between number and currency symbol. */ +#define NUMFMT_ACCOUNTING_MINUS_NUMBER_SYMBOL( INDEX, SYMBOL, BLINDS, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_-* #,##0" SPACE BLINDS "_-;-* #,##0" SPACE BLINDS "_-;_-* \"-\"" SPACE BLINDS "_-;_-@_-" ), \ + NUMFMT_STRING( INDEX + 1, "_-* #,##0" SPACE SYMBOL "_-;-* #,##0" SPACE SYMBOL "_-;_-* \"-\"" SPACE SYMBOL "_-;_-@_-" ), \ + NUMFMT_STRING( INDEX + 2, "_-* #,##0.00" SPACE BLINDS "_-;-* #,##0.00" SPACE BLINDS "_-;_-* \"-\"?\?" SPACE BLINDS "_-;_-@_-" ), \ + NUMFMT_STRING( INDEX + 3, "_-* #,##0.00" SPACE SYMBOL "_-;-* #,##0.00" SPACE SYMBOL "_-;_-* \"-\"?\?" SPACE SYMBOL "_-;_-@_-" ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following format: + "[minus], number, symbol". + @param SYMBOL Currency symbol. + @param BLINDS Blind currency symbol. + @param SPACE Space character(s) between number and currency symbol. */ +#define NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( SYMBOL, BLINDS, SPACE ) \ + NUMFMT_CURRENCY_MINUS_NUMBER_SYMBOL( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_MINUS_NUMBER_SYMBOL( 37, BLINDS, SPACE, "" ), \ + NUMFMT_ACCOUNTING_MINUS_NUMBER_SYMBOL( 41, SYMBOL, BLINDS, SPACE ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "[opening parenthesis], symbol, number, [closing parenthesis].". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF SYMBOL SPACE "#,##0_);" MODIF "(" SYMBOL SPACE "#,##0)" ), \ + NUMFMT_STRING( INDEX + 1, MODIF SYMBOL SPACE "#,##0_);" "[RED]" MODIF "(" SYMBOL SPACE "#,##0)" ), \ + NUMFMT_STRING( INDEX + 2, MODIF SYMBOL SPACE "#,##0.00_);" MODIF "(" SYMBOL SPACE "#,##0.00)" ), \ + NUMFMT_STRING( INDEX + 3, MODIF SYMBOL SPACE "#,##0.00_);" "[RED]" MODIF "(" SYMBOL SPACE "#,##0.00)" ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following format: + "[opening parenthesis], symbol, number, [closing parenthesis].". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ACCOUNTING_OPEN_SYMBOL_NUMBER_CLOSE( INDEX, SYMBOL, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_(" "* #,##0_);" "_(" "* (#,##0);" "_(" "* \"-\"_);" "_(@_)" ), \ + NUMFMT_STRING( INDEX + 1, "_(" SYMBOL SPACE "* #,##0_);" "_(" SYMBOL SPACE "* (#,##0);" "_(" SYMBOL SPACE "* \"-\"_);" "_(@_)" ), \ + NUMFMT_STRING( INDEX + 2, "_(" "* #,##0.00_);" "_(" "* (#,##0.00);" "_(" "* \"-\"?\?_);" "_(@_)" ), \ + NUMFMT_STRING( INDEX + 3, "_(" SYMBOL SPACE "* #,##0.00_);" "_(" SYMBOL SPACE "* (#,##0.00);" "_(" SYMBOL SPACE "* \"-\"?\?_);" "_(@_)" ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following format: + "[opening parenthesis], symbol, number, [closing parenthesis].". + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between currency symbol and number. */ +#define NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( SYMBOL, SPACE ) \ + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 37, "", "", "" ), \ + NUMFMT_ACCOUNTING_OPEN_SYMBOL_NUMBER_CLOSE( 41, SYMBOL, SPACE ) + +/** Defines builtin currency formats INDEX...INDEX+3 in the following format: + "[opening parenthesis], number, symbol, [closing parenthesis].". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param SPACE Space character(s) between number and currency symbol. + @param MODIF Leading modifier for each portion (e.g. "t" for Thai formats). */ +#define NUMFMT_CURRENCY_OPEN_NUMBER_SYMBOL_CLOSE( INDEX, SYMBOL, SPACE, MODIF ) \ + NUMFMT_STRING( INDEX + 0, MODIF "#,##0" SPACE SYMBOL "_);" MODIF "(#,##0" SPACE SYMBOL ")" ), \ + NUMFMT_STRING( INDEX + 1, MODIF "#,##0" SPACE SYMBOL "_);" "[RED]" MODIF "(#,##0" SPACE SYMBOL ")" ), \ + NUMFMT_STRING( INDEX + 2, MODIF "#,##0.00" SPACE SYMBOL "_);" MODIF "(#,##0.00" SPACE SYMBOL ")" ), \ + NUMFMT_STRING( INDEX + 3, MODIF "#,##0.00" SPACE SYMBOL "_);" "[RED]" MODIF "(#,##0.00" SPACE SYMBOL ")" ) + +/** Defines builtin accounting formats INDEX...INDEX+3 in the following format: + "[opening parenthesis], number, symbol, [closing parenthesis].". + @param INDEX First number format index. + @param SYMBOL Currency symbol. + @param BLINDS Blind currency symbol. + @param SPACE Space character(s) between number and currency symbol. */ +#define NUMFMT_ACCOUNTING_OPEN_NUMBER_SYMBOL_CLOSE( INDEX, SYMBOL, BLINDS, SPACE ) \ + NUMFMT_STRING( INDEX + 0, "_ * #,##0_)" SPACE BLINDS "_ ;_ * (#,##0)" SPACE BLINDS "_ ;_ * \"-\"_)" SPACE BLINDS "_ ;_ @_ " ), \ + NUMFMT_STRING( INDEX + 1, "_ * #,##0_)" SPACE SYMBOL "_ ;_ * (#,##0)" SPACE SYMBOL "_ ;_ * \"-\"_)" SPACE SYMBOL "_ ;_ @_ " ), \ + NUMFMT_STRING( INDEX + 2, "_ * #,##0.00_)" SPACE BLINDS "_ ;_ * (#,##0.00)" SPACE BLINDS "_ ;_ * \"-\"?\?_)" SPACE BLINDS "_ ;_ @_ " ), \ + NUMFMT_STRING( INDEX + 3, "_ * #,##0.00_)" SPACE SYMBOL "_ ;_ * (#,##0.00)" SPACE SYMBOL "_ ;_ * \"-\"?\?_)" SPACE SYMBOL "_ ;_ @_ " ) + +/** Defines builtin currency formats 5...8 (with currency symbol), 37...40 + (blind currency symbol), and 41...44 (accounting), in the following format: + "[opening parenthesis], number, symbol, [closing parenthesis].". + @param SYMBOL Currency symbol. + @param BLINDS Blind currency symbol. + @param SPACE Space character(s) between number and currency symbol. */ +#define NUMFMT_ALLCURRENCIES_OPEN_NUMBER_SYMBOL_CLOSE( SYMBOL, BLINDS, SPACE ) \ + NUMFMT_CURRENCY_OPEN_NUMBER_SYMBOL_CLOSE( 5, SYMBOL, SPACE, "" ), \ + NUMFMT_CURRENCY_OPEN_NUMBER_SYMBOL_CLOSE( 37, BLINDS, SPACE, "" ), \ + NUMFMT_ACCOUNTING_OPEN_NUMBER_SYMBOL_CLOSE( 41, SYMBOL, BLINDS, SPACE ) + +// currency unit characters +#define UTF8_BAHT "\340\270\277" +#define UTF8_COLON "\342\202\241" +#define UTF8_CURR_AR_AE "\330\257.\330\245." +#define UTF8_CURR_AR_BH "\330\257.\330\250." +#define UTF8_CURR_AR_DZ "\330\257.\330\254." +#define UTF8_CURR_AR_EG "\330\254.\331\205." +#define UTF8_CURR_AR_IQ "\330\257.\330\271." +#define UTF8_CURR_AR_JO "\330\257.\330\247." +#define UTF8_CURR_AR_KW "\330\257.\331\203." +#define UTF8_CURR_AR_LB "\331\204.\331\204." +#define UTF8_CURR_AR_LY "\330\257.\331\204." +#define UTF8_CURR_AR_MA "\330\257.\331\205." +#define UTF8_CURR_AR_OM "\330\261.\330\271." +#define UTF8_CURR_AR_QA "\330\261.\331\202." +#define UTF8_CURR_AR_SA "\330\261.\330\263." +#define UTF8_CURR_AR_SY "\331\204.\330\263." +#define UTF8_CURR_AR_TN "\330\257.\330\252." +#define UTF8_CURR_AR_YE "\330\261.\331\212." +#define UTF8_CURR_BN_IN "\340\246\237\340\246\276" +#define UTF8_CURR_FA_IR "\330\261\331\212\330\247\331\204" +#define UTF8_CURR_GU_IN "\340\252\260\340\253\202" +#define UTF8_CURR_HI_IN "\340\244\260\340\245\201" +#define UTF8_CURR_KN_IN "\340\262\260\340\263\202" +#define UTF8_CURR_ML_IN "\340\264\225" +#define UTF8_CURR_PA_IN "\340\250\260\340\251\201" +#define UTF8_CURR_TA_IN "\340\256\260\340\257\202" +#define UTF8_CURR_TE_IN "\340\260\260\340\261\202" +#define UTF8_DONG "\342\202\253" +#define UTF8_EURO "\342\202\254" +#define UTF8_POUND_GB "\302\243" +#define UTF8_RUFIYAA "\336\203" +#define UTF8_SHEQEL "\342\202\252" +#define UTF8_TUGRUG "\342\202\256" +#define UTF8_WON "\342\202\251" +#define UTF8_YEN_CN "\357\277\245" +#define UTF8_YEN_JP "\302\245" + +// Unicode characters for currency units +#define UTF8_CCARON_LC "\304\215" +#define UTF8_LSTROKE_LC "\305\202" +// Armenian +#define UTF8_HY_DA_LC "\325\244" +#define UTF8_HY_REH_LC "\326\200" +// Cyrillic +#define UTF8_CYR_G_LC "\320\263" +#define UTF8_CYR_L_LC "\320\273" +#define UTF8_CYR_M_LC "\320\274" +#define UTF8_CYR_N_LC "\320\275" +#define UTF8_CYR_O_LC "\320\276" +#define UTF8_CYR_R_LC "\321\200" +#define UTF8_CYR_S_LC "\321\201" +#define UTF8_CYR_W_LC "\320\262" + +// 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_YEAR "\345\271\264" +#define UTF8_CS_MON "\346\234\210" +#define UTF8_CS_DAY "\346\227\245" +#define UTF8_CS_HOUR "\346\227\266" +#define UTF8_CS_MIN "\345\210\206" +#define UTF8_CS_SEC "\347\247\222" + +// 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 locales. */ +static const BuiltinFormat spBuiltinFormats_BASE[] = +{ + // 0..13 numeric and currency formats + NUMFMT_PREDEF( 0, NUMBER_STANDARD ), // General + NUMFMT_PREDEF( 1, NUMBER_INT ), // 0 + NUMFMT_PREDEF( 2, NUMBER_DEC2 ), // 0.00 + NUMFMT_PREDEF( 3, NUMBER_1000INT ), // #,##0 + NUMFMT_PREDEF( 4, NUMBER_1000DEC2 ), // #,##0.00 + NUMFMT_PREDEF( 5, CURRENCY_1000INT ), // #,##0[symbol] + NUMFMT_PREDEF( 6, CURRENCY_1000INT_RED ), // #,##0[symbol];[RED]-#,##0[symbol] + NUMFMT_PREDEF( 7, CURRENCY_1000DEC2 ), // #,##0.00[symbol] + NUMFMT_PREDEF( 8, CURRENCY_1000DEC2_RED ), // #,##0.00[symbol];[RED]-#,##0.00[symbol] + NUMFMT_PREDEF( 9, PERCENT_INT ), // 0% + NUMFMT_PREDEF( 10, PERCENT_DEC2 ), // 0.00% + NUMFMT_PREDEF( 11, SCIENTIFIC_000E00 ), // 0.00E+00 + NUMFMT_PREDEF( 12, FRACTION_1 ), // # ?/? + NUMFMT_PREDEF( 13, FRACTION_2 ), // # ??/?? + + // 14...22 date and time formats + NUMFMT_PREDEF( 14, DATE_SYS_DDMMYYYY ), + NUMFMT_PREDEF( 15, DATE_SYS_DMMMYY ), + NUMFMT_PREDEF( 16, DATE_SYS_DDMMM ), + NUMFMT_PREDEF( 17, DATE_SYS_MMYY ), + NUMFMT_PREDEF( 18, TIME_HHMMAMPM ), + NUMFMT_PREDEF( 19, TIME_HHMMSSAMPM ), + NUMFMT_PREDEF( 20, TIME_HHMM ), + NUMFMT_PREDEF( 21, TIME_HHMMSS ), + NUMFMT_PREDEF( 22, DATETIME_SYSTEM_SHORT_HHMM ), + + // 23...36 international formats + NUMFMT_REUSE( 23, 0 ), + NUMFMT_REUSE( 24, 0 ), + NUMFMT_REUSE( 25, 0 ), + NUMFMT_REUSE( 26, 0 ), + NUMFMT_REUSE( 27, 14 ), + NUMFMT_REUSE( 28, 14 ), + NUMFMT_REUSE( 29, 14 ), + NUMFMT_REUSE( 30, 14 ), + NUMFMT_REUSE( 31, 14 ), + NUMFMT_REUSE( 32, 21 ), + NUMFMT_REUSE( 33, 21 ), + NUMFMT_REUSE( 34, 21 ), + NUMFMT_REUSE( 35, 21 ), + NUMFMT_REUSE( 36, 14 ), + + // 37...44 accounting formats, defaults without currency symbol here + NUMFMT_CURRENCY_MINUS_SYMBOL_NUMBER( 37, "", "", "" ), + NUMFMT_ACCOUNTING_MINUS_SYMBOL_NUMBER( 41, "", "" ), + + // 45...49 more special formats + NUMFMT_STRING( 45, "mm:ss" ), + NUMFMT_STRING( 46, "[h]:mm:ss" ), + NUMFMT_STRING( 47, "mm:ss.0" ), + NUMFMT_STRING( 48, "##0.0E+0" ), + NUMFMT_PREDEF( 49, TEXT ), + + // 50...81 international formats + NUMFMT_REUSE( 50, 14 ), + NUMFMT_REUSE( 51, 14 ), + NUMFMT_REUSE( 52, 14 ), + NUMFMT_REUSE( 53, 14 ), + NUMFMT_REUSE( 54, 14 ), + NUMFMT_REUSE( 55, 14 ), + NUMFMT_REUSE( 56, 14 ), + NUMFMT_REUSE( 57, 14 ), + NUMFMT_REUSE( 58, 14 ), + NUMFMT_REUSE( 59, 1 ), + NUMFMT_REUSE( 60, 2 ), + NUMFMT_REUSE( 61, 3 ), + NUMFMT_REUSE( 62, 4 ), + NUMFMT_REUSE( 63, 5 ), + NUMFMT_REUSE( 64, 6 ), + NUMFMT_REUSE( 65, 7 ), + NUMFMT_REUSE( 66, 8 ), + NUMFMT_REUSE( 67, 9 ), + NUMFMT_REUSE( 68, 10 ), + NUMFMT_REUSE( 69, 12 ), + NUMFMT_REUSE( 70, 13 ), + NUMFMT_REUSE( 71, 14 ), + NUMFMT_REUSE( 72, 14 ), + NUMFMT_REUSE( 73, 15 ), + NUMFMT_REUSE( 74, 16 ), + NUMFMT_REUSE( 75, 17 ), + NUMFMT_REUSE( 76, 20 ), + NUMFMT_REUSE( 77, 21 ), + NUMFMT_REUSE( 78, 22 ), + NUMFMT_REUSE( 79, 45 ), + NUMFMT_REUSE( 80, 46 ), + NUMFMT_REUSE( 81, 47 ), + + // 82...163 not used, must not occur in a file (Excel may crash) + + NUMFMT_ENDTABLE() +}; + +// ---------------------------------------------------------------------------- + +/** Arabic, U.A.E. */ +static const BuiltinFormat spBuiltinFormats_ar_AE[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_AE "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Bahrain. */ +static const BuiltinFormat spBuiltinFormats_ar_BH[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_BH "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Algeria. */ +static const BuiltinFormat spBuiltinFormats_ar_DZ[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_DZ "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Egypt. */ +static const BuiltinFormat spBuiltinFormats_ar_EG[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_EG "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Iraq. */ +static const BuiltinFormat spBuiltinFormats_ar_IQ[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_IQ "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Jordan. */ +static const BuiltinFormat spBuiltinFormats_ar_JO[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_JO "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Kuwait. */ +static const BuiltinFormat spBuiltinFormats_ar_KW[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_KW "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Lebanon. */ +static const BuiltinFormat spBuiltinFormats_ar_LB[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_LB "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Libya. */ +static const BuiltinFormat spBuiltinFormats_ar_LY[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_LY "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Morocco. */ +static const BuiltinFormat spBuiltinFormats_ar_MA[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_MA "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Oman. */ +static const BuiltinFormat spBuiltinFormats_ar_OM[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_OM "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Qatar. */ +static const BuiltinFormat spBuiltinFormats_ar_QA[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_QA "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Saudi Arabia. */ +static const BuiltinFormat spBuiltinFormats_ar_SA[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_SA "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Syria. */ +static const BuiltinFormat spBuiltinFormats_ar_SY[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_SY "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Tunisia. */ +static const BuiltinFormat spBuiltinFormats_ar_TN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_TN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Arabic, Yemen. */ +static const BuiltinFormat spBuiltinFormats_ar_YE[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_AR_YE "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Belarusian, Belarus. */ +static const BuiltinFormat spBuiltinFormats_be_BY[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_CYR_R_LC ".\"", "_" UTF8_CYR_R_LC "_.", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Bulgarian, Bulgaria. */ +static const BuiltinFormat spBuiltinFormats_bg_BG[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.M.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_CYR_L_LC UTF8_CYR_W_LC "\"", "_" UTF8_CYR_L_LC "_" UTF8_CYR_W_LC, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Bengali, India. */ +static const BuiltinFormat spBuiltinFormats_bn_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_BN_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Czech, Czech Republic. */ +static const BuiltinFormat spBuiltinFormats_cs_CZ[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"K" UTF8_CCARON_LC "\"", "_K_" UTF8_CCARON_LC, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Danish, Denmark. */ +static const BuiltinFormat spBuiltinFormats_da_DK[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"kr\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** German, Austria. */ +static const BuiltinFormat spBuiltinFormats_de_AT[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** German, Switzerland. */ +static const BuiltinFormat spBuiltinFormats_de_CH[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ". ", "MMM", " ", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"SFr.\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** German, Germany. */ +static const BuiltinFormat spBuiltinFormats_de_DE[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ". ", "MMM", " ", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** German, Liechtenstein. */ +static const BuiltinFormat spBuiltinFormats_de_LI[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ". ", "MMM", " ", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"CHF\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** German, Luxembourg. */ +static const BuiltinFormat spBuiltinFormats_de_LU[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Divehi, Maldives. */ +static const BuiltinFormat spBuiltinFormats_div_MV[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_NUMBER_SYMBOL_MINUS( "\"" UTF8_RUFIYAA ".\"", "_" UTF8_RUFIYAA "_.", " " ), + NUMFMT_ENDTABLE() +}; + +/** Greek, Greece. */ +static const BuiltinFormat spBuiltinFormats_el_GR[] = +{ + NUMFMT_ALLDATETIMES( "D/M/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** English, Australia. */ +static const BuiltinFormat spBuiltinFormats_en_AU[] = +{ + NUMFMT_ALLDATETIMES( "D/MM/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Belize. */ +static const BuiltinFormat spBuiltinFormats_en_BZ[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"BZ$\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Canada. */ +static const BuiltinFormat spBuiltinFormats_en_CA[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Caribbean. */ +static const BuiltinFormat spBuiltinFormats_en_CB[] = +{ + NUMFMT_ALLDATETIMES( "MM/DD/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, United Kingdom. */ +static const BuiltinFormat spBuiltinFormats_en_GB[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_POUND_GB, "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Ireland. */ +static const BuiltinFormat spBuiltinFormats_en_IE[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_EURO, "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Jamaica. */ +static const BuiltinFormat spBuiltinFormats_en_JM[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "\"J$\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, New Zealand. */ +static const BuiltinFormat spBuiltinFormats_en_NZ[] = +{ + NUMFMT_ALLDATETIMES( "D/MM/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Philippines. */ +static const BuiltinFormat spBuiltinFormats_en_PH[] = +{ + NUMFMT_ALLDATETIMES( "M/D/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"Php\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, Trinidad and Tobago. */ +static const BuiltinFormat spBuiltinFormats_en_TT[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"TT$\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, USA. */ +static const BuiltinFormat spBuiltinFormats_en_US[] = +{ + NUMFMT_ALLDATETIMES( "M/D/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** English, South Africa. */ +static const BuiltinFormat spBuiltinFormats_en_ZA[] = +{ + NUMFMT_ALLDATETIMES( "YYYY/MM/DD", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\\R", " " ), + NUMFMT_ENDTABLE() +}; + +/** English, Zimbabwe. */ +static const BuiltinFormat spBuiltinFormats_en_ZW[] = +{ + NUMFMT_ALLDATETIMES( "M/D/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"Z$\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Argentina. */ +static const BuiltinFormat spBuiltinFormats_es_AR[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "$", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Bolivia. */ +static const BuiltinFormat spBuiltinFormats_es_BO[] = +{ + // slashes must be quoted to prevent conversion to minus + NUMFMT_ALLDATETIMES( "DD\\/MM\\/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"$b\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Chile. */ +static const BuiltinFormat spBuiltinFormats_es_CL[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Colombia. */ +static const BuiltinFormat spBuiltinFormats_es_CO[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Costa Rica. */ +static const BuiltinFormat spBuiltinFormats_es_CR[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( UTF8_COLON, "" ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Dominican Republic. */ +static const BuiltinFormat spBuiltinFormats_es_DO[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"RD$\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Ecuador. */ +static const BuiltinFormat spBuiltinFormats_es_EC[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Spain. */ +static const BuiltinFormat spBuiltinFormats_es_ES[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Guatemala. */ +static const BuiltinFormat spBuiltinFormats_es_GT[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\\Q", "" ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Honduras. */ +static const BuiltinFormat spBuiltinFormats_es_HN[] = +{ + // slashes must be quoted to prevent conversion to minus + NUMFMT_ALLDATETIMES( "DD\\/MM\\/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"L.\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Mexico. */ +static const BuiltinFormat spBuiltinFormats_es_MX[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Nicaragua. */ +static const BuiltinFormat spBuiltinFormats_es_NI[] = +{ + // slashes must be quoted to prevent conversion to minus + NUMFMT_ALLDATETIMES( "DD\\/MM\\/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"C$\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Panama. */ +static const BuiltinFormat spBuiltinFormats_es_PA[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"B/.\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Peru. */ +static const BuiltinFormat spBuiltinFormats_es_PE[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"S/.\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Puerto Rico. */ +static const BuiltinFormat spBuiltinFormats_es_PR[] = +{ + // slashes must be quoted to prevent conversion to minus + NUMFMT_ALLDATETIMES( "DD\\/MM\\/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Paraguay. */ +static const BuiltinFormat spBuiltinFormats_es_PY[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"Gs\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, El Salvador. */ +static const BuiltinFormat spBuiltinFormats_es_SV[] = +{ + // slashes must be quoted to prevent conversion to minus + NUMFMT_ALLDATETIMES( "DD\\/MM\\/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Uruguay. */ +static const BuiltinFormat spBuiltinFormats_es_UY[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"$U\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Spanish, Venezuela. */ +static const BuiltinFormat spBuiltinFormats_es_VE[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "Bs", " " ), + NUMFMT_ENDTABLE() +}; + +/** Estonian, Estonia. */ +static const BuiltinFormat spBuiltinFormats_et_EE[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "D.MM.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"kr\"", "_k_r", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Farsi, Iran. */ +static const BuiltinFormat spBuiltinFormats_fa_IR[] = +{ + NUMFMT_ALLDATETIMES( "YYYY/MM/DD", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"" UTF8_CURR_FA_IR "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Finnish, Finland. */ +static const BuiltinFormat spBuiltinFormats_fi_FI[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_STRING( 9, "0\\ %" ), + NUMFMT_STRING( 10, "0.00\\ %" ), + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Faroese, Faroe Islands. */ +static const BuiltinFormat spBuiltinFormats_fo_FO[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"kr\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** French, Belgium. */ +static const BuiltinFormat spBuiltinFormats_fr_BE[] = +{ + NUMFMT_ALLDATETIMES( "D/MM/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** French, Canada. */ +static const BuiltinFormat spBuiltinFormats_fr_CA[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "YYYY-MM-DD", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_NUMBER_SYMBOL_CLOSE( "$", "_$", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** French, Switzerland. */ +static const BuiltinFormat spBuiltinFormats_fr_CH[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"SFr.\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** French, France. */ +static const BuiltinFormat spBuiltinFormats_fr_FR[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** French, Luxembourg. */ +static const BuiltinFormat spBuiltinFormats_fr_LU[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** French, Monaco. */ +static const BuiltinFormat spBuiltinFormats_fr_MC[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Galizian, Spain. */ +static const BuiltinFormat spBuiltinFormats_gl_ES[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Gujarati, India. */ +static const BuiltinFormat spBuiltinFormats_gu_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_GU_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Hebrew, Israel. */ +static const BuiltinFormat spBuiltinFormats_he_IL[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( UTF8_SHEQEL, " " ), + NUMFMT_ENDTABLE() +}; + +/** Hindi, India. */ +static const BuiltinFormat spBuiltinFormats_hi_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_HI_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Croatian, Bosnia and Herzegowina. */ +static const BuiltinFormat spBuiltinFormats_hr_BA[] = +{ + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"KM\"", "_K_M", " " ), + NUMFMT_ENDTABLE() +}; + +/** Croatian, Croatia. */ +static const BuiltinFormat spBuiltinFormats_hr_HR[] = +{ + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"kn\"", "_k_n", " " ), + NUMFMT_ENDTABLE() +}; + +/** Hungarian, Hungary. */ +static const BuiltinFormat spBuiltinFormats_hu_HU[] = +{ + // space character is group separator, literal spaces must be quoted + // MMM is rendered differently in Calc and Excel (see #i41488#) + NUMFMT_ALLDATETIMES( "YYYY.MM.DD", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"Ft\"", "_F_t", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Armenian, Armenia. */ +static const BuiltinFormat spBuiltinFormats_hy_AM[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_HY_DA_LC UTF8_HY_REH_LC ".\"", "_" UTF8_HY_DA_LC "_" UTF8_HY_REH_LC "_.", " " ), + NUMFMT_ENDTABLE() +}; + +/** Indonesian, Indonesia. */ +static const BuiltinFormat spBuiltinFormats_id_ID[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"Rp\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** Icelandic, Iceland. */ +static const BuiltinFormat spBuiltinFormats_is_IS[] = +{ + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"kr.\"", "_k_r_.", " " ), + NUMFMT_ENDTABLE() +}; + +/** Italian, Switzerland. */ +static const BuiltinFormat spBuiltinFormats_it_CH[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"SFr.\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Italian, Italy. */ +static const BuiltinFormat spBuiltinFormats_it_IT[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Georgian, Georgia. */ +static const BuiltinFormat spBuiltinFormats_ka_GE[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"Lari\"", "_L_a_r_i", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Kazakh, Kazakhstan. */ +static const BuiltinFormat spBuiltinFormats_kk_KZ[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "\\T", "" ), + NUMFMT_ENDTABLE() +}; + +/** Kannada, India. */ +static const BuiltinFormat spBuiltinFormats_kn_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_KN_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Kyrgyz, Kyrgyzstan. */ +static const BuiltinFormat spBuiltinFormats_ky_KG[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_CYR_S_LC UTF8_CYR_O_LC UTF8_CYR_M_LC "\"", "_" UTF8_CYR_S_LC "_" UTF8_CYR_O_LC "_" UTF8_CYR_M_LC, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Lithuanian, Lithuania. */ +static const BuiltinFormat spBuiltinFormats_lt_LT[] = +{ + NUMFMT_ALLDATETIMES( "YYYY.MM.DD", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"Lt\"", "_L_t", " " ), + NUMFMT_ENDTABLE() +}; + +/** Latvian, Latvia. */ +static const BuiltinFormat spBuiltinFormats_lv_LV[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "YYYY.MM.DD", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "\"Ls\"", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Malayalam, India. */ +static const BuiltinFormat spBuiltinFormats_ml_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_ML_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Mongolian, Mongolia. */ +static const BuiltinFormat spBuiltinFormats_mn_MN[] = +{ + NUMFMT_ALLDATETIMES( "YY.MM.DD", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_TUGRUG, "_" UTF8_TUGRUG, "" ), + NUMFMT_ENDTABLE() +}; + +/** Malay, Brunei Darussalam. */ +static const BuiltinFormat spBuiltinFormats_ms_BN[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", "" ), + NUMFMT_ENDTABLE() +}; + +/** Malay, Malaysia. */ +static const BuiltinFormat spBuiltinFormats_ms_MY[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\\R", "" ), + NUMFMT_ENDTABLE() +}; + +/** Maltese, Malta. */ +static const BuiltinFormat spBuiltinFormats_mt_MT[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "\"Lm\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** Dutch, Belgium. */ +static const BuiltinFormat spBuiltinFormats_nl_BE[] = +{ + // slashes must be quoted to prevent conversion to minus + NUMFMT_ALLDATETIMES( "D\\/MM\\/YYYY", "D", "\\/", "MMM", "\\/", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Dutch, Netherlands. */ +static const BuiltinFormat spBuiltinFormats_nl_NL[] = +{ + NUMFMT_ALLDATETIMES( "D-M-YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Norwegian (Bokmal and Nynorsk), Norway. */ +static const BuiltinFormat spBuiltinFormats_no_NO[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"kr\"", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Punjabi, India. */ +static const BuiltinFormat spBuiltinFormats_pa_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YY", "DD", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_PA_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Polish, Poland. */ +static const BuiltinFormat spBuiltinFormats_pl_PL[] = +{ + // space character is group separator, literal spaces must be quoted + // MMM is rendered differently in Calc and Excel (see #i72300#) + NUMFMT_ALLDATETIMES( "YYYY-MM-DD", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"z" UTF8_LSTROKE_LC "\"", "_z_" UTF8_LSTROKE_LC, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Portugese, Brazil. */ +static const BuiltinFormat spBuiltinFormats_pt_BR[] = +{ + NUMFMT_ALLDATETIMES( "D/M/YYYY", "D", "/", "MMM", "/", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"R$\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Portugese, Portugal. */ +static const BuiltinFormat spBuiltinFormats_pt_PT[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, " " ), + NUMFMT_ENDTABLE() +}; + +/** Romanian, Romania. */ +static const BuiltinFormat spBuiltinFormats_ro_RO[] = +{ + // space character is group separator, literal spaces must be quoted (but see #i75367#) + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"lei\"", "_l_e_i", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Russian, Russian Federation. */ +static const BuiltinFormat spBuiltinFormats_ru_RU[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_CYR_R_LC ".\"", "_" UTF8_CYR_R_LC "_.", "" ), + NUMFMT_ENDTABLE() +}; + +/** Slovak, Slovakia. */ +static const BuiltinFormat spBuiltinFormats_sk_SK[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"Sk\"", "_S_k", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Slovenian, Slovenia. */ +static const BuiltinFormat spBuiltinFormats_sl_SI[] = +{ + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"SIT\"", "_S_I_T", " " ), + NUMFMT_ENDTABLE() +}; + +/** Swedish, Finland. */ +static const BuiltinFormat spBuiltinFormats_sv_FI[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_STRING( 9, "0\\ %" ), + NUMFMT_STRING( 10, "0.00\\ %" ), + NUMFMT_ALLDATETIMES( "D.M.YYYY", "D", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_EURO, "_" UTF8_EURO, "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Swedish, Sweden. */ +static const BuiltinFormat spBuiltinFormats_sv_SE[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "YYYY-MM-DD", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"kr\"", "_k_r", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Swahili, Tanzania. */ +static const BuiltinFormat spBuiltinFormats_sw_TZ[] = +{ + NUMFMT_ALLDATETIMES( "M/D/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\\S", "" ), + NUMFMT_ENDTABLE() +}; + +/** Tamil, India. */ +static const BuiltinFormat spBuiltinFormats_ta_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YYYY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_TA_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Telugu, India. */ +static const BuiltinFormat spBuiltinFormats_te_IN[] = +{ + NUMFMT_ALLDATETIMES( "DD-MM-YY", "DD", "-", "MMM", "-", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( "\"" UTF8_CURR_TE_IN "\"", " " ), + NUMFMT_ENDTABLE() +}; + +/** Thai, Thailand. */ +static const BuiltinFormat spBuiltinFormats_th_TH[] = +{ + NUMFMT_ALLDATETIMES( "D/M/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_BAHT, "" ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 63, UTF8_BAHT, "", "t" ), + NUMFMT_STRING( 59, "t0" ), + NUMFMT_STRING( 60, "t0.00" ), + NUMFMT_STRING( 61, "t#,##0" ), + NUMFMT_STRING( 62, "t#,##0.00" ), + NUMFMT_STRING( 67, "t0%" ), + NUMFMT_STRING( 68, "t0.00%" ), + NUMFMT_STRING( 69, "t# ?/?" ), + NUMFMT_STRING( 70, "t# ?\?/?\?" ), + NUMFMT_STRING( 71, "tD/M/EE" ), + NUMFMT_STRING( 72, "tD-MMM-E" ), + NUMFMT_STRING( 73, "tD-MMM" ), + NUMFMT_STRING( 74, "tMMM-E" ), + NUMFMT_STRING( 75, "th:mm" ), + NUMFMT_STRING( 76, "th:mm:ss" ), + NUMFMT_STRING( 77, "tD/M/EE h:mm" ), + NUMFMT_STRING( 78, "tmm:ss" ), + NUMFMT_STRING( 79, "t[h]:mm:ss" ), + NUMFMT_STRING( 80, "tmm:ss.0" ), + NUMFMT_STRING( 81, "D/M/E" ), + NUMFMT_ENDTABLE() +}; + +/** Turkish, Turkey. */ +static const BuiltinFormat spBuiltinFormats_tr_TR[] = +{ + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"TL\"", "_T_L", " " ), + NUMFMT_ENDTABLE() +}; + +/** Tatar, Russian Federation. */ +static const BuiltinFormat spBuiltinFormats_tt_RU[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_CYR_R_LC ".\"", "_" UTF8_CYR_R_LC "_.", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Ukrainian, Ukraine. */ +static const BuiltinFormat spBuiltinFormats_uk_UA[] = +{ + // space character is group separator, literal spaces must be quoted + NUMFMT_ALLDATETIMES( "DD.MM.YYYY", "DD", ".", "MMM", ".", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( "\"" UTF8_CYR_G_LC UTF8_CYR_R_LC UTF8_CYR_N_LC ".\"", "_" UTF8_CYR_G_LC "_" UTF8_CYR_R_LC "_" UTF8_CYR_N_LC "_.", "\\ " ), + NUMFMT_ENDTABLE() +}; + +/** Urdu, Pakistan. */ +static const BuiltinFormat spBuiltinFormats_ur_PK[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_SYMBOL_NUMBER_MINUS( "\"Rs\"", "" ), + NUMFMT_ENDTABLE() +}; + +/** Vietnamese, Viet Nam. */ +static const BuiltinFormat spBuiltinFormats_vi_VN[] = +{ + NUMFMT_ALLDATETIMES( "DD/MM/YYYY", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_NUMBER_SYMBOL( UTF8_DONG, "_" UTF8_DONG, " " ), + NUMFMT_ENDTABLE() +}; + +// CJK ------------------------------------------------------------------------ + +/** Base table for CJK locales. */ +static const BuiltinFormat spBuiltinFormats_CJK[] = +{ + NUMFMT_REUSE( 29, 28 ), + NUMFMT_REUSE( 36, 27 ), + NUMFMT_REUSE( 50, 27 ), + NUMFMT_REUSE( 51, 28 ), + NUMFMT_REUSE( 52, 34 ), + NUMFMT_REUSE( 53, 35 ), + NUMFMT_REUSE( 54, 28 ), + NUMFMT_REUSE( 55, 34 ), + NUMFMT_REUSE( 56, 35 ), + NUMFMT_REUSE( 57, 27 ), + NUMFMT_REUSE( 58, 28 ), + NUMFMT_ENDTABLE() +}; + +/** Japanese, Japan. */ +static const BuiltinFormat spBuiltinFormats_ja_JP[] = +{ + NUMFMT_ALLDATETIMES( "YYYY/MM/DD", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_YEN_JP, "" ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "$", "", "" ), + NUMFMT_STRING( 27, "[$-411]GE.MM.DD" ), + NUMFMT_STRING( 28, "[$-411]GGGE\"" UTF8_CJ_YEAR "\"MM\"" UTF8_CJ_MON "\"DD\"" UTF8_CJ_DAY "\"" ), + NUMFMT_STRING( 30, "MM/DD/YY" ), + NUMFMT_STRING( 31, "YYYY\"" UTF8_CJ_YEAR "\"MM\"" UTF8_CJ_MON "\"DD\"" UTF8_CJ_DAY "\"" ), + NUMFMT_TIME_CJK( 32, "h", UTF8_CJ_HOUR, UTF8_CJ_MIN, UTF8_CJ_SEC ), + NUMFMT_STRING( 34, "YYYY\"" UTF8_CJ_YEAR "\"MM\"" UTF8_CJ_MON "\"" ), + NUMFMT_STRING( 35, "MM\"" UTF8_CJ_MON "\"DD\"" UTF8_CJ_DAY "\"" ), + NUMFMT_ENDTABLE() +}; + +/** Korean, South Korea. */ +static const BuiltinFormat spBuiltinFormats_ko_KR[] = +{ + NUMFMT_ALLDATETIMES( "YYYY-MM-DD", "DD", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( UTF8_WON, "" ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "$", "", "" ), + NUMFMT_STRING( 27, "YYYY" UTF8_CJ_YEAR " MM" UTF8_CJ_MON " DD" UTF8_CJ_DAY ), + NUMFMT_STRING( 28, "MM-DD" ), + NUMFMT_STRING( 30, "MM-DD-YY" ), + NUMFMT_STRING( 31, "YYYY" UTF8_KO_YEAR " MM" UTF8_KO_MON " DD" UTF8_KO_DAY ), + NUMFMT_TIME_CJK( 32, "h", UTF8_KO_HOUR, UTF8_KO_MIN, UTF8_KO_SEC ), + // slashes must be quoted to prevent conversion to minus + NUMFMT_STRING( 34, "YYYY\\/MM\\/DD" ), + NUMFMT_REUSE( 35, 14 ), + NUMFMT_ENDTABLE() +}; + +/** Chinese, China. */ +static const BuiltinFormat spBuiltinFormats_zh_CN[] = +{ + NUMFMT_ALLDATETIMES( "YYYY-M-D", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_SYMBOL_MINUS_NUMBER( UTF8_YEN_CN, "" ), + NUMFMT_ALLTIMES_CJK( "h", "h", UTF8_CS_HOUR, UTF8_CS_MIN, UTF8_CS_SEC ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "$", "", "" ), + NUMFMT_STRING( 27, "YYYY\"" UTF8_CS_YEAR "\"M\"" UTF8_CS_MON "\"" ), + NUMFMT_STRING( 28, "M\"" UTF8_CS_MON "\"D\"" UTF8_CS_DAY "\"" ), + NUMFMT_STRING( 30, "M-D-YY" ), + NUMFMT_STRING( 31, "YYYY\"" UTF8_CS_YEAR "\"M\"" UTF8_CS_MON "\"D\"" UTF8_CS_DAY "\"" ), + NUMFMT_REUSE( 52, 27 ), + NUMFMT_REUSE( 53, 28 ), + NUMFMT_ENDTABLE() +}; + +/** Chinese, Hong Kong. */ +static const BuiltinFormat spBuiltinFormats_zh_HK[] = +{ + NUMFMT_ALLDATETIMES( "D/M/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\"HK$\"", "" ), + NUMFMT_ALLTIMES_CJK( "h", "h", UTF8_CJ_HOUR, UTF8_CJ_MIN, UTF8_CJ_SEC ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "\"US$\"", "", "" ), + NUMFMT_STRING( 27, "[$-404]D/M/E" ), + NUMFMT_STRING( 28, "[$-404]D\"" UTF8_CJ_DAY "\"M\"" UTF8_CJ_MON "\"E\"" UTF8_CJ_YEAR "\"" ), + NUMFMT_STRING( 30, "M/D/YY" ), + NUMFMT_STRING( 31, "D\"" UTF8_CJ_DAY "\"M\"" UTF8_CJ_MON "\"YYYY\"" UTF8_CJ_YEAR "\"" ), + NUMFMT_ENDTABLE() +}; + +/** Chinese, Macau. */ +static const BuiltinFormat spBuiltinFormats_zh_MO[] = +{ + NUMFMT_ALLDATETIMES( "D/M/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "\\P", "" ), + NUMFMT_ALLTIMES_CJK( "h", "h", UTF8_CJ_HOUR, UTF8_CJ_MIN, UTF8_CJ_SEC ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "\"US$\"", "", "" ), + NUMFMT_STRING( 27, "[$-404]D/M/E" ), + NUMFMT_STRING( 28, "[$-404]D\"" UTF8_CJ_DAY "\"M\"" UTF8_CJ_MON "\"E\"" UTF8_CJ_YEAR "\"" ), + NUMFMT_STRING( 30, "M/D/YY" ), + NUMFMT_STRING( 31, "D\"" UTF8_CJ_DAY "\"M\"" UTF8_CJ_MON "\"YYYY\"" UTF8_CJ_YEAR "\"" ), + NUMFMT_ENDTABLE() +}; + +/** Chinese, Singapore. */ +static const BuiltinFormat spBuiltinFormats_zh_SG[] = +{ + NUMFMT_ALLDATETIMES( "D/M/YYYY", "D", "-", "MMM", "-", "YY", "h", "h" ), + NUMFMT_ALLCURRENCIES_OPEN_SYMBOL_NUMBER_CLOSE( "$", "" ), + NUMFMT_ALLTIMES_CJK( "h", "h", UTF8_CS_HOUR, UTF8_CS_MIN, UTF8_CS_SEC ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "$", "", "" ), + NUMFMT_STRING( 27, "YYYY\"" UTF8_CS_YEAR "\"M\"" UTF8_CS_MON "\"" ), + NUMFMT_STRING( 28, "M\"" UTF8_CS_MON "\"D\"" UTF8_CS_DAY "\"" ), + NUMFMT_STRING( 30, "M/D/YY" ), + NUMFMT_STRING( 31, "D\"" UTF8_CS_DAY "\"M\"" UTF8_CS_MON "\"YYYY\"" UTF8_CS_YEAR "\"" ), + NUMFMT_ENDTABLE() +}; + +/** Chinese, Taiwan. */ +static const BuiltinFormat spBuiltinFormats_zh_TW[] = +{ + NUMFMT_ALLDATETIMES( "YYYY/M/D", "D", "-", "MMM", "-", "YY", "hh", "hh" ), + NUMFMT_ALLCURRENCIES_MINUS_SYMBOL_NUMBER( "$", "" ), + NUMFMT_ALLTIMES_CJK( "hh", "hh", UTF8_CJ_HOUR, UTF8_CJ_MIN, UTF8_CJ_SEC ), + NUMFMT_CURRENCY_OPEN_SYMBOL_NUMBER_CLOSE( 23, "\"US$\"", "", "" ), + NUMFMT_STRING( 27, "[$-404]E/M/D" ), + NUMFMT_STRING( 28, "[$-404]E\"" UTF8_CJ_YEAR "\"M\"" UTF8_CJ_MON "\"D\"" UTF8_CJ_DAY "\"" ), + NUMFMT_STRING( 30, "M/D/YY" ), + NUMFMT_STRING( 31, "YYYY\"" UTF8_CJ_YEAR "\"M\"" UTF8_CJ_MON "\"D\"" UTF8_CJ_DAY "\"" ), + NUMFMT_ENDTABLE() +}; + +// ---------------------------------------------------------------------------- + +/** Specifies a built-in number format table for a specific locale. */ +struct BuiltinFormatTable +{ + const sal_Char* mpcLocale; /// The locale for this table. + const sal_Char* mpcParent; /// The locale of the parent table. + const BuiltinFormat* mpFormats; /// The number format table (may be 0, if equal to parent). +}; + +static const BuiltinFormatTable spBuiltinFormatTables[] = +{ // locale parent format table + { "*", "", spBuiltinFormats_BASE }, // Base table + { "af-ZA", "*", spBuiltinFormats_en_ZA }, // Afrikaans, South Africa + { "ar-AE", "*", spBuiltinFormats_ar_AE }, // Arabic, U.A.E. + { "ar-BH", "*", spBuiltinFormats_ar_BH }, // Arabic, Bahrain + { "ar-DZ", "*", spBuiltinFormats_ar_DZ }, // Arabic, Algeria + { "ar-EG", "*", spBuiltinFormats_ar_EG }, // Arabic, Egypt + { "ar-IQ", "*", spBuiltinFormats_ar_IQ }, // Arabic, Iraq + { "ar-JO", "*", spBuiltinFormats_ar_JO }, // Arabic, Jordan + { "ar-KW", "*", spBuiltinFormats_ar_KW }, // Arabic, Kuwait + { "ar-LB", "*", spBuiltinFormats_ar_LB }, // Arabic, Lebanon + { "ar-LY", "*", spBuiltinFormats_ar_LY }, // Arabic, Libya + { "ar-MA", "*", spBuiltinFormats_ar_MA }, // Arabic, Morocco + { "ar-OM", "*", spBuiltinFormats_ar_OM }, // Arabic, Oman + { "ar-QA", "*", spBuiltinFormats_ar_QA }, // Arabic, Qatar + { "ar-SA", "*", spBuiltinFormats_ar_SA }, // Arabic, Saudi Arabia + { "ar-SY", "*", spBuiltinFormats_ar_SY }, // Arabic, Syria + { "ar-TN", "*", spBuiltinFormats_ar_TN }, // Arabic, Tunisia + { "ar-YE", "*", spBuiltinFormats_ar_YE }, // Arabic, Yemen + { "be-BY", "*", spBuiltinFormats_be_BY }, // Belarusian, Belarus + { "bg-BG", "*", spBuiltinFormats_bg_BG }, // Bulgarian, Bulgaria + { "bn-IN", "*", spBuiltinFormats_bn_IN }, // Bengali, India + { "ca-ES", "*", spBuiltinFormats_es_ES }, // Catalan, Spain + { "cs-CZ", "*", spBuiltinFormats_cs_CZ }, // Czech, Czech Republic + { "cy-GB", "*", spBuiltinFormats_en_GB }, // Welsh, United Kingdom + { "da-DK", "*", spBuiltinFormats_da_DK }, // Danish, Denmark + { "de-AT", "*", spBuiltinFormats_de_AT }, // German, Austria + { "de-CH", "*", spBuiltinFormats_de_CH }, // German, Switzerland + { "de-DE", "*", spBuiltinFormats_de_DE }, // German, Germany + { "de-LI", "*", spBuiltinFormats_de_LI }, // German, Liechtenstein + { "de-LU", "*", spBuiltinFormats_de_LU }, // German, Luxembourg + { "div-MV", "*", spBuiltinFormats_div_MV }, // Divehi, Maldives + { "el-GR", "*", spBuiltinFormats_el_GR }, // Greek, Greece + { "en-AU", "*", spBuiltinFormats_en_AU }, // English, Australia + { "en-BZ", "*", spBuiltinFormats_en_BZ }, // English, Belize + { "en-CA", "*", spBuiltinFormats_en_CA }, // English, Canada + { "en-CB", "*", spBuiltinFormats_en_CB }, // English, Caribbean + { "en-GB", "*", spBuiltinFormats_en_GB }, // English, United Kingdom + { "en-IE", "*", spBuiltinFormats_en_IE }, // English, Ireland + { "en-JM", "*", spBuiltinFormats_en_JM }, // English, Jamaica + { "en-NZ", "*", spBuiltinFormats_en_NZ }, // English, New Zealand + { "en-PH", "*", spBuiltinFormats_en_PH }, // English, Philippines + { "en-TT", "*", spBuiltinFormats_en_TT }, // English, Trinidad and Tobago + { "en-US", "*", spBuiltinFormats_en_US }, // English, USA + { "en-ZA", "*", spBuiltinFormats_en_ZA }, // English, South Africa + { "en-ZW", "*", spBuiltinFormats_en_ZW }, // English, Zimbabwe + { "es-AR", "*", spBuiltinFormats_es_AR }, // Spanish, Argentina + { "es-BO", "*", spBuiltinFormats_es_BO }, // Spanish, Bolivia + { "es-CL", "*", spBuiltinFormats_es_CL }, // Spanish, Chile + { "es-CO", "*", spBuiltinFormats_es_CO }, // Spanish, Colombia + { "es-CR", "*", spBuiltinFormats_es_CR }, // Spanish, Costa Rica + { "es-DO", "*", spBuiltinFormats_es_DO }, // Spanish, Dominican Republic + { "es-EC", "*", spBuiltinFormats_es_EC }, // Spanish, Ecuador + { "es-ES", "*", spBuiltinFormats_es_ES }, // Spanish, Spain + { "es-GT", "*", spBuiltinFormats_es_GT }, // Spanish, Guatemala + { "es-HN", "*", spBuiltinFormats_es_HN }, // Spanish, Honduras + { "es-MX", "*", spBuiltinFormats_es_MX }, // Spanish, Mexico + { "es-NI", "*", spBuiltinFormats_es_NI }, // Spanish, Nicaragua + { "es-PA", "*", spBuiltinFormats_es_PA }, // Spanish, Panama + { "es-PE", "*", spBuiltinFormats_es_PE }, // Spanish, Peru + { "es-PR", "*", spBuiltinFormats_es_PR }, // Spanish, Puerto Rico + { "es-PY", "*", spBuiltinFormats_es_PY }, // Spanish, Paraguay + { "es-SV", "*", spBuiltinFormats_es_SV }, // Spanish, El Salvador + { "es-UY", "*", spBuiltinFormats_es_UY }, // Spanish, Uruguay + { "es-VE", "*", spBuiltinFormats_es_VE }, // Spanish, Venezuela + { "et-EE", "*", spBuiltinFormats_et_EE }, // Estonian, Estonia + { "fa-IR", "*", spBuiltinFormats_fa_IR }, // Farsi, Iran + { "fi-FI", "*", spBuiltinFormats_fi_FI }, // Finnish, Finland + { "fo-FO", "*", spBuiltinFormats_fo_FO }, // Faroese, Faroe Islands + { "fr-BE", "*", spBuiltinFormats_fr_BE }, // French, Belgium + { "fr-CA", "*", spBuiltinFormats_fr_CA }, // French, Canada + { "fr-CH", "*", spBuiltinFormats_fr_CH }, // French, Switzerland + { "fr-FR", "*", spBuiltinFormats_fr_FR }, // French, France + { "fr-LU", "*", spBuiltinFormats_fr_LU }, // French, Luxembourg + { "fr-MC", "*", spBuiltinFormats_fr_MC }, // French, Monaco + { "gl-ES", "*", spBuiltinFormats_gl_ES }, // Galizian, Spain + { "gu-IN", "*", spBuiltinFormats_gu_IN }, // Gujarati, India + { "he-IL", "*", spBuiltinFormats_he_IL }, // Hebrew, Israel + { "hi-IN", "*", spBuiltinFormats_hi_IN }, // Hindi, India + { "hr-BA", "*", spBuiltinFormats_hr_BA }, // Croatian, Bosnia and Herzegowina + { "hr-HR", "*", spBuiltinFormats_hr_HR }, // Croatian, Croatia + { "hu-HU", "*", spBuiltinFormats_hu_HU }, // Hungarian, Hungary + { "hy-AM", "*", spBuiltinFormats_hy_AM }, // Armenian, Armenia + { "id-ID", "*", spBuiltinFormats_id_ID }, // Indonesian, Indonesia + { "is-IS", "*", spBuiltinFormats_is_IS }, // Icelandic, Iceland + { "it-CH", "*", spBuiltinFormats_it_CH }, // Italian, Switzerland + { "it-IT", "*", spBuiltinFormats_it_IT }, // Italian, Italy + { "ka-GE", "*", spBuiltinFormats_ka_GE }, // Georgian, Georgia + { "kk-KZ", "*", spBuiltinFormats_kk_KZ }, // Kazakh, Kazakhstan + { "kn-IN", "*", spBuiltinFormats_kn_IN }, // Kannada, India + { "kok-IN", "*", spBuiltinFormats_hi_IN }, // Konkani, India + { "ky-KG", "*", spBuiltinFormats_ky_KG }, // Kyrgyz, Kyrgyzstan + { "lt-LT", "*", spBuiltinFormats_lt_LT }, // Lithuanian, Lithuania + { "lv-LV", "*", spBuiltinFormats_lv_LV }, // Latvian, Latvia + { "mi-NZ", "*", spBuiltinFormats_en_NZ }, // Maori, New Zealand + { "ml-IN", "*", spBuiltinFormats_ml_IN }, // Malayalam, India + { "mn-MN", "*", spBuiltinFormats_mn_MN }, // Mongolian, Mongolia + { "mr-IN", "*", spBuiltinFormats_hi_IN }, // Marathi, India + { "ms-BN", "*", spBuiltinFormats_ms_BN }, // Malay, Brunei Darussalam + { "ms-MY", "*", spBuiltinFormats_ms_MY }, // Malay, Malaysia + { "mt-MT", "*", spBuiltinFormats_mt_MT }, // Maltese, Malta + { "nb-NO", "*", spBuiltinFormats_no_NO }, // Norwegian Bokmal, Norway + { "nl-BE", "*", spBuiltinFormats_nl_BE }, // Dutch, Belgium + { "nl-NL", "*", spBuiltinFormats_nl_NL }, // Dutch, Netherlands + { "nn-NO", "*", spBuiltinFormats_no_NO }, // Norwegian Nynorsk, Norway + { "nso-ZA", "*", spBuiltinFormats_en_ZA }, // Northern Sotho, South Africa + { "pa-IN", "*", spBuiltinFormats_pa_IN }, // Punjabi, India + { "pl-PL", "*", spBuiltinFormats_pl_PL }, // Polish, Poland + { "pt-BR", "*", spBuiltinFormats_pt_BR }, // Portugese, Brazil + { "pt-PT", "*", spBuiltinFormats_pt_PT }, // Portugese, Portugal + { "qu-BO", "*", spBuiltinFormats_es_BO }, // Quechua, Bolivia + { "qu-EC", "*", spBuiltinFormats_es_EC }, // Quechua, Ecuador + { "qu-PE", "*", spBuiltinFormats_es_PE }, // Quechua, Peru + { "ro-RO", "*", spBuiltinFormats_ro_RO }, // Romanian, Romania + { "ru-RU", "*", spBuiltinFormats_ru_RU }, // Russian, Russian Federation + { "sa-IN", "*", spBuiltinFormats_hi_IN }, // Sanskrit, India + { "se-FI", "*", spBuiltinFormats_fi_FI }, // Sami, Finland + { "se-NO", "*", spBuiltinFormats_no_NO }, // Sami, Norway + { "se-SE", "*", spBuiltinFormats_sv_SE }, // Sami, Sweden + { "sk-SK", "*", spBuiltinFormats_sk_SK }, // Slovak, Slovakia + { "sl-SI", "*", spBuiltinFormats_sl_SI }, // Slovenian, Slovenia + { "sv-FI", "*", spBuiltinFormats_sv_FI }, // Swedish, Finland + { "sv-SE", "*", spBuiltinFormats_sv_SE }, // Swedish, Sweden + { "sw-TZ", "*", spBuiltinFormats_sw_TZ }, // Swahili, Tanzania + { "syr-SY", "*", spBuiltinFormats_ar_SY }, // Syriac, Syria + { "syr-TR", "*", spBuiltinFormats_tr_TR }, // Syriac, Turkey + { "ta-IN", "*", spBuiltinFormats_ta_IN }, // Tamil, India + { "te-IN", "*", spBuiltinFormats_te_IN }, // Telugu, India + { "th-TH", "*", spBuiltinFormats_th_TH }, // Thai, Thailand + { "tn-ZA", "*", spBuiltinFormats_en_ZA }, // Tswana, South Africa + { "tr-TR", "*", spBuiltinFormats_tr_TR }, // Turkish, Turkey + { "tt-RU", "*", spBuiltinFormats_tt_RU }, // Tatar, Russian Federation + { "uk-UA", "*", spBuiltinFormats_uk_UA }, // Ukrainian, Ukraine + { "ur-PK", "*", spBuiltinFormats_ur_PK }, // Urdu, Pakistan + { "vi-VN", "*", spBuiltinFormats_vi_VN }, // Vietnamese, Viet Nam + { "xh-ZA", "*", spBuiltinFormats_en_ZA }, // Xhosa, South Africa + { "zu-ZA", "*", spBuiltinFormats_en_ZA }, // Zulu, South Africa + + { "*CJK", "*", spBuiltinFormats_CJK }, // CJK base table + { "ja-JP", "*CJK", spBuiltinFormats_ja_JP }, // Japanese, Japan + { "ko-KR", "*CJK", spBuiltinFormats_ko_KR }, // Korean, South Korea + { "zh-CN", "*CJK", spBuiltinFormats_zh_CN }, // Chinese, China + { "zh-HK", "*CJK", spBuiltinFormats_zh_HK }, // Chinese, Hong Kong + { "zh-MO", "*CJK", spBuiltinFormats_zh_MO }, // Chinese, Macau + { "zh-SG", "*CJK", spBuiltinFormats_zh_SG }, // Chinese, Singapore + { "zh-TW", "*CJK", spBuiltinFormats_zh_TW } // Chinese, Taiwan +}; + +} // namespace + +// ============================================================================ + +NumFmtModel::NumFmtModel() : + mnPredefId( -1 ) +{ +} + +// ---------------------------------------------------------------------------- + +ApiNumFmtData::ApiNumFmtData() : + mnIndex( 0 ) +{ +} + +// ---------------------------------------------------------------------------- + +namespace { + +sal_Int32 lclCreatePredefinedFormat( const Reference< XNumberFormats >& rxNumFmts, + sal_Int16 nPredefId, const Locale& rToLocale ) +{ + sal_Int32 nIndex = 0; + try + { + Reference< XNumberFormatTypes > xNumFmtTypes( rxNumFmts, UNO_QUERY_THROW ); + nIndex = (nPredefId >= 0) ? + xNumFmtTypes->getFormatIndex( nPredefId, rToLocale ) : + xNumFmtTypes->getStandardIndex( rToLocale ); + } + catch( Exception& ) + { + OSL_ENSURE( false, + OStringBuffer( "lclCreatePredefinedFormat - cannot create predefined number format " ). + append( OString::valueOf( static_cast< sal_Int32 >( nPredefId ) ) ).getStr() ); + } + return nIndex; +} + +sal_Int32 lclCreateFormat( const Reference< XNumberFormats >& rxNumFmts, + const OUString& rFmtCode, const Locale& rToLocale, const Locale& rFromLocale ) +{ + sal_Int32 nIndex = 0; + try + { + nIndex = rxNumFmts->addNewConverted( rFmtCode, rFromLocale, rToLocale ); + } + catch( Exception& ) + { + // BIFF2-BIFF4 stores standard format explicitly in stream + static const OUString saGeneral = CREATE_OUSTRING( "general" ); + if( rFmtCode.equalsIgnoreAsciiCase( saGeneral ) ) + { + nIndex = lclCreatePredefinedFormat( rxNumFmts, 0, rToLocale ); + } + else + { + OSL_ENSURE( false, + OStringBuffer( "lclCreateFormat - cannot create number format '" ). + append( OUStringToOString( rFmtCode, osl_getThreadTextEncoding() ) ). + append( '\'' ).getStr() ); + } + } + return nIndex; +} + +// ---------------------------------------------------------------------------- + +/** Functor for converting an XML number format to an API number format index. */ +class NumberFormatFinalizer +{ +public: + explicit NumberFormatFinalizer( const WorkbookHelper& rHelper ); + + inline bool is() const { return mxNumFmts.is(); } + + inline void operator()( NumberFormat& rNumFmt ) const + { rNumFmt.finalizeImport( mxNumFmts, maEnUsLocale ); } + +private: + Reference< XNumberFormats > mxNumFmts; + Locale maEnUsLocale; +}; + +NumberFormatFinalizer::NumberFormatFinalizer( const WorkbookHelper& rHelper ) : + maEnUsLocale( CREATE_OUSTRING( "en" ), CREATE_OUSTRING( "US" ), OUString() ) +{ + try + { + Reference< XNumberFormatsSupplier > xNumFmtsSupp( rHelper.getDocument(), UNO_QUERY_THROW ); + mxNumFmts = xNumFmtsSupp->getNumberFormats(); + } + catch( Exception& ) + { + } + OSL_ENSURE( mxNumFmts.is(), "NumberFormatFinalizer::NumberFormatFinalizer - cannot get number formats" ); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +NumberFormat::NumberFormat( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void NumberFormat::setFormatCode( const OUString& rFmtCode ) +{ + maModel.maFmtCode = rFmtCode; +} + +void NumberFormat::setFormatCode( const Locale& rLocale, const sal_Char* pcFmtCode ) +{ + maModel.maLocale = rLocale; + maModel.maFmtCode = OStringToOUString( OString( pcFmtCode ), RTL_TEXTENCODING_UTF8 ); + maModel.mnPredefId = -1; +} + +void NumberFormat::setPredefinedId( const Locale& rLocale, sal_Int16 nPredefId ) +{ + maModel.maLocale = rLocale; + maModel.maFmtCode = OUString(); + maModel.mnPredefId = nPredefId; +} + +sal_Int32 NumberFormat::finalizeImport( const Reference< XNumberFormats >& rxNumFmts, const Locale& rFromLocale ) +{ + if( rxNumFmts.is() && (maModel.maFmtCode.getLength() > 0) ) + maApiData.mnIndex = lclCreateFormat( rxNumFmts, maModel.maFmtCode, maModel.maLocale, rFromLocale ); + else + maApiData.mnIndex = lclCreatePredefinedFormat( rxNumFmts, maModel.mnPredefId, maModel.maLocale ); + return maApiData.mnIndex; +} + +void NumberFormat::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + rPropMap[ PROP_NumberFormat ] <<= maApiData.mnIndex; +} + +// ============================================================================ + +NumberFormatsBuffer::NumberFormatsBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mnNextBiffIndex( 0 ) +{ + // get the current locale + try + { + Reference< XMultiServiceFactory > xConfigProv( getGlobalFactory()->createInstance( + CREATE_OUSTRING( "com.sun.star.configuration.ConfigurationProvider" ) ), UNO_QUERY_THROW ); + + // try user-defined locale setting + Sequence< Any > aArgs( 1 ); + aArgs[ 0 ] <<= CREATE_OUSTRING( "org.openoffice.Setup/L10N/" ); + Reference< XNameAccess > xConfigNA( xConfigProv->createInstanceWithArguments( + CREATE_OUSTRING( "com.sun.star.configuration.ConfigurationAccess" ), aArgs ), UNO_QUERY_THROW ); + xConfigNA->getByName( CREATE_OUSTRING( "ooSetupSystemLocale" ) ) >>= maLocaleStr; + + // if set to "use system", get locale from system + if( maLocaleStr.getLength() == 0 ) + { + aArgs[ 0 ] <<= CREATE_OUSTRING( "org.openoffice.System/L10N/" ); + xConfigNA.set( xConfigProv->createInstanceWithArguments( + CREATE_OUSTRING( "com.sun.star.configuration.ConfigurationAccess" ), aArgs ), UNO_QUERY_THROW ); + xConfigNA->getByName( CREATE_OUSTRING( "Locale" ) ) >>= maLocaleStr; + } + } + catch( Exception& ) + { + OSL_ENSURE( false, "NumberFormatsBuffer::NumberFormatsBuffer - cannot get system locale" ); + } + + // create built-in formats for current locale + insertBuiltinFormats(); +} + +NumberFormatRef NumberFormatsBuffer::createNumFmt( sal_Int32 nNumFmtId, const OUString& rFmtCode ) +{ + NumberFormatRef xNumFmt; + if( nNumFmtId >= 0 ) + { + xNumFmt.reset( new NumberFormat( *this ) ); + maNumFmts[ nNumFmtId ] = xNumFmt; + xNumFmt->setFormatCode( rFmtCode ); + } + return xNumFmt; +} + +NumberFormatRef NumberFormatsBuffer::importNumFmt( const AttributeList& rAttribs ) +{ + sal_Int32 nNumFmtId = rAttribs.getInteger( XML_numFmtId, -1 ); + OUString aFmtCode = rAttribs.getXString( XML_formatCode, OUString() ); + return createNumFmt( nNumFmtId, aFmtCode ); +} + +void NumberFormatsBuffer::importNumFmt( RecordInputStream& rStrm ) +{ + sal_Int32 nNumFmtId = rStrm.readuInt16(); + OUString aFmtCode = rStrm.readString(); + createNumFmt( nNumFmtId, aFmtCode ); +} + +void NumberFormatsBuffer::importFormat( BiffInputStream& rStrm ) +{ + OUString aFmtCode; + switch( getBiff() ) + { + case BIFF2: + case BIFF3: + aFmtCode = rStrm.readByteStringUC( false, getTextEncoding() ); + break; + case BIFF4: + rStrm.skip( 2 ); // in BIFF4 the index field exists, but is undefined + aFmtCode = rStrm.readByteStringUC( false, getTextEncoding() ); + break; + case BIFF5: + mnNextBiffIndex = rStrm.readuInt16(); + aFmtCode = rStrm.readByteStringUC( false, getTextEncoding() ); + break; + case BIFF8: + mnNextBiffIndex = rStrm.readuInt16(); + aFmtCode = rStrm.readUniString(); + break; + case BIFF_UNKNOWN: break; + } + + createNumFmt( mnNextBiffIndex, aFmtCode ); + ++mnNextBiffIndex; +} + +void NumberFormatsBuffer::finalizeImport() +{ + maNumFmts.forEach( NumberFormatFinalizer( *this ) ); +} + +void NumberFormatsBuffer::writeToPropertyMap( PropertyMap& rPropMap, sal_Int32 nNumFmtId ) const +{ + if( const NumberFormat* pNumFmt = maNumFmts.get( nNumFmtId ).get() ) + pNumFmt->writeToPropertyMap( rPropMap ); +} + +void NumberFormatsBuffer::insertBuiltinFormats() +{ + // build a map containing pointers to all tables + typedef ::std::map< OUString, const BuiltinFormatTable* > BuiltinMap; + BuiltinMap aBuiltinMap; + for( const BuiltinFormatTable* pTable = spBuiltinFormatTables; + pTable != STATIC_ARRAY_END( spBuiltinFormatTables ); ++pTable ) + aBuiltinMap[ OUString::createFromAscii( pTable->mpcLocale ) ] = pTable; + + // convert locale string to locale struct + Locale aSysLocale; + sal_Int32 nDashPos = maLocaleStr.indexOf( '-' ); + if( nDashPos < 0 ) nDashPos = maLocaleStr.getLength(); + aSysLocale.Language = maLocaleStr.copy( 0, nDashPos ); + if( nDashPos + 1 < maLocaleStr.getLength() ) + aSysLocale.Country = maLocaleStr.copy( nDashPos + 1 ); + + // build a list of table pointers for the current locale, with all parent tables + typedef ::std::vector< const BuiltinFormatTable* > BuiltinVec; + BuiltinVec aBuiltinVec; + BuiltinMap::const_iterator aMIt = aBuiltinMap.find( maLocaleStr ), aMEnd = aBuiltinMap.end(); + OSL_ENSURE( aMIt != aMEnd, + OStringBuffer( "NumberFormatsBuffer::insertBuiltinFormats - locale '" ). + append( OUStringToOString( maLocaleStr, RTL_TEXTENCODING_ASCII_US ) ). + append( "' not supported (#i29949#)" ).getStr() ); + // start with default table, if no table has been found + if( aMIt == aMEnd ) + aMIt = aBuiltinMap.find( CREATE_OUSTRING( "*" ) ); + OSL_ENSURE( aMIt != aMEnd, "NumberFormatsBuffer::insertBuiltinFormats - default map not found" ); + // insert all tables into the vector + for( ; aMIt != aMEnd; aMIt = aBuiltinMap.find( OUString::createFromAscii( aMIt->second->mpcParent ) ) ) + aBuiltinVec.push_back( aMIt->second ); + + // insert the default formats in the format map (in reverse order from default table to system locale) + typedef ::std::map< sal_Int32, sal_Int32 > ReuseMap; + ReuseMap aReuseMap; + for( BuiltinVec::reverse_iterator aVIt = aBuiltinVec.rbegin(), aVEnd = aBuiltinVec.rend(); aVIt != aVEnd; ++aVIt ) + { + // do not put the current system locale for default table + Locale aLocale; + if( (*aVIt)->mpcLocale[ 0 ] != '\0' ) + aLocale = aSysLocale; + for( const BuiltinFormat* pBuiltin = (*aVIt)->mpFormats; pBuiltin && (pBuiltin->mnNumFmtId >= 0); ++pBuiltin ) + { + NumberFormatRef& rxNumFmt = maNumFmts[ pBuiltin->mnNumFmtId ]; + rxNumFmt.reset( new NumberFormat( *this ) ); + + bool bReuse = false; + if( pBuiltin->mpcFmtCode ) + rxNumFmt->setFormatCode( aLocale, pBuiltin->mpcFmtCode ); + else if( pBuiltin->mnPredefId >= 0 ) + rxNumFmt->setPredefinedId( aLocale, pBuiltin->mnPredefId ); + else + bReuse = pBuiltin->mnReuseId >= 0; + + if( bReuse ) + aReuseMap[ pBuiltin->mnNumFmtId ] = pBuiltin->mnReuseId; + else + aReuseMap.erase( pBuiltin->mnNumFmtId ); + } + } + + // copy reused number formats + for( ReuseMap::const_iterator aRIt = aReuseMap.begin(), aREnd = aReuseMap.end(); aRIt != aREnd; ++aRIt ) + maNumFmts[ aRIt->first ] = maNumFmts[ aRIt->second ]; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/ooxformulaparser.cxx b/oox/source/xls/ooxformulaparser.cxx new file mode 100644 index 000000000000..ec2668aef0f2 --- /dev/null +++ b/oox/source/xls/ooxformulaparser.cxx @@ -0,0 +1,225 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/ooxformulaparser.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include "oox/xls/formulaparser.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::RuntimeException; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::XComponentContext; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::sheet::FormulaToken; + +namespace oox { +namespace xls { + +// ============================================================================ + +class OOXMLFormulaParserImpl : private FormulaFinalizer +{ +public: + explicit OOXMLFormulaParserImpl( const Reference< XMultiServiceFactory >& rxFactory ); + + Sequence< FormulaToken > parseFormula( const OUString& rFormula, const CellAddress& rReferencePos ); + +protected: + virtual const FunctionInfo* resolveBadFuncName( const OUString& rTokenData ) const; + +private: + ApiParserWrapper maApiParser; +}; + +// ---------------------------------------------------------------------------- + +OOXMLFormulaParserImpl::OOXMLFormulaParserImpl( const Reference< XMultiServiceFactory >& rxFactory ) : + FormulaFinalizer( OpCodeProvider( rxFactory, FILTER_OOX, BIFF_UNKNOWN, true ) ), + maApiParser( rxFactory, *this ) +{ +} + +Sequence< FormulaToken > OOXMLFormulaParserImpl::parseFormula( const OUString& rFormula, const CellAddress& rReferencePos ) +{ + return finalizeTokenArray( maApiParser.parseFormula( rFormula, rReferencePos ) ); +} + +const FunctionInfo* OOXMLFormulaParserImpl::resolveBadFuncName( const OUString& rTokenData ) const +{ + /* Try to parse calls to library functions. The format of such a function + call is assumed to be + "'<path-to-office-install>\Library\<libname>'!<funcname>". */ + + // the string has to start with an apostroph (followed by the library URL) + if( (rTokenData.getLength() >= 6) && (rTokenData[ 0 ] == '\'') ) + { + // library URL and function name are separated by an exclamation mark + sal_Int32 nExclamPos = rTokenData.lastIndexOf( '!' ); + if( (1 < nExclamPos) && (nExclamPos + 1 < rTokenData.getLength()) && (rTokenData[ nExclamPos - 1 ] == '\'') ) + { + // find the last backslash that separates library path and name + sal_Int32 nFileSep = rTokenData.lastIndexOf( '\\', nExclamPos - 2 ); + if( nFileSep > 1 ) + { + // find preceding backslash that separates the last directory name + sal_Int32 nDirSep = rTokenData.lastIndexOf( '\\', nFileSep - 1 ); + // function library is located in a directory called 'library' + if( (nDirSep > 0) && rTokenData.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "\\LIBRARY\\" ), nDirSep ) ) + { + // try to find a function info for the function name + OUString aFuncName = rTokenData.copy( nExclamPos + 1 ).toAsciiUpperCase(); + const FunctionInfo* pFuncInfo = getFuncInfoFromOoxFuncName( aFuncName ); + if( pFuncInfo && (pFuncInfo->meFuncLibType != FUNCLIB_UNKNOWN) ) + { + // check that the name of the library matches + OUString aLibName = rTokenData.copy( nFileSep + 1, nExclamPos - nFileSep - 2 ); + if( pFuncInfo->meFuncLibType == getFuncLibTypeFromLibraryName( aLibName ) ) + return pFuncInfo; + } + } + } + } + } + return 0; +} + +// ============================================================================ + +class OOXMLFormulaPrinterImpl : public OpCodeProvider +{ +public: + explicit OOXMLFormulaPrinterImpl( const Reference< XMultiServiceFactory >& rxFactory ); + +private: + ApiParserWrapper maApiParser; +}; + +// ---------------------------------------------------------------------------- + +OOXMLFormulaPrinterImpl::OOXMLFormulaPrinterImpl( const Reference< XMultiServiceFactory >& rxFactory ) : + OpCodeProvider( rxFactory, FILTER_OOX, BIFF_UNKNOWN, false ), + maApiParser( rxFactory, *this ) +{ +} + +// ============================================================================ + +Sequence< OUString > OOXMLFormulaParser_getSupportedServiceNames() +{ + Sequence< OUString > aServiceNames( 1 ); + aServiceNames[ 0 ] = CREATE_OUSTRING( "com.sun.star.sheet.FilterFormulaParser" ); + return aServiceNames; +} + +OUString OOXMLFormulaParser_getImplementationName() +{ + return CREATE_OUSTRING( "com.sun.star.comp.oox.OOXMLFormulaParser" ); +} + +Reference< XInterface > SAL_CALL OOXMLFormulaParser_createInstance( const Reference< XComponentContext >& ) throw( Exception ) +{ + return static_cast< ::cppu::OWeakObject* >( new OOXMLFormulaParser ); +} + +// ============================================================================ + +OOXMLFormulaParser::OOXMLFormulaParser() +{ +} + +OOXMLFormulaParser::~OOXMLFormulaParser() +{ +} + +// com.sun.star.lang.XServiceInfo interface ----------------------------------- + +OUString SAL_CALL OOXMLFormulaParser::getImplementationName() throw( RuntimeException ) +{ + return OOXMLFormulaParser_getImplementationName(); +} + +sal_Bool SAL_CALL OOXMLFormulaParser::supportsService( const OUString& rService ) throw( RuntimeException ) +{ + const Sequence< OUString > aServices( OOXMLFormulaParser_getSupportedServiceNames() ); + const OUString* pArray = aServices.getConstArray(); + const OUString* pArrayEnd = pArray + aServices.getLength(); + return ::std::find( pArray, pArrayEnd, rService ) != pArrayEnd; +} + +Sequence< OUString > SAL_CALL OOXMLFormulaParser::getSupportedServiceNames() throw( RuntimeException ) +{ + return OOXMLFormulaParser_getSupportedServiceNames(); +} + +// com.sun.star.lang.XInitialization interface -------------------------------- + +void SAL_CALL OOXMLFormulaParser::initialize( const Sequence< Any >& rArgs ) throw( Exception, RuntimeException ) +{ + OSL_ENSURE( rArgs.hasElements(), "OOXMLFormulaParser::initialize - missing arguments" ); + if( !rArgs.hasElements() ) + throw RuntimeException(); + mxComponent.set( rArgs[ 0 ], UNO_QUERY_THROW ); +} + +// com.sun.star.sheet.XFilterFormulaParser interface -------------------------- + +OUString SAL_CALL OOXMLFormulaParser::getSupportedNamespace() throw( RuntimeException ) +{ + return CREATE_OUSTRING( "http://schemas.microsoft.com/office/excel/formula" ); +} + +// com.sun.star.sheet.XFormulaParser interface -------------------------------- + +Sequence< FormulaToken > SAL_CALL OOXMLFormulaParser::parseFormula( + const OUString& rFormula, const CellAddress& rReferencePos ) throw( RuntimeException ) +{ + if( !mxParserImpl ) + { + Reference< XMultiServiceFactory > xFactory( mxComponent, UNO_QUERY_THROW ); + mxParserImpl.reset( new OOXMLFormulaParserImpl( xFactory ) ); + } + return mxParserImpl->parseFormula( rFormula, rReferencePos ); +} + +OUString SAL_CALL OOXMLFormulaParser::printFormula( + const Sequence< FormulaToken >& /*rTokens*/, const CellAddress& /*rReferencePos*/ ) throw( RuntimeException ) +{ + // not implemented + throw RuntimeException(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/pagesettings.cxx b/oox/source/xls/pagesettings.cxx new file mode 100644 index 000000000000..0a5ce05a37df --- /dev/null +++ b/oox/source/xls/pagesettings.cxx @@ -0,0 +1,1255 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/pagesettings.hxx" +#include <set> +#include <algorithm> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/sheet/XHeaderFooterContent.hpp> +#include <com/sun/star/style/GraphicLocation.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextCursor.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/graphichelper.hxx" +#include "oox/helper/propertymap.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/xmlfilterbase.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/excelhandlers.hxx" +#include "oox/xls/stylesbuffer.hxx" +#include "oox/xls/unitconverter.hxx" + +using ::rtl::OString; +using ::rtl::OStringBuffer; +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::container::XNamed; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::awt::Size; +using ::com::sun::star::sheet::XHeaderFooterContent; +using ::com::sun::star::style::XStyle; +using ::com::sun::star::text::XText; +using ::com::sun::star::text::XTextCursor; +using ::com::sun::star::text::XTextContent; +using ::com::sun::star::text::XTextRange; +using ::oox::core::Relations; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const double OOX_MARGIN_DEFAULT_LR = 0.748; /// Left/right default margin in inches. +const double OOX_MARGIN_DEFAULT_TB = 0.984; /// Top/bottom default margin in inches. +const double OOX_MARGIN_DEFAULT_HF = 0.512; /// Header/footer default margin in inches. + +const sal_uInt16 OOBIN_PRINTOPT_HORCENTER = 0x0001; +const sal_uInt16 OOBIN_PRINTOPT_VERCENTER = 0x0002; +const sal_uInt16 OOBIN_PRINTOPT_PRINTHEADING = 0x0004; +const sal_uInt16 OOBIN_PRINTOPT_PRINTGRID = 0x0008; + +const sal_uInt16 OOBIN_HEADERFOOTER_DIFFEVEN = 0x0001; +const sal_uInt16 OOBIN_HEADERFOOTER_DIFFFIRST = 0x0002; +const sal_uInt16 OOBIN_HEADERFOOTER_SCALEDOC = 0x0004; +const sal_uInt16 OOBIN_HEADERFOOTER_ALIGNMARGIN = 0x0008; + +const sal_uInt16 OOBIN_PAGESETUP_INROWS = 0x0001; +const sal_uInt16 OOBIN_PAGESETUP_LANDSCAPE = 0x0002; +const sal_uInt16 OOBIN_PAGESETUP_INVALID = 0x0004; +const sal_uInt16 OOBIN_PAGESETUP_BLACKWHITE = 0x0008; +const sal_uInt16 OOBIN_PAGESETUP_DRAFTQUALITY = 0x0010; +const sal_uInt16 OOBIN_PAGESETUP_PRINTNOTES = 0x0020; +const sal_uInt16 OOBIN_PAGESETUP_DEFAULTORIENT = 0x0040; +const sal_uInt16 OOBIN_PAGESETUP_USEFIRSTPAGE = 0x0080; +const sal_uInt16 OOBIN_PAGESETUP_NOTES_END = 0x0100; // different to BIFF flag + +const sal_uInt16 OOBIN_CHARTPAGESETUP_LANDSCAPE = 0x0001; +const sal_uInt16 OOBIN_CHARTPAGESETUP_INVALID = 0x0002; +const sal_uInt16 OOBIN_CHARTPAGESETUP_BLACKWHITE = 0x0004; +const sal_uInt16 OOBIN_CHARTPAGESETUP_DEFAULTORIENT = 0x0008; +const sal_uInt16 OOBIN_CHARTPAGESETUP_USEFIRSTPAGE = 0x0010; +const sal_uInt16 OOBIN_CHARTPAGESETUP_DRAFTQUALITY = 0x0020; + +const sal_uInt16 BIFF_PAGESETUP_INROWS = 0x0001; +const sal_uInt16 BIFF_PAGESETUP_PORTRAIT = 0x0002; +const sal_uInt16 BIFF_PAGESETUP_INVALID = 0x0004; +const sal_uInt16 BIFF_PAGESETUP_BLACKWHITE = 0x0008; +const sal_uInt16 BIFF_PAGESETUP_DRAFTQUALITY = 0x0010; +const sal_uInt16 BIFF_PAGESETUP_PRINTNOTES = 0x0020; +const sal_uInt16 BIFF_PAGESETUP_DEFAULTORIENT = 0x0040; +const sal_uInt16 BIFF_PAGESETUP_USEFIRSTPAGE = 0x0080; +const sal_uInt16 BIFF_PAGESETUP_NOTES_END = 0x0200; + +} // namespace + +// ============================================================================ + +PageSettingsModel::PageSettingsModel() : + mfLeftMargin( OOX_MARGIN_DEFAULT_LR ), + mfRightMargin( OOX_MARGIN_DEFAULT_LR ), + mfTopMargin( OOX_MARGIN_DEFAULT_TB ), + mfBottomMargin( OOX_MARGIN_DEFAULT_TB ), + mfHeaderMargin( OOX_MARGIN_DEFAULT_HF ), + mfFooterMargin( OOX_MARGIN_DEFAULT_HF ), + mnPaperSize( 1 ), + mnCopies( 1 ), + mnScale( 100 ), + mnFirstPage( 1 ), + mnFitToWidth( 1 ), + mnFitToHeight( 1 ), + mnHorPrintRes( 600 ), + mnVerPrintRes( 600 ), + mnOrientation( XML_default ), + mnPageOrder( XML_downThenOver ), + mnCellComments( XML_none ), + mnPrintErrors( XML_displayed ), + mbUseEvenHF( false ), + mbUseFirstHF( false ), + mbValidSettings( true ), + mbUseFirstPage( false ), + mbBlackWhite( false ), + mbDraftQuality( false ), + mbFitToPages( false ), + mbHorCenter( false ), + mbVerCenter( false ), + mbPrintGrid( false ), + mbPrintHeadings( false ) +{ +} + +void PageSettingsModel::setBinPrintErrors( sal_uInt8 nPrintErrors ) +{ + static const sal_Int32 spnErrorIds[] = { XML_displayed, XML_none, XML_dash, XML_NA }; + mnPrintErrors = STATIC_ARRAY_SELECT( spnErrorIds, nPrintErrors, XML_none ); +} + +// ============================================================================ + +PageSettings::PageSettings( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +void PageSettings::importPrintOptions( const AttributeList& rAttribs ) +{ + maModel.mbHorCenter = rAttribs.getBool( XML_horizontalCentered, false ); + maModel.mbVerCenter = rAttribs.getBool( XML_verticalCentered, false ); + maModel.mbPrintGrid = rAttribs.getBool( XML_gridLines, false ); + maModel.mbPrintHeadings = rAttribs.getBool( XML_headings, false ); +} + +void PageSettings::importPageMargins( const AttributeList& rAttribs ) +{ + maModel.mfLeftMargin = rAttribs.getDouble( XML_left, OOX_MARGIN_DEFAULT_LR ); + maModel.mfRightMargin = rAttribs.getDouble( XML_right, OOX_MARGIN_DEFAULT_LR ); + maModel.mfTopMargin = rAttribs.getDouble( XML_top, OOX_MARGIN_DEFAULT_TB ); + maModel.mfBottomMargin = rAttribs.getDouble( XML_bottom, OOX_MARGIN_DEFAULT_TB ); + maModel.mfHeaderMargin = rAttribs.getDouble( XML_header, OOX_MARGIN_DEFAULT_HF ); + maModel.mfFooterMargin = rAttribs.getDouble( XML_footer, OOX_MARGIN_DEFAULT_HF ); +} + +void PageSettings::importPageSetup( const Relations& rRelations, const AttributeList& rAttribs ) +{ + maModel.maBinSettPath = rRelations.getFragmentPathFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ); + maModel.mnPaperSize = rAttribs.getInteger( XML_paperSize, 1 ); + maModel.mnCopies = rAttribs.getInteger( XML_copies, 1 ); + maModel.mnScale = rAttribs.getInteger( XML_scale, 100 ); + maModel.mnFirstPage = rAttribs.getInteger( XML_firstPageNumber, 1 ); + maModel.mnFitToWidth = rAttribs.getInteger( XML_fitToWidth, 1 ); + maModel.mnFitToHeight = rAttribs.getInteger( XML_fitToHeight, 1 ); + maModel.mnHorPrintRes = rAttribs.getInteger( XML_horizontalDpi, 600 ); + maModel.mnVerPrintRes = rAttribs.getInteger( XML_verticalDpi, 600 ); + maModel.mnOrientation = rAttribs.getToken( XML_orientation, XML_default ); + maModel.mnPageOrder = rAttribs.getToken( XML_pageOrder, XML_downThenOver ); + maModel.mnCellComments = rAttribs.getToken( XML_cellComments, XML_none ); + maModel.mnPrintErrors = rAttribs.getToken( XML_errors, XML_displayed ); + maModel.mbValidSettings = rAttribs.getBool( XML_usePrinterDefaults, true ); + maModel.mbUseFirstPage = rAttribs.getBool( XML_useFirstPageNumber, false ); + maModel.mbBlackWhite = rAttribs.getBool( XML_blackAndWhite, false ); + maModel.mbDraftQuality = rAttribs.getBool( XML_draft, false ); +} + +void PageSettings::importChartPageSetup( const Relations& rRelations, const AttributeList& rAttribs ) +{ + maModel.maBinSettPath = rRelations.getFragmentPathFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ); + maModel.mnPaperSize = rAttribs.getInteger( XML_paperSize, 1 ); + maModel.mnCopies = rAttribs.getInteger( XML_copies, 1 ); + maModel.mnFirstPage = rAttribs.getInteger( XML_firstPageNumber, 1 ); + maModel.mnHorPrintRes = rAttribs.getInteger( XML_horizontalDpi, 600 ); + maModel.mnVerPrintRes = rAttribs.getInteger( XML_verticalDpi, 600 ); + maModel.mnOrientation = rAttribs.getToken( XML_orientation, XML_default ); + maModel.mbValidSettings = rAttribs.getBool( XML_usePrinterDefaults, true ); + maModel.mbUseFirstPage = rAttribs.getBool( XML_useFirstPageNumber, false ); + maModel.mbBlackWhite = rAttribs.getBool( XML_blackAndWhite, false ); + maModel.mbDraftQuality = rAttribs.getBool( XML_draft, false ); +} + +void PageSettings::importHeaderFooter( const AttributeList& rAttribs ) +{ + maModel.mbUseEvenHF = rAttribs.getBool( XML_differentOddEven, false ); + maModel.mbUseFirstHF = rAttribs.getBool( XML_differentFirst, false ); +} + +void PageSettings::importHeaderFooterCharacters( const OUString& rChars, sal_Int32 nElement ) +{ + switch( nElement ) + { + case XLS_TOKEN( oddHeader ): maModel.maOddHeader += rChars; break; + case XLS_TOKEN( oddFooter ): maModel.maOddFooter += rChars; break; + case XLS_TOKEN( evenHeader ): maModel.maEvenHeader += rChars; break; + case XLS_TOKEN( evenFooter ): maModel.maEvenFooter += rChars; break; + case XLS_TOKEN( firstHeader ): maModel.maFirstHeader += rChars; break; + case XLS_TOKEN( firstFooter ): maModel.maFirstFooter += rChars; break; + } +} + +void PageSettings::importPicture( const Relations& rRelations, const AttributeList& rAttribs ) +{ + importPictureData( rRelations, rAttribs.getString( R_TOKEN( id ), OUString() ) ); +} + +void PageSettings::importPageMargins( RecordInputStream& rStrm ) +{ + rStrm >> maModel.mfLeftMargin >> maModel.mfRightMargin + >> maModel.mfTopMargin >> maModel.mfBottomMargin + >> maModel.mfHeaderMargin >> maModel.mfFooterMargin; +} + +void PageSettings::importPrintOptions( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> nFlags; + maModel.mbHorCenter = getFlag( nFlags, OOBIN_PRINTOPT_HORCENTER ); + maModel.mbVerCenter = getFlag( nFlags, OOBIN_PRINTOPT_VERCENTER ); + maModel.mbPrintGrid = getFlag( nFlags, OOBIN_PRINTOPT_PRINTGRID ); + maModel.mbPrintHeadings = getFlag( nFlags, OOBIN_PRINTOPT_PRINTHEADING ); +} + +void PageSettings::importPageSetup( const Relations& rRelations, RecordInputStream& rStrm ) +{ + OUString aRelId; + sal_uInt16 nFlags; + rStrm >> maModel.mnPaperSize >> maModel.mnScale + >> maModel.mnHorPrintRes >> maModel.mnVerPrintRes + >> maModel.mnCopies >> maModel.mnFirstPage + >> maModel.mnFitToWidth >> maModel.mnFitToHeight + >> nFlags >> aRelId; + maModel.setBinPrintErrors( extractValue< sal_uInt8 >( nFlags, 9, 2 ) ); + maModel.maBinSettPath = rRelations.getFragmentPathFromRelId( aRelId ); + maModel.mnOrientation = getFlagValue( nFlags, OOBIN_PAGESETUP_DEFAULTORIENT, XML_default, getFlagValue( nFlags, OOBIN_PAGESETUP_LANDSCAPE, XML_landscape, XML_portrait ) ); + maModel.mnPageOrder = getFlagValue( nFlags, OOBIN_PAGESETUP_INROWS, XML_overThenDown, XML_downThenOver ); + maModel.mnCellComments = getFlagValue( nFlags, OOBIN_PAGESETUP_PRINTNOTES, getFlagValue( nFlags, OOBIN_PAGESETUP_NOTES_END, XML_atEnd, XML_asDisplayed ), XML_none ); + maModel.mbValidSettings = !getFlag( nFlags, OOBIN_PAGESETUP_INVALID ); + maModel.mbUseFirstPage = getFlag( nFlags, OOBIN_PAGESETUP_USEFIRSTPAGE ); + maModel.mbBlackWhite = getFlag( nFlags, OOBIN_PAGESETUP_BLACKWHITE ); + maModel.mbDraftQuality = getFlag( nFlags, OOBIN_PAGESETUP_DRAFTQUALITY ); +} + +void PageSettings::importChartPageSetup( const Relations& rRelations, RecordInputStream& rStrm ) +{ + OUString aRelId; + sal_uInt16 nFirstPage, nFlags; + rStrm >> maModel.mnPaperSize >> maModel.mnHorPrintRes >> maModel.mnVerPrintRes + >> maModel.mnCopies >> nFirstPage >> nFlags >> aRelId; + maModel.maBinSettPath = rRelations.getFragmentPathFromRelId( aRelId ); + maModel.mnFirstPage = nFirstPage; // 16-bit in CHARTPAGESETUP + maModel.mnOrientation = getFlagValue( nFlags, OOBIN_CHARTPAGESETUP_DEFAULTORIENT, XML_default, getFlagValue( nFlags, OOBIN_CHARTPAGESETUP_LANDSCAPE, XML_landscape, XML_portrait ) ); + maModel.mbValidSettings = !getFlag( nFlags, OOBIN_CHARTPAGESETUP_INVALID ); + maModel.mbUseFirstPage = getFlag( nFlags, OOBIN_CHARTPAGESETUP_USEFIRSTPAGE ); + maModel.mbBlackWhite = getFlag( nFlags, OOBIN_CHARTPAGESETUP_BLACKWHITE ); + maModel.mbDraftQuality = getFlag( nFlags, OOBIN_CHARTPAGESETUP_DRAFTQUALITY ); +} + +void PageSettings::importHeaderFooter( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> nFlags + >> maModel.maOddHeader >> maModel.maOddFooter + >> maModel.maEvenHeader >> maModel.maEvenFooter + >> maModel.maFirstHeader >> maModel.maFirstFooter; + maModel.mbUseEvenHF = getFlag( nFlags, OOBIN_HEADERFOOTER_DIFFEVEN ); + maModel.mbUseFirstHF = getFlag( nFlags, OOBIN_HEADERFOOTER_DIFFFIRST ); +} + +void PageSettings::importPicture( const Relations& rRelations, RecordInputStream& rStrm ) +{ + importPictureData( rRelations, rStrm.readString() ); +} + +void PageSettings::importLeftMargin( BiffInputStream& rStrm ) +{ + rStrm >> maModel.mfLeftMargin; +} + +void PageSettings::importRightMargin( BiffInputStream& rStrm ) +{ + rStrm >> maModel.mfRightMargin; +} + +void PageSettings::importTopMargin( BiffInputStream& rStrm ) +{ + rStrm >> maModel.mfTopMargin; +} + +void PageSettings::importBottomMargin( BiffInputStream& rStrm ) +{ + rStrm >> maModel.mfBottomMargin; +} + +void PageSettings::importPageSetup( BiffInputStream& rStrm ) +{ + sal_uInt16 nPaperSize, nScale, nFirstPage, nFitToWidth, nFitToHeight, nFlags; + rStrm >> nPaperSize >> nScale >> nFirstPage >> nFitToWidth >> nFitToHeight >> nFlags; + + maModel.mnPaperSize = nPaperSize; // equal in BIFF and OOX + maModel.mnScale = nScale; + maModel.mnFirstPage = nFirstPage; + maModel.mnFitToWidth = nFitToWidth; + maModel.mnFitToHeight = nFitToHeight; + maModel.mnOrientation = getFlagValue( nFlags, BIFF_PAGESETUP_PORTRAIT, XML_portrait, XML_landscape ); + maModel.mnPageOrder = getFlagValue( nFlags, BIFF_PAGESETUP_INROWS, XML_overThenDown, XML_downThenOver ); + maModel.mbValidSettings = !getFlag( nFlags, BIFF_PAGESETUP_INVALID ); + maModel.mbUseFirstPage = true; + maModel.mbBlackWhite = getFlag( nFlags, BIFF_PAGESETUP_BLACKWHITE ); + + if( getBiff() >= BIFF5 ) + { + sal_uInt16 nHorPrintRes, nVerPrintRes, nCopies; + rStrm >> nHorPrintRes >> nVerPrintRes >> maModel.mfHeaderMargin >> maModel.mfFooterMargin >> nCopies; + + maModel.mnCopies = nCopies; + maModel.mnOrientation = getFlagValue( nFlags, BIFF_PAGESETUP_DEFAULTORIENT, XML_default, maModel.mnOrientation ); + maModel.mnHorPrintRes = nHorPrintRes; + maModel.mnVerPrintRes = nVerPrintRes; + maModel.mnCellComments = getFlagValue( nFlags, BIFF_PAGESETUP_PRINTNOTES, XML_asDisplayed, XML_none ); + maModel.mbUseFirstPage = getFlag( nFlags, BIFF_PAGESETUP_USEFIRSTPAGE ); + maModel.mbDraftQuality = getFlag( nFlags, BIFF_PAGESETUP_DRAFTQUALITY ); + + if( getBiff() == BIFF8 ) + { + maModel.setBinPrintErrors( extractValue< sal_uInt8 >( nFlags, 10, 2 ) ); + maModel.mnCellComments = getFlagValue( nFlags, BIFF_PAGESETUP_PRINTNOTES, getFlagValue( nFlags, BIFF_PAGESETUP_NOTES_END, XML_atEnd, XML_asDisplayed ), XML_none ); + } + } +} + +void PageSettings::importHorCenter( BiffInputStream& rStrm ) +{ + maModel.mbHorCenter = rStrm.readuInt16() != 0; +} + +void PageSettings::importVerCenter( BiffInputStream& rStrm ) +{ + maModel.mbVerCenter = rStrm.readuInt16() != 0; +} + +void PageSettings::importPrintHeaders( BiffInputStream& rStrm ) +{ + maModel.mbPrintHeadings = rStrm.readuInt16() != 0; +} + +void PageSettings::importPrintGridLines( BiffInputStream& rStrm ) +{ + maModel.mbPrintGrid = rStrm.readuInt16() != 0; +} + +void PageSettings::importHeader( BiffInputStream& rStrm ) +{ + if( rStrm.getRemaining() > 0 ) + maModel.maOddHeader = (getBiff() == BIFF8) ? rStrm.readUniString() : rStrm.readByteStringUC( false, getTextEncoding() ); + else + maModel.maOddHeader = OUString(); +} + +void PageSettings::importFooter( BiffInputStream& rStrm ) +{ + if( rStrm.getRemaining() > 0 ) + maModel.maOddFooter = (getBiff() == BIFF8) ? rStrm.readUniString() : rStrm.readByteStringUC( false, getTextEncoding() ); + else + maModel.maOddFooter = OUString(); +} + +void PageSettings::importPicture( BiffInputStream& rStrm ) +{ + StreamDataSequence aPictureData; + BiffHelper::importImgData( aPictureData, rStrm, getBiff() ); + maModel.maGraphicUrl = getBaseFilter().getGraphicHelper().importGraphicObject( aPictureData ); +} + +void PageSettings::setFitToPagesMode( bool bFitToPages ) +{ + maModel.mbFitToPages = bFitToPages; +} + +void PageSettings::finalizeImport() +{ + OUStringBuffer aStyleNameBuffer( CREATE_OUSTRING( "PageStyle_" ) ); + Reference< XNamed > xSheetName( getSheet(), UNO_QUERY ); + if( xSheetName.is() ) + aStyleNameBuffer.append( xSheetName->getName() ); + else + aStyleNameBuffer.append( static_cast< sal_Int32 >( getSheetIndex() + 1 ) ); + OUString aStyleName = aStyleNameBuffer.makeStringAndClear(); + + Reference< XStyle > xStyle = createStyleObject( aStyleName, true ); + PropertySet aStyleProps( xStyle ); + getPageSettingsConverter().writePageSettingsProperties( aStyleProps, maModel, getSheetType() ); + + PropertySet aSheetProps( getSheet() ); + aSheetProps.setProperty( PROP_PageStyle, aStyleName ); +} + +void PageSettings::importPictureData( const Relations& rRelations, const OUString& rRelId ) +{ + OUString aPicturePath = rRelations.getFragmentPathFromRelId( rRelId ); + if( aPicturePath.getLength() > 0 ) + maModel.maGraphicUrl = getBaseFilter().getGraphicHelper().importEmbeddedGraphicObject( aPicturePath ); +} + +// ============================================================================ +// ============================================================================ + +enum HFPortionId +{ + HF_LEFT, + HF_CENTER, + HF_RIGHT, + HF_COUNT +}; + +// ---------------------------------------------------------------------------- + +struct HFPortionInfo +{ + Reference< XText > mxText; /// XText interface of this portion. + Reference< XTextCursor > mxStart; /// Start position of current text range for formatting. + Reference< XTextCursor > mxEnd; /// End position of current text range for formatting. + double mfTotalHeight; /// Sum of heights of previous lines in points. + double mfCurrHeight; /// Height of the current text line in points. + + bool initialize( const Reference< XText >& rxText ); +}; + +bool HFPortionInfo::initialize( const Reference< XText >& rxText ) +{ + mfTotalHeight = mfCurrHeight = 0.0; + mxText = rxText; + if( mxText.is() ) + { + mxStart = mxText->createTextCursor(); + mxEnd = mxText->createTextCursor(); + } + bool bRet = mxText.is() && mxStart.is() && mxEnd.is(); + OSL_ENSURE( bRet, "HFPortionInfo::initialize - missing interfaces" ); + return bRet; +} + +// ============================================================================ + +class HeaderFooterParser : public WorkbookHelper +{ +public: + explicit HeaderFooterParser( const WorkbookHelper& rHelper ); + + /** Parses the passed string and creates the header/footer contents. + @returns The total height of the converted header or footer in points. */ + double parse( + const Reference< XHeaderFooterContent >& rxContext, + const OUString& rData ); + +private: + /** Returns the current edit engine text object. */ + inline HFPortionInfo& getPortion() { return maPortions[ meCurrPortion ]; } + /** Returns the start cursor of the current text range. */ + inline const Reference< XTextCursor >& getStartPos() { return getPortion().mxStart; } + /** Returns the end cursor of the current text range. */ + inline const Reference< XTextCursor >& getEndPos() { return getPortion().mxEnd; } + + /** Returns the current line height of the specified portion. */ + double getCurrHeight( HFPortionId ePortion ) const; + /** Returns the current line height. */ + double getCurrHeight() const; + + /** Updates the current line height of the specified portion, using the current font size. */ + void updateCurrHeight( HFPortionId ePortion ); + /** Updates the current line height, using the current font size. */ + void updateCurrHeight(); + + /** Sets the font attributes at the current selection. */ + void setAttributes(); + /** Appends and clears internal string buffer. */ + void appendText(); + /** Appends a line break and adjusts internal text height data. */ + void appendLineBreak(); + + /** Creates a text field from the passed service name. */ + Reference< XTextContent > createField( const OUString& rServiceName ) const; + /** Appends the passed text field. */ + void appendField( const Reference< XTextContent >& rxContent ); + + /** Sets the passed font name if it is valid. */ + void convertFontName( const OUString& rStyle ); + /** Converts a font style given as string. */ + void convertFontStyle( const OUString& rStyle ); + /** Converts a font color given as string. */ + void convertFontColor( const OUString& rColor ); + + /** Finalizes current portion: sets font attributes and updates text height data. */ + void finalizePortion(); + /** Changes current header/footer portion. */ + void setNewPortion( HFPortionId ePortion ); + +private: + typedef ::std::vector< HFPortionInfo > HFPortionInfoVec; + typedef ::std::set< OString > OStringSet; + + const OUString maPageNumberService; + const OUString maPageCountService; + const OUString maSheetNameService; + const OUString maFileNameService; + const OUString maDateTimeService; + const OStringSet maBoldNames; /// All names for bold font style in lowercase UTF-8. + const OStringSet maItalicNames; /// All names for italic font style in lowercase UTF-8. + HFPortionInfoVec maPortions; + HFPortionId meCurrPortion; /// Identifier of current H/F portion. + OUStringBuffer maBuffer; /// Text data to append to current text range. + FontModel maFontModel; /// Font attributes of current text range. +}; + +// ---------------------------------------------------------------------------- + +namespace { + +// different names for bold font style (lowercase) +static const sal_Char* const sppcBoldNames[] = +{ + "bold", + "fett", // German 'bold' + "demibold", + "halbfett", // German 'demibold' + "black", + "heavy" +}; + +// different names for italic font style (lowercase) +static const sal_Char* const sppcItalicNames[] = +{ + "italic", + "kursiv", // German 'italic' + "oblique", + "schr\303\204g", // German 'oblique' with uppercase A umlaut + "schr\303\244g" // German 'oblique' with lowercase A umlaut +}; + +} // namespace + +// ---------------------------------------------------------------------------- + +HeaderFooterParser::HeaderFooterParser( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + maPageNumberService( CREATE_OUSTRING( "com.sun.star.text.TextField.PageNumber" ) ), + maPageCountService( CREATE_OUSTRING( "com.sun.star.text.TextField.PageCount" ) ), + maSheetNameService( CREATE_OUSTRING( "com.sun.star.text.TextField.SheetName" ) ), + maFileNameService( CREATE_OUSTRING( "com.sun.star.text.TextField.FileName" ) ), + maDateTimeService( CREATE_OUSTRING( "com.sun.star.text.TextField.DateTime" ) ), + maBoldNames( sppcBoldNames, STATIC_ARRAY_END( sppcBoldNames ) ), + maItalicNames( sppcItalicNames, STATIC_ARRAY_END( sppcItalicNames ) ), + maPortions( static_cast< size_t >( HF_COUNT ) ), + meCurrPortion( HF_CENTER ) +{ +} + +double HeaderFooterParser::parse( const Reference< XHeaderFooterContent >& rxContext, const OUString& rData ) +{ + if( !rxContext.is() || (rData.getLength() == 0) || + !maPortions[ HF_LEFT ].initialize( rxContext->getLeftText() ) || + !maPortions[ HF_CENTER ].initialize( rxContext->getCenterText() ) || + !maPortions[ HF_RIGHT ].initialize( rxContext->getRightText() ) ) + return 0.0; + + meCurrPortion = HF_CENTER; + maBuffer.setLength( 0 ); + maFontModel = getStyles().getDefaultFontModel(); + OUStringBuffer aFontName; // current font name + OUStringBuffer aFontStyle; // current font style + sal_Int32 nFontHeight = 0; // current font height + + /** State of the parser. */ + enum + { + STATE_TEXT, /// Literal text data. + STATE_TOKEN, /// Control token following a '&' character. + STATE_FONTNAME, /// Font name ('&' is followed by '"', reads until next '"' or ','). + STATE_FONTSTYLE, /// Font style name (font part after ',', reads until next '"'). + STATE_FONTHEIGHT /// Font height ('&' is followed by num. digits, reads until non-digit). + } + eState = STATE_TEXT; + + const sal_Unicode* pcChar = rData.getStr(); + const sal_Unicode* pcEnd = pcChar + rData.getLength(); + for( ; (pcChar != pcEnd) && (*pcChar != 0); ++pcChar ) + { + sal_Unicode cChar = *pcChar; + switch( eState ) + { + case STATE_TEXT: + { + switch( cChar ) + { + case '&': // new token + appendText(); + eState = STATE_TOKEN; + break; + case '\n': // line break + appendText(); + appendLineBreak(); + break; + default: + maBuffer.append( cChar ); + } + } + break; + + case STATE_TOKEN: + { + // default: back to text mode, may be changed in specific cases + eState = STATE_TEXT; + // ignore case of token codes + if( ('a' <= cChar) && (cChar <= 'z') ) + (cChar -= 'a') += 'A'; + switch( cChar ) + { + case '&': maBuffer.append( cChar ); break; // the '&' character + + case 'L': setNewPortion( HF_LEFT ); break; // left portion + case 'C': setNewPortion( HF_CENTER ); break; // center portion + case 'R': setNewPortion( HF_RIGHT ); break; // right portion + + case 'P': // page number + appendField( createField( maPageNumberService ) ); + break; + case 'N': // total page count + appendField( createField( maPageCountService ) ); + break; + case 'A': // current sheet name + appendField( createField( maSheetNameService ) ); + break; + + case 'F': // file name + { + Reference< XTextContent > xContent = createField( maFileNameService ); + PropertySet aPropSet( xContent ); + aPropSet.setProperty( PROP_FileFormat, ::com::sun::star::text::FilenameDisplayFormat::NAME_AND_EXT ); + appendField( xContent ); + } + break; + case 'Z': // file path (without file name), BIFF8 and OOX only + if( (getFilterType() == FILTER_OOX) || ((getFilterType() == FILTER_BIFF) && (getBiff() == BIFF8)) ) + { + Reference< XTextContent > xContent = createField( maFileNameService ); + PropertySet aPropSet( xContent ); + // FilenameDisplayFormat::PATH not supported by Calc + aPropSet.setProperty( PROP_FileFormat, ::com::sun::star::text::FilenameDisplayFormat::FULL ); + appendField( xContent ); + /* path only is not supported -- if we find a '&Z&F' + combination for path/name, skip the '&F' part */ + if( (pcChar + 2 < pcEnd) && (pcChar[ 1 ] == '&') && ((pcChar[ 2 ] == 'f') || (pcChar[ 2 ] == 'F')) ) + pcChar += 2; + } + break; + case 'D': // date + { + Reference< XTextContent > xContent = createField( maDateTimeService ); + PropertySet aPropSet( xContent ); + aPropSet.setProperty( PROP_IsDate, true ); + appendField( xContent ); + } + break; + case 'T': // time + { + Reference< XTextContent > xContent = createField( maDateTimeService ); + PropertySet aPropSet( xContent ); + aPropSet.setProperty( PROP_IsDate, false ); + appendField( xContent ); + } + break; + + case 'B': // bold + setAttributes(); + maFontModel.mbBold = !maFontModel.mbBold; + break; + case 'I': // italic + setAttributes(); + maFontModel.mbItalic = !maFontModel.mbItalic; + break; + case 'U': // underline + setAttributes(); + maFontModel.mnUnderline = (maFontModel.mnUnderline == XML_single) ? XML_none : XML_single; + break; + case 'E': // double underline + setAttributes(); + maFontModel.mnUnderline = (maFontModel.mnUnderline == XML_double) ? XML_none : XML_double; + break; + case 'S': // strikeout + setAttributes(); + maFontModel.mbStrikeout = !maFontModel.mbStrikeout; + break; + case 'X': // superscript + setAttributes(); + maFontModel.mnEscapement = (maFontModel.mnEscapement == XML_superscript) ? XML_baseline : XML_superscript; + break; + case 'Y': // subsrcipt + setAttributes(); + maFontModel.mnEscapement = (maFontModel.mnEscapement == XML_subscript) ? XML_baseline : XML_subscript; + break; + case 'O': // outlined + setAttributes(); + maFontModel.mbOutline = !maFontModel.mbOutline; + break; + case 'H': // shadow + setAttributes(); + maFontModel.mbShadow = !maFontModel.mbShadow; + break; + + case 'K': // text color (not in BIFF) + if( (getFilterType() == FILTER_OOX) && (pcChar + 6 < pcEnd) ) + { + setAttributes(); + // eat the following 6 characters + convertFontColor( OUString( pcChar + 1, 6 ) ); + pcChar += 6; + } + break; + + case '\"': // font name + aFontName.setLength( 0 ); + aFontStyle.setLength( 0 ); + eState = STATE_FONTNAME; + break; + default: + if( ('0' <= cChar) && (cChar <= '9') ) // font size + { + nFontHeight = cChar - '0'; + eState = STATE_FONTHEIGHT; + } + } + } + break; + + case STATE_FONTNAME: + { + switch( cChar ) + { + case '\"': + setAttributes(); + convertFontName( aFontName.makeStringAndClear() ); + eState = STATE_TEXT; + break; + case ',': + eState = STATE_FONTSTYLE; + break; + default: + aFontName.append( cChar ); + } + } + break; + + case STATE_FONTSTYLE: + { + switch( cChar ) + { + case '\"': + setAttributes(); + convertFontName( aFontName.makeStringAndClear() ); + convertFontStyle( aFontStyle.makeStringAndClear() ); + eState = STATE_TEXT; + break; + default: + aFontStyle.append( cChar ); + } + } + break; + + case STATE_FONTHEIGHT: + { + if( ('0' <= cChar) && (cChar <= '9') ) + { + if( nFontHeight >= 0 ) + { + nFontHeight *= 10; + nFontHeight += (cChar - '0'); + if( nFontHeight > 1000 ) + nFontHeight = -1; + } + } + else + { + if( nFontHeight > 0 ) + { + setAttributes(); + maFontModel.mfHeight = nFontHeight; + } + --pcChar; + eState = STATE_TEXT; + } + } + break; + } + } + + // finalize + finalizePortion(); + maPortions[ HF_LEFT ].mfTotalHeight += getCurrHeight( HF_LEFT ); + maPortions[ HF_CENTER ].mfTotalHeight += getCurrHeight( HF_CENTER ); + maPortions[ HF_RIGHT ].mfTotalHeight += getCurrHeight( HF_RIGHT ); + + return ::std::max( maPortions[ HF_LEFT ].mfTotalHeight, + ::std::max( maPortions[ HF_CENTER ].mfTotalHeight, maPortions[ HF_RIGHT ].mfTotalHeight ) ); +} + +// private -------------------------------------------------------------------- + +double HeaderFooterParser::getCurrHeight( HFPortionId ePortion ) const +{ + double fMaxHt = maPortions[ ePortion ].mfCurrHeight; + return (fMaxHt == 0.0) ? maFontModel.mfHeight : fMaxHt; +} + +double HeaderFooterParser::getCurrHeight() const +{ + return getCurrHeight( meCurrPortion ); +} + +void HeaderFooterParser::updateCurrHeight( HFPortionId ePortion ) +{ + double& rfMaxHt = maPortions[ ePortion ].mfCurrHeight; + rfMaxHt = ::std::max( rfMaxHt, maFontModel.mfHeight ); +} + +void HeaderFooterParser::updateCurrHeight() +{ + updateCurrHeight( meCurrPortion ); +} + +void HeaderFooterParser::setAttributes() +{ + Reference< XTextRange > xRange( getStartPos(), UNO_QUERY ); + getEndPos()->gotoRange( xRange, sal_False ); + getEndPos()->gotoEnd( sal_True ); + if( !getEndPos()->isCollapsed() ) + { + Font aFont( *this, maFontModel ); + aFont.finalizeImport(); + PropertySet aPropSet( getEndPos() ); + aFont.writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT ); + getStartPos()->gotoEnd( sal_False ); + getEndPos()->gotoEnd( sal_False ); + } +} + +void HeaderFooterParser::appendText() +{ + if( maBuffer.getLength() > 0 ) + { + getEndPos()->gotoEnd( sal_False ); + getEndPos()->setString( maBuffer.makeStringAndClear() ); + updateCurrHeight(); + } +} + +void HeaderFooterParser::appendLineBreak() +{ + getEndPos()->gotoEnd( sal_False ); + getEndPos()->setString( OUString( sal_Unicode( '\n' ) ) ); + getPortion().mfTotalHeight += getCurrHeight(); + getPortion().mfCurrHeight = 0; +} + +Reference< XTextContent > HeaderFooterParser::createField( const OUString& rServiceName ) const +{ + Reference< XTextContent > xContent; + try + { + Reference< XMultiServiceFactory > xFactory( getDocument(), UNO_QUERY_THROW ); + xContent.set( xFactory->createInstance( rServiceName ), UNO_QUERY_THROW ); + } + catch( Exception& ) + { + OSL_ENSURE( false, + OStringBuffer( "HeaderFooterParser::createField - error while creating text field \"" ). + append( OUStringToOString( rServiceName, RTL_TEXTENCODING_ASCII_US ) ). + append( '"' ).getStr() ); + } + return xContent; +} + +void HeaderFooterParser::appendField( const Reference< XTextContent >& rxContent ) +{ + getEndPos()->gotoEnd( sal_False ); + try + { + Reference< XTextRange > xRange( getEndPos(), UNO_QUERY_THROW ); + getPortion().mxText->insertTextContent( xRange, rxContent, sal_False ); + updateCurrHeight(); + } + catch( Exception& ) + { + } +} + +void HeaderFooterParser::convertFontName( const OUString& rName ) +{ + if( rName.getLength() > 0 ) + { + // single dash is document default font + if( (rName.getLength() == 1) && (rName[ 0 ] == '-') ) + maFontModel.maName = getStyles().getDefaultFontModel().maName; + else + maFontModel.maName = rName; + } +} + +void HeaderFooterParser::convertFontStyle( const OUString& rStyle ) +{ + maFontModel.mbBold = maFontModel.mbItalic = false; + sal_Int32 nPos = 0; + sal_Int32 nLen = rStyle.getLength(); + while( (0 <= nPos) && (nPos < nLen) ) + { + OString aToken = OUStringToOString( rStyle.getToken( 0, ' ', nPos ), RTL_TEXTENCODING_UTF8 ).toAsciiLowerCase(); + if( aToken.getLength() > 0 ) + { + if( maBoldNames.count( aToken ) > 0 ) + maFontModel.mbBold = true; + else if( maItalicNames.count( aToken ) > 0 ) + maFontModel.mbItalic = true; + } + } +} + +void HeaderFooterParser::convertFontColor( const OUString& rColor ) +{ + OSL_ENSURE( rColor.getLength() == 6, "HeaderFooterParser::convertFontColor - invalid font color code" ); + if( (rColor[ 2 ] == '+') || (rColor[ 2 ] == '-') ) + // theme color: TTSNNN (TT = decimal theme index, S = +/-, NNN = decimal tint/shade in percent) + maFontModel.maColor.setTheme( + rColor.copy( 0, 2 ).toInt32(), + static_cast< double >( rColor.copy( 2 ).toInt32() ) / 100.0 ); + else + // RGB color: RRGGBB + maFontModel.maColor.setRgb( rColor.toInt32( 16 ) ); +} + +void HeaderFooterParser::finalizePortion() +{ + appendText(); + setAttributes(); +} + +void HeaderFooterParser::setNewPortion( HFPortionId ePortion ) +{ + if( ePortion != meCurrPortion ) + { + finalizePortion(); + meCurrPortion = ePortion; + maFontModel = getStyles().getDefaultFontModel(); + } +} + +// ============================================================================ + +namespace { + +/** Paper size in 1/100 millimeters. */ +struct ApiPaperSize +{ + sal_Int32 mnWidth; + sal_Int32 mnHeight; +}; + +#define IN2MM100( v ) static_cast< sal_Int32 >( (v) * 2540.0 + 0.5 ) +#define MM2MM100( v ) static_cast< sal_Int32 >( (v) * 100.0 + 0.5 ) + +static const ApiPaperSize spPaperSizeTable[] = +{ + { 0, 0 }, // 0 - (undefined) + { IN2MM100( 8.5 ), IN2MM100( 11 ) }, // 1 - Letter paper + { IN2MM100( 8.5 ), IN2MM100( 11 ) }, // 2 - Letter small paper + { IN2MM100( 11 ), IN2MM100( 17 ) }, // 3 - Tabloid paper + { IN2MM100( 17 ), IN2MM100( 11 ) }, // 4 - Ledger paper + { IN2MM100( 8.5 ), IN2MM100( 14 ) }, // 5 - Legal paper + { IN2MM100( 5.5 ), IN2MM100( 8.5 ) }, // 6 - Statement paper + { IN2MM100( 7.25 ), IN2MM100( 10.5 ) }, // 7 - Executive paper + { MM2MM100( 297 ), MM2MM100( 420 ) }, // 8 - A3 paper + { MM2MM100( 210 ), MM2MM100( 297 ) }, // 9 - A4 paper + { MM2MM100( 210 ), MM2MM100( 297 ) }, // 10 - A4 small paper + { MM2MM100( 148 ), MM2MM100( 210 ) }, // 11 - A5 paper + { MM2MM100( 250 ), MM2MM100( 353 ) }, // 12 - B4 paper + { MM2MM100( 176 ), MM2MM100( 250 ) }, // 13 - B5 paper + { IN2MM100( 8.5 ), IN2MM100( 13 ) }, // 14 - Folio paper + { MM2MM100( 215 ), MM2MM100( 275 ) }, // 15 - Quarto paper + { IN2MM100( 10 ), IN2MM100( 14 ) }, // 16 - Standard paper + { IN2MM100( 11 ), IN2MM100( 17 ) }, // 17 - Standard paper + { IN2MM100( 8.5 ), IN2MM100( 11 ) }, // 18 - Note paper + { IN2MM100( 3.875 ), IN2MM100( 8.875 ) }, // 19 - #9 envelope + { IN2MM100( 4.125 ), IN2MM100( 9.5 ) }, // 20 - #10 envelope + { IN2MM100( 4.5 ), IN2MM100( 10.375 ) }, // 21 - #11 envelope + { IN2MM100( 4.75 ), IN2MM100( 11 ) }, // 22 - #12 envelope + { IN2MM100( 5 ), IN2MM100( 11.5 ) }, // 23 - #14 envelope + { IN2MM100( 17 ), IN2MM100( 22 ) }, // 24 - C paper + { IN2MM100( 22 ), IN2MM100( 34 ) }, // 25 - D paper + { IN2MM100( 34 ), IN2MM100( 44 ) }, // 26 - E paper + { MM2MM100( 110 ), MM2MM100( 220 ) }, // 27 - DL envelope + { MM2MM100( 162 ), MM2MM100( 229 ) }, // 28 - C5 envelope + { MM2MM100( 324 ), MM2MM100( 458 ) }, // 29 - C3 envelope + { MM2MM100( 229 ), MM2MM100( 324 ) }, // 30 - C4 envelope + { MM2MM100( 114 ), MM2MM100( 162 ) }, // 31 - C6 envelope + { MM2MM100( 114 ), MM2MM100( 229 ) }, // 32 - C65 envelope + { MM2MM100( 250 ), MM2MM100( 353 ) }, // 33 - B4 envelope + { MM2MM100( 176 ), MM2MM100( 250 ) }, // 34 - B5 envelope + { MM2MM100( 176 ), MM2MM100( 125 ) }, // 35 - B6 envelope + { MM2MM100( 110 ), MM2MM100( 230 ) }, // 36 - Italy envelope + { IN2MM100( 3.875 ), IN2MM100( 7.5 ) }, // 37 - Monarch envelope + { IN2MM100( 3.625 ), IN2MM100( 6.5 ) }, // 38 - 6 3/4 envelope + { IN2MM100( 14.875 ), IN2MM100( 11 ) }, // 39 - US standard fanfold + { IN2MM100( 8.5 ), IN2MM100( 12 ) }, // 40 - German standard fanfold + { IN2MM100( 8.5 ), IN2MM100( 13 ) }, // 41 - German legal fanfold + { MM2MM100( 250 ), MM2MM100( 353 ) }, // 42 - ISO B4 + { MM2MM100( 200 ), MM2MM100( 148 ) }, // 43 - Japanese double postcard + { IN2MM100( 9 ), IN2MM100( 11 ) }, // 44 - Standard paper + { IN2MM100( 10 ), IN2MM100( 11 ) }, // 45 - Standard paper + { IN2MM100( 15 ), IN2MM100( 11 ) }, // 46 - Standard paper + { MM2MM100( 220 ), MM2MM100( 220 ) }, // 47 - Invite envelope + { 0, 0 }, // 48 - (undefined) + { 0, 0 }, // 49 - (undefined) + { IN2MM100( 9.275 ), IN2MM100( 12 ) }, // 50 - Letter extra paper + { IN2MM100( 9.275 ), IN2MM100( 15 ) }, // 51 - Legal extra paper + { IN2MM100( 11.69 ), IN2MM100( 18 ) }, // 52 - Tabloid extra paper + { MM2MM100( 236 ), MM2MM100( 322 ) }, // 53 - A4 extra paper + { IN2MM100( 8.275 ), IN2MM100( 11 ) }, // 54 - Letter transverse paper + { MM2MM100( 210 ), MM2MM100( 297 ) }, // 55 - A4 transverse paper + { IN2MM100( 9.275 ), IN2MM100( 12 ) }, // 56 - Letter extra transverse paper + { MM2MM100( 227 ), MM2MM100( 356 ) }, // 57 - SuperA/SuperA/A4 paper + { MM2MM100( 305 ), MM2MM100( 487 ) }, // 58 - SuperB/SuperB/A3 paper + { IN2MM100( 8.5 ), IN2MM100( 12.69 ) }, // 59 - Letter plus paper + { MM2MM100( 210 ), MM2MM100( 330 ) }, // 60 - A4 plus paper + { MM2MM100( 148 ), MM2MM100( 210 ) }, // 61 - A5 transverse paper + { MM2MM100( 182 ), MM2MM100( 257 ) }, // 62 - JIS B5 transverse paper + { MM2MM100( 322 ), MM2MM100( 445 ) }, // 63 - A3 extra paper + { MM2MM100( 174 ), MM2MM100( 235 ) }, // 64 - A5 extra paper + { MM2MM100( 201 ), MM2MM100( 276 ) }, // 65 - ISO B5 extra paper + { MM2MM100( 420 ), MM2MM100( 594 ) }, // 66 - A2 paper + { MM2MM100( 297 ), MM2MM100( 420 ) }, // 67 - A3 transverse paper + { MM2MM100( 322 ), MM2MM100( 445 ) } // 68 - A3 extra transverse paper +}; + +} // namespace + +// ---------------------------------------------------------------------------- + +PageSettingsConverter::HFHelperData::HFHelperData( sal_Int32 nLeftPropId, sal_Int32 nRightPropId ) : + mnLeftPropId( nLeftPropId ), + mnRightPropId( nRightPropId ), + mnHeight( 0 ), + mnBodyDist( 0 ), + mbHasContent( false ), + mbShareOddEven( false ), + mbDynamicHeight( false ) +{ +} + +// ---------------------------------------------------------------------------- + +PageSettingsConverter::PageSettingsConverter( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mxHFParser( new HeaderFooterParser( rHelper ) ), + maHeaderData( PROP_LeftPageHeaderContent, PROP_RightPageHeaderContent ), + maFooterData( PROP_LeftPageFooterContent, PROP_RightPageFooterContent ) +{ +} + +PageSettingsConverter::~PageSettingsConverter() +{ +} + +void PageSettingsConverter::writePageSettingsProperties( + PropertySet& rPropSet, const PageSettingsModel& rModel, WorksheetType eSheetType ) +{ + // special handling for chart sheets + bool bChartSheet = eSheetType == SHEETTYPE_CHARTSHEET; + + // printout scaling + if( bChartSheet ) + { + // always fit chart sheet to 1 page + rPropSet.setProperty< sal_Int16 >( PROP_ScaleToPages, 1 ); + } + else if( rModel.mbFitToPages ) + { + // fit to number of pages + rPropSet.setProperty( PROP_ScaleToPagesX, getLimitedValue< sal_Int16, sal_Int32 >( rModel.mnFitToWidth, 0, 1000 ) ); + rPropSet.setProperty( PROP_ScaleToPagesY, getLimitedValue< sal_Int16, sal_Int32 >( rModel.mnFitToHeight, 0, 1000 ) ); + } + else + { + // scale may be 0 which indicates uninitialized + sal_Int16 nScale = (rModel.mbValidSettings && (rModel.mnScale > 0)) ? getLimitedValue< sal_Int16, sal_Int32 >( rModel.mnScale, 10, 400 ) : 100; + rPropSet.setProperty( PROP_PageScale, nScale ); + } + + // paper orientation + bool bLandscape = rModel.mnOrientation == XML_landscape; + // default orientation for current sheet type (chart sheets default to landscape) + if( !rModel.mbValidSettings || (rModel.mnOrientation == XML_default) ) + bLandscape = bChartSheet; + + // paper size + if( rModel.mbValidSettings && (0 < rModel.mnPaperSize) && (rModel.mnPaperSize < static_cast< sal_Int32 >( STATIC_ARRAY_SIZE( spPaperSizeTable ) )) ) + { + const ApiPaperSize& rPaperSize = spPaperSizeTable[ rModel.mnPaperSize ]; + Size aSize( rPaperSize.mnWidth, rPaperSize.mnHeight ); + if( bLandscape ) + ::std::swap( aSize.Width, aSize.Height ); + rPropSet.setProperty( PROP_Size, aSize ); + } + + // header/footer + convertHeaderFooterData( rPropSet, maHeaderData, rModel.maOddHeader, rModel.maEvenHeader, rModel.mbUseEvenHF, rModel.mfTopMargin, rModel.mfHeaderMargin ); + convertHeaderFooterData( rPropSet, maFooterData, rModel.maOddFooter, rModel.maEvenFooter, rModel.mbUseEvenHF, rModel.mfBottomMargin, rModel.mfFooterMargin ); + + // write all properties to property set + const UnitConverter& rUnitConv = getUnitConverter(); + PropertyMap aPropMap; + aPropMap[ PROP_IsLandscape ] <<= bLandscape; + aPropMap[ PROP_FirstPageNumber ] <<= getLimitedValue< sal_Int16, sal_Int32 >( rModel.mbUseFirstPage ? rModel.mnFirstPage : 0, 0, 9999 ); + aPropMap[ PROP_PrintDownFirst ] <<= (rModel.mnPageOrder == XML_downThenOver); + aPropMap[ PROP_PrintAnnotations ] <<= (rModel.mnCellComments == XML_asDisplayed); + aPropMap[ PROP_CenterHorizontally ] <<= rModel.mbHorCenter; + aPropMap[ PROP_CenterVertically ] <<= rModel.mbVerCenter; + aPropMap[ PROP_PrintGrid ] <<= (!bChartSheet && rModel.mbPrintGrid); // no gridlines in chart sheets + aPropMap[ PROP_PrintHeaders ] <<= (!bChartSheet && rModel.mbPrintHeadings); // no column/row headings in chart sheets + aPropMap[ PROP_LeftMargin ] <<= rUnitConv.scaleToMm100( rModel.mfLeftMargin, UNIT_INCH ); + aPropMap[ PROP_RightMargin ] <<= rUnitConv.scaleToMm100( rModel.mfRightMargin, UNIT_INCH ); + // #i23296# In Calc, "TopMargin" property is distance to top of header if enabled + aPropMap[ PROP_TopMargin ] <<= rUnitConv.scaleToMm100( maHeaderData.mbHasContent ? rModel.mfHeaderMargin : rModel.mfTopMargin, UNIT_INCH ); + // #i23296# In Calc, "BottomMargin" property is distance to bottom of footer if enabled + aPropMap[ PROP_BottomMargin ] <<= rUnitConv.scaleToMm100( maFooterData.mbHasContent ? rModel.mfFooterMargin : rModel.mfBottomMargin, UNIT_INCH ); + aPropMap[ PROP_HeaderIsOn ] <<= maHeaderData.mbHasContent; + aPropMap[ PROP_HeaderIsShared ] <<= maHeaderData.mbShareOddEven; + aPropMap[ PROP_HeaderIsDynamicHeight ] <<= maHeaderData.mbDynamicHeight; + aPropMap[ PROP_HeaderHeight ] <<= maHeaderData.mnHeight; + aPropMap[ PROP_HeaderBodyDistance ] <<= maHeaderData.mnBodyDist; + aPropMap[ PROP_FooterIsOn ] <<= maFooterData.mbHasContent; + aPropMap[ PROP_FooterIsShared ] <<= maFooterData.mbShareOddEven; + aPropMap[ PROP_FooterIsDynamicHeight ] <<= maFooterData.mbDynamicHeight; + aPropMap[ PROP_FooterHeight ] <<= maFooterData.mnHeight; + aPropMap[ PROP_FooterBodyDistance ] <<= maFooterData.mnBodyDist; + // background image + if( rModel.maGraphicUrl.getLength() > 0 ) + { + aPropMap[ PROP_BackGraphicURL ] <<= rModel.maGraphicUrl; + aPropMap[ PROP_BackGraphicLocation ] <<= ::com::sun::star::style::GraphicLocation_TILED; + } + + rPropSet.setProperties( aPropMap ); +} + +void PageSettingsConverter::convertHeaderFooterData( + PropertySet& rPropSet, HFHelperData& orHFData, + const OUString rOddContent, const OUString rEvenContent, bool bUseEvenContent, + double fPageMargin, double fContentMargin ) +{ + bool bHasOddContent = rOddContent.getLength() > 0; + bool bHasEvenContent = bUseEvenContent && (rEvenContent.getLength() > 0); + + sal_Int32 nOddHeight = bHasOddContent ? writeHeaderFooter( rPropSet, orHFData.mnRightPropId, rOddContent ) : 0; + sal_Int32 nEvenHeight = bHasEvenContent ? writeHeaderFooter( rPropSet, orHFData.mnLeftPropId, rEvenContent ) : 0; + + orHFData.mnHeight = 750; + orHFData.mnBodyDist = 250; + orHFData.mbHasContent = bHasOddContent || bHasEvenContent; + orHFData.mbShareOddEven = !bUseEvenContent; + orHFData.mbDynamicHeight = true; + + if( orHFData.mbHasContent ) + { + // use maximum height of odd/even header/footer + orHFData.mnHeight = ::std::max( nOddHeight, nEvenHeight ); + /* Calc contains distance between bottom of header and top of page + body in "HeaderBodyDistance" property, and distance between bottom + of page body and top of footer in "FooterBodyDistance" property */ + orHFData.mnBodyDist = getUnitConverter().scaleToMm100( fPageMargin - fContentMargin, UNIT_INCH ) - orHFData.mnHeight; + /* #i23296# Distance less than 0 means, header or footer overlays page + body. As this is not possible in Calc, set fixed header or footer + height (crop header/footer) to get correct top position of page body. */ + orHFData.mbDynamicHeight = orHFData.mnBodyDist >= 0; + /* "HeaderHeight" property is in fact distance from top of header to + top of page body (including "HeaderBodyDistance"). + "FooterHeight" property is in fact distance from bottom of page + body to bottom of footer (including "FooterBodyDistance"). */ + orHFData.mnHeight += orHFData.mnBodyDist; + // negative body distance not allowed + orHFData.mnBodyDist = ::std::max< sal_Int32 >( orHFData.mnBodyDist, 0 ); + } +} + +sal_Int32 PageSettingsConverter::writeHeaderFooter( + PropertySet& rPropSet, sal_Int32 nPropId, const OUString& rContent ) +{ + OSL_ENSURE( rContent.getLength() > 0, "PageSettingsConverter::writeHeaderFooter - empty h/f string found" ); + sal_Int32 nHeight = 0; + if( rContent.getLength() > 0 ) + { + Reference< XHeaderFooterContent > xHFContent; + if( rPropSet.getProperty( xHFContent, nPropId ) && xHFContent.is() ) + { + double fTotalHeight = mxHFParser->parse( xHFContent, rContent ); + rPropSet.setProperty( nPropId, xHFContent ); + nHeight = getUnitConverter().scaleToMm100( fTotalHeight, UNIT_POINT ); + } + } + return nHeight; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/pivotcachebuffer.cxx b/oox/source/xls/pivotcachebuffer.cxx new file mode 100644 index 000000000000..d76ab81d07c4 --- /dev/null +++ b/oox/source/xls/pivotcachebuffer.cxx @@ -0,0 +1,1548 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/pivotcachebuffer.hxx" +#include <set> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/table/XCell.hpp> +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldGroupInfo.hpp> +#include <com/sun/star/sheet/XDataPilotFieldGrouping.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/defnamesbuffer.hxx" +#include "oox/xls/excelhandlers.hxx" +#include "oox/xls/pivotcachefragment.hxx" +#include "oox/xls/tablebuffer.hxx" +#include "oox/xls/unitconverter.hxx" +#include "oox/xls/worksheetbuffer.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::container::XNamed; +using ::com::sun::star::util::DateTime; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::sheet::DataPilotFieldGroupInfo; +using ::com::sun::star::table::XCell; +using ::com::sun::star::sheet::XDataPilotField; +using ::com::sun::star::sheet::XDataPilotFieldGrouping; +using ::oox::core::Relations; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt16 OOBIN_PCDFIELD_SERVERFIELD = 0x0001; +const sal_uInt16 OOBIN_PCDFIELD_NOUNIQUEITEMS = 0x0002; +const sal_uInt16 OOBIN_PCDFIELD_DATABASEFIELD = 0x0004; +const sal_uInt16 OOBIN_PCDFIELD_HASCAPTION = 0x0008; +const sal_uInt16 OOBIN_PCDFIELD_MEMBERPROPFIELD = 0x0010; +const sal_uInt16 OOBIN_PCDFIELD_HASFORMULA = 0x0100; +const sal_uInt16 OOBIN_PCDFIELD_HASPROPERTYNAME = 0x0200; + +const sal_uInt16 OOBIN_PCDFSITEMS_HASSEMIMIXED = 0x0001; +const sal_uInt16 OOBIN_PCDFSITEMS_HASNONDATE = 0x0002; +const sal_uInt16 OOBIN_PCDFSITEMS_HASDATE = 0x0004; +const sal_uInt16 OOBIN_PCDFSITEMS_HASSTRING = 0x0008; +const sal_uInt16 OOBIN_PCDFSITEMS_HASBLANK = 0x0010; +const sal_uInt16 OOBIN_PCDFSITEMS_HASMIXED = 0x0020; +const sal_uInt16 OOBIN_PCDFSITEMS_ISNUMERIC = 0x0040; +const sal_uInt16 OOBIN_PCDFSITEMS_ISINTEGER = 0x0080; +const sal_uInt16 OOBIN_PCDFSITEMS_HASMINMAX = 0x0100; +const sal_uInt16 OOBIN_PCDFSITEMS_HASLONGTEXT = 0x0200; + +const sal_uInt16 OOBIN_PCITEM_ARRAY_DOUBLE = 0x0001; +const sal_uInt16 OOBIN_PCITEM_ARRAY_STRING = 0x0002; +const sal_uInt16 OOBIN_PCITEM_ARRAY_ERROR = 0x0010; +const sal_uInt16 OOBIN_PCITEM_ARRAY_DATE = 0x0020; + +const sal_uInt8 OOBIN_PCDFRANGEPR_AUTOSTART = 0x01; +const sal_uInt8 OOBIN_PCDFRANGEPR_AUTOEND = 0x02; +const sal_uInt8 OOBIN_PCDFRANGEPR_DATEGROUP = 0x04; + +const sal_uInt8 OOBIN_PCDEFINITION_SAVEDATA = 0x01; +const sal_uInt8 OOBIN_PCDEFINITION_INVALID = 0x02; +const sal_uInt8 OOBIN_PCDEFINITION_REFRESHONLOAD = 0x04; +const sal_uInt8 OOBIN_PCDEFINITION_OPTIMIZEMEMORY = 0x08; +const sal_uInt8 OOBIN_PCDEFINITION_ENABLEREFRESH = 0x10; +const sal_uInt8 OOBIN_PCDEFINITION_BACKGROUNDQUERY = 0x20; +const sal_uInt8 OOBIN_PCDEFINITION_UPGRADEONREFR = 0x40; +const sal_uInt8 OOBIN_PCDEFINITION_TUPELCACHE = 0x80; + +const sal_uInt8 OOBIN_PCDEFINITION_HASUSERNAME = 0x01; +const sal_uInt8 OOBIN_PCDEFINITION_HASRELID = 0x02; +const sal_uInt8 OOBIN_PCDEFINITION_SUPPORTSUBQUERY = 0x04; +const sal_uInt8 OOBIN_PCDEFINITION_SUPPORTDRILL = 0x08; + +const sal_uInt8 OOBIN_PCDWBSOURCE_HASRELID = 0x01; +const sal_uInt8 OOBIN_PCDWBSOURCE_HASSHEET = 0x02; + +const sal_uInt16 BIFF_PCDSOURCE_WORKSHEET = 0x0001; +const sal_uInt16 BIFF_PCDSOURCE_EXTERNAL = 0x0002; +const sal_uInt16 BIFF_PCDSOURCE_CONSOLIDATION = 0x0004; +const sal_uInt16 BIFF_PCDSOURCE_SCENARIO = 0x0010; + +const sal_uInt16 BIFF_PC_NOSTRING = 0xFFFF; + +const sal_uInt16 BIFF_PCDFIELD_HASITEMS = 0x0001; +const sal_uInt16 BIFF_PCDFIELD_HASUNSHAREDITEMS = 0x0002; +const sal_uInt16 BIFF_PCDFIELD_CALCULATED = 0x0004; +const sal_uInt16 BIFF_PCDFIELD_HASPARENT = 0x0008; +const sal_uInt16 BIFF_PCDFIELD_RANGEGROUP = 0x0010; +const sal_uInt16 BIFF_PCDFIELD_ISNUMERIC = 0x0020; +const sal_uInt16 BIFF_PCDFIELD_HASSEMIMIXED = 0x0080; +const sal_uInt16 BIFF_PCDFIELD_HASMINMAX = 0x0100; +const sal_uInt16 BIFF_PCDFIELD_HASLONGINDEX = 0x0200; +const sal_uInt16 BIFF_PCDFIELD_HASNONDATE = 0x0400; +const sal_uInt16 BIFF_PCDFIELD_HASDATE = 0x0800; +const sal_uInt16 BIFF_PCDFIELD_SERVERFIELD = 0x2000; +const sal_uInt16 BIFF_PCDFIELD_NOUNIQUEITEMS = 0x4000; + +const sal_uInt16 BIFF_PCDFRANGEPR_AUTOSTART = 0x0001; +const sal_uInt16 BIFF_PCDFRANGEPR_AUTOEND = 0x0002; + +const sal_uInt16 BIFF_PCDEFINITION_SAVEDATA = 0x0001; +const sal_uInt16 BIFF_PCDEFINITION_INVALID = 0x0002; +const sal_uInt16 BIFF_PCDEFINITION_REFRESHONLOAD = 0x0004; +const sal_uInt16 BIFF_PCDEFINITION_OPTIMIZEMEMORY = 0x0008; +const sal_uInt16 BIFF_PCDEFINITION_BACKGROUNDQUERY = 0x0010; +const sal_uInt16 BIFF_PCDEFINITION_ENABLEREFRESH = 0x0020; + +// ---------------------------------------------------------------------------- + +/** Adjusts the weird date format read from binary streams. + + Dates before 1900-Mar-01 are stored including the non-existing leap day + 1900-02-29. Time values (without date) are stored as times of day + 1900-Jan-00. Nothing has to be done when the workbook is stored in 1904 + date mode (dates before 1904-Jan-01 will not occur in this case). + */ +void lclAdjustBinDateTime( DateTime& orDateTime ) +{ + if( (orDateTime.Year == 1900) && (orDateTime.Month <= 2) ) + { + OSL_ENSURE( (orDateTime.Month == 1) || ((orDateTime.Month == 2) && (orDateTime.Day > 0)), "lclAdjustBinDateTime - invalid date" ); + switch( orDateTime.Month ) + { + case 2: if( orDateTime.Day > 1 ) --orDateTime.Day; else { orDateTime.Day += 30; --orDateTime.Month; } break; + case 1: if( orDateTime.Day > 1 ) --orDateTime.Day; else { orDateTime.Day += 30; orDateTime.Month = 12; --orDateTime.Year; } break; + } + } +} + +} // namespace + +// ============================================================================ + +PivotCacheItem::PivotCacheItem() : + mnType( XML_m ) +{ +} + +void PivotCacheItem::readString( const AttributeList& rAttribs ) +{ + maValue <<= rAttribs.getXString( XML_v, OUString() ); + mnType = XML_s; +} + +void PivotCacheItem::readNumeric( const AttributeList& rAttribs ) +{ + maValue <<= rAttribs.getDouble( XML_v, 0.0 ); + mnType = XML_n; +} + +void PivotCacheItem::readDate( const AttributeList& rAttribs ) +{ + maValue <<= rAttribs.getDateTime( XML_v, DateTime() ); + mnType = XML_d; +} + +void PivotCacheItem::readBool( const AttributeList& rAttribs ) +{ + maValue <<= rAttribs.getBool( XML_v, false ); + mnType = XML_b; +} + +void PivotCacheItem::readError( const AttributeList& rAttribs, const UnitConverter& rUnitConverter ) +{ + maValue <<= static_cast< sal_Int32 >( rUnitConverter.calcBiffErrorCode( rAttribs.getXString( XML_v, OUString() ) ) ); + mnType = XML_e; +} + +void PivotCacheItem::readIndex( const AttributeList& rAttribs ) +{ + maValue <<= rAttribs.getInteger( XML_v, -1 ); + mnType = XML_x; +} + +void PivotCacheItem::readString( RecordInputStream& rStrm ) +{ + maValue <<= rStrm.readString(); + mnType = XML_s; +} + +void PivotCacheItem::readDouble( RecordInputStream& rStrm ) +{ + maValue <<= rStrm.readDouble(); + mnType = XML_n; +} + +void PivotCacheItem::readDate( RecordInputStream& rStrm ) +{ + DateTime aDateTime; + aDateTime.Year = rStrm.readuInt16(); + aDateTime.Month = rStrm.readuInt16(); + aDateTime.Day = rStrm.readuInt8(); + aDateTime.Hours = rStrm.readuInt8(); + aDateTime.Minutes = rStrm.readuInt8(); + aDateTime.Seconds = rStrm.readuInt8(); + lclAdjustBinDateTime( aDateTime ); + maValue <<= aDateTime; + mnType = XML_d; +} + +void PivotCacheItem::readBool( RecordInputStream& rStrm ) +{ + maValue <<= (rStrm.readuInt8() != 0); + mnType = XML_b; +} + +void PivotCacheItem::readError( RecordInputStream& rStrm ) +{ + maValue <<= static_cast< sal_Int32 >( rStrm.readuInt8() ); + mnType = XML_e; +} + +void PivotCacheItem::readIndex( RecordInputStream& rStrm ) +{ + maValue <<= rStrm.readInt32(); + mnType = XML_x; +} + +void PivotCacheItem::readString( BiffInputStream& rStrm, const WorkbookHelper& rHelper ) +{ + maValue <<= (rHelper.getBiff() == BIFF8) ? rStrm.readUniString() : rStrm.readByteStringUC( true, rHelper.getTextEncoding() ); + mnType = XML_s; +} + +void PivotCacheItem::readDouble( BiffInputStream& rStrm ) +{ + maValue <<= rStrm.readDouble(); + mnType = XML_n; +} + +void PivotCacheItem::readInteger( BiffInputStream& rStrm ) +{ + maValue <<= rStrm.readInt16(); + mnType = XML_i; // fake, used for BIFF only +} + +void PivotCacheItem::readDate( BiffInputStream& rStrm ) +{ + DateTime aDateTime; + aDateTime.Year = rStrm.readuInt16(); + aDateTime.Month = rStrm.readuInt16(); + aDateTime.Day = rStrm.readuInt8(); + aDateTime.Hours = rStrm.readuInt8(); + aDateTime.Minutes = rStrm.readuInt8(); + aDateTime.Seconds = rStrm.readuInt8(); + lclAdjustBinDateTime( aDateTime ); + maValue <<= aDateTime; + mnType = XML_d; +} + +void PivotCacheItem::readBool( BiffInputStream& rStrm ) +{ + maValue <<= (rStrm.readuInt8() != 0); + mnType = XML_b; +} + +void PivotCacheItem::readError( BiffInputStream& rStrm ) +{ + maValue <<= static_cast< sal_Int32 >( rStrm.readuInt8() ); + mnType = XML_e; +} + +OUString PivotCacheItem::getName() const +{ + switch( mnType ) + { + case XML_m: return OUString(); + case XML_s: return maValue.get< OUString >(); + case XML_n: return OUString::valueOf( maValue.get< double >() ); // !TODO + case XML_i: return OUString::valueOf( maValue.get< sal_Int32 >() ); + case XML_d: return OUString(); // !TODO + case XML_b: return OUString::valueOf( static_cast< sal_Bool >( maValue.get< bool >() ) ); // !TODO + case XML_e: return OUString(); // !TODO + } + OSL_ENSURE( false, "PivotCacheItem::getName - invalid data type" ); + return OUString(); +} + +// ---------------------------------------------------------------------------- + +PivotCacheItemList::PivotCacheItemList( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void PivotCacheItemList::importItem( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + PivotCacheItem& rItem = createItem(); + switch( nElement ) + { + case XLS_TOKEN( m ): break; + case XLS_TOKEN( s ): rItem.readString( rAttribs ); break; + case XLS_TOKEN( n ): rItem.readNumeric( rAttribs ); break; + case XLS_TOKEN( d ): rItem.readDate( rAttribs ); break; + case XLS_TOKEN( b ): rItem.readBool( rAttribs ); break; + case XLS_TOKEN( e ): rItem.readError( rAttribs, getUnitConverter() ); break; + default: OSL_ENSURE( false, "PivotCacheItemList::importItem - unknown element type" ); + } +} + +void PivotCacheItemList::importItem( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + if( nRecId == OOBIN_ID_PCITEM_ARRAY ) + { + importArray( rStrm ); + return; + } + + PivotCacheItem& rItem = createItem(); + switch( nRecId ) + { + case OOBIN_ID_PCITEM_MISSING: + case OOBIN_ID_PCITEMA_MISSING: break; + case OOBIN_ID_PCITEM_STRING: + case OOBIN_ID_PCITEMA_STRING: rItem.readString( rStrm ); break; + case OOBIN_ID_PCITEM_DOUBLE: + case OOBIN_ID_PCITEMA_DOUBLE: rItem.readDouble( rStrm ); break; + case OOBIN_ID_PCITEM_DATE: + case OOBIN_ID_PCITEMA_DATE: rItem.readDate( rStrm ); break; + case OOBIN_ID_PCITEM_BOOL: + case OOBIN_ID_PCITEMA_BOOL: rItem.readBool( rStrm ); break; + case OOBIN_ID_PCITEM_ERROR: + case OOBIN_ID_PCITEMA_ERROR: rItem.readError( rStrm ); break; + default: OSL_ENSURE( false, "PivotCacheItemList::importItem - unknown record type" ); + } +} + +void PivotCacheItemList::importItemList( BiffInputStream& rStrm, sal_uInt16 nCount ) +{ + bool bLoop = true; + for( sal_uInt16 nItemIdx = 0; bLoop && (nItemIdx < nCount); ++nItemIdx ) + { + bLoop = rStrm.startNextRecord(); + if( bLoop ) switch( rStrm.getRecId() ) + { + case BIFF_ID_PCITEM_MISSING: createItem(); break; + case BIFF_ID_PCITEM_STRING: createItem().readString( rStrm, *this ); break; + case BIFF_ID_PCITEM_DOUBLE: createItem().readDouble( rStrm ); break; + case BIFF_ID_PCITEM_INTEGER: createItem().readInteger( rStrm ); break; + case BIFF_ID_PCITEM_DATE: createItem().readDate( rStrm ); break; + case BIFF_ID_PCITEM_BOOL: createItem().readBool( rStrm ); break; + case BIFF_ID_PCITEM_ERROR: createItem().readError( rStrm ); break; + default: rStrm.rewindRecord(); bLoop = false; + } + } + OSL_ENSURE( bLoop, "PivotCacheItemList::importItemList - could not read all cache item records" ); +} + +const PivotCacheItem* PivotCacheItemList::getCacheItem( sal_Int32 nItemIdx ) const +{ + return ContainerHelper::getVectorElement( maItems, nItemIdx ); +} + +void PivotCacheItemList::getCacheItemNames( ::std::vector< OUString >& orItemNames ) const +{ + orItemNames.clear(); + orItemNames.reserve( maItems.size() ); + for( CacheItemVector::const_iterator aIt = maItems.begin(), aEnd = maItems.end(); aIt != aEnd; ++aIt ) + orItemNames.push_back( aIt->getName() ); +} + +// private -------------------------------------------------------------------- + +PivotCacheItem& PivotCacheItemList::createItem() +{ + maItems.resize( maItems.size() + 1 ); + return maItems.back(); +} + +void PivotCacheItemList::importArray( RecordInputStream& rStrm ) +{ + sal_uInt16 nType = rStrm.readuInt16(); + sal_Int32 nCount = rStrm.readInt32(); + for( sal_Int32 nIdx = 0; !rStrm.isEof() && (nIdx < nCount); ++nIdx ) + { + switch( nType ) + { + case OOBIN_PCITEM_ARRAY_DOUBLE: createItem().readDouble( rStrm ); break; + case OOBIN_PCITEM_ARRAY_STRING: createItem().readString( rStrm ); break; + case OOBIN_PCITEM_ARRAY_ERROR: createItem().readError( rStrm ); break; + case OOBIN_PCITEM_ARRAY_DATE: createItem().readDate( rStrm ); break; + default: + OSL_ENSURE( false, "PivotCacheItemList::importArray - unknown data type" ); + nIdx = nCount; + } + } +} + +// ============================================================================ + +PCFieldModel::PCFieldModel() : + mnNumFmtId( 0 ), + mnSqlType( 0 ), + mnHierarchy( 0 ), + mnLevel( 0 ), + mnMappingCount( 0 ), + mbDatabaseField( true ), + mbServerField( false ), + mbUniqueList( true ), + mbMemberPropField( false ) +{ +} + +// ---------------------------------------------------------------------------- + +PCSharedItemsModel::PCSharedItemsModel() : + mbHasSemiMixed( true ), + mbHasNonDate( true ), + mbHasDate( false ), + mbHasString( true ), + mbHasBlank( false ), + mbHasMixed( false ), + mbIsNumeric( false ), + mbIsInteger( false ), + mbHasLongText( false ), + mbHasLongIndexes( false ) +{ +} + +// ---------------------------------------------------------------------------- + +PCFieldGroupModel::PCFieldGroupModel() : + mfStartValue( 0.0 ), + mfEndValue( 0.0 ), + mfInterval( 1.0 ), + mnParentField( -1 ), + mnBaseField( -1 ), + mnGroupBy( XML_range ), + mbRangeGroup( false ), + mbDateGroup( false ), + mbAutoStart( true ), + mbAutoEnd( true ) +{ +} + +void PCFieldGroupModel::setBinGroupBy( sal_uInt8 nGroupBy ) +{ + static const sal_Int32 spnGroupBy[] = { XML_range, + XML_seconds, XML_minutes, XML_hours, XML_days, XML_months, XML_quarters, XML_years }; + mnGroupBy = STATIC_ARRAY_SELECT( spnGroupBy, nGroupBy, XML_range ); +} + +// ---------------------------------------------------------------------------- + +PivotCacheField::PivotCacheField( const WorkbookHelper& rHelper, bool bIsDatabaseField ) : + WorkbookHelper( rHelper ), + maSharedItems( rHelper ), + maGroupItems( rHelper ) +{ + maFieldModel.mbDatabaseField = bIsDatabaseField; +} + +void PivotCacheField::importCacheField( const AttributeList& rAttribs ) +{ + maFieldModel.maName = rAttribs.getXString( XML_name, OUString() ); + maFieldModel.maCaption = rAttribs.getXString( XML_caption, OUString() ); + maFieldModel.maPropertyName = rAttribs.getXString( XML_propertyName, OUString() ); + maFieldModel.maFormula = rAttribs.getXString( XML_formula, OUString() ); + maFieldModel.mnNumFmtId = rAttribs.getInteger( XML_numFmtId, 0 ); + maFieldModel.mnSqlType = rAttribs.getInteger( XML_sqlType, 0 ); + maFieldModel.mnHierarchy = rAttribs.getInteger( XML_hierarchy, 0 ); + maFieldModel.mnLevel = rAttribs.getInteger( XML_level, 0 ); + maFieldModel.mnMappingCount = rAttribs.getInteger( XML_mappingCount, 0 ); + maFieldModel.mbDatabaseField = rAttribs.getBool( XML_databaseField, true ); + maFieldModel.mbServerField = rAttribs.getBool( XML_serverField, false ); + maFieldModel.mbUniqueList = rAttribs.getBool( XML_uniqueList, true ); + maFieldModel.mbMemberPropField = rAttribs.getBool( XML_memberPropertyField, false ); +} + +void PivotCacheField::importSharedItems( const AttributeList& rAttribs ) +{ + OSL_ENSURE( maSharedItems.empty(), "PivotCacheField::importSharedItems - multiple shared items elements" ); + maSharedItemsModel.mbHasSemiMixed = rAttribs.getBool( XML_containsSemiMixedTypes, true ); + maSharedItemsModel.mbHasNonDate = rAttribs.getBool( XML_containsNonDate, true ); + maSharedItemsModel.mbHasDate = rAttribs.getBool( XML_containsDate, false ); + maSharedItemsModel.mbHasString = rAttribs.getBool( XML_containsString, true ); + maSharedItemsModel.mbHasBlank = rAttribs.getBool( XML_containsBlank, false ); + maSharedItemsModel.mbHasMixed = rAttribs.getBool( XML_containsMixedTypes, false ); + maSharedItemsModel.mbIsNumeric = rAttribs.getBool( XML_containsNumber, false ); + maSharedItemsModel.mbIsInteger = rAttribs.getBool( XML_containsInteger, false ); + maSharedItemsModel.mbHasLongText = rAttribs.getBool( XML_longText, false ); +} + +void PivotCacheField::importSharedItem( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + maSharedItems.importItem( nElement, rAttribs ); +} + +void PivotCacheField::importFieldGroup( const AttributeList& rAttribs ) +{ + maFieldGroupModel.mnParentField = rAttribs.getInteger( XML_par, -1 ); + maFieldGroupModel.mnBaseField = rAttribs.getInteger( XML_base, -1 ); +} + +void PivotCacheField::importRangePr( const AttributeList& rAttribs ) +{ + maFieldGroupModel.maStartDate = rAttribs.getDateTime( XML_startDate, DateTime() ); + maFieldGroupModel.maEndDate = rAttribs.getDateTime( XML_endDate, DateTime() ); + maFieldGroupModel.mfStartValue = rAttribs.getDouble( XML_startNum, 0.0 ); + maFieldGroupModel.mfEndValue = rAttribs.getDouble( XML_endNum, 0.0 ); + maFieldGroupModel.mfInterval = rAttribs.getDouble( XML_groupInterval, 1.0 ); + maFieldGroupModel.mnGroupBy = rAttribs.getToken( XML_groupBy, XML_range ); + maFieldGroupModel.mbRangeGroup = true; + maFieldGroupModel.mbDateGroup = maFieldGroupModel.mnGroupBy != XML_range; + maFieldGroupModel.mbAutoStart = rAttribs.getBool( XML_autoStart, true ); + maFieldGroupModel.mbAutoEnd = rAttribs.getBool( XML_autoEnd, true ); +} + +void PivotCacheField::importDiscretePrItem( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + OSL_ENSURE( nElement == XLS_TOKEN( x ), "PivotCacheField::importDiscretePrItem - unexpected element" ); + if( nElement == XLS_TOKEN( x ) ) + maDiscreteItems.push_back( rAttribs.getInteger( XML_v, -1 ) ); +} + +void PivotCacheField::importGroupItem( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + maGroupItems.importItem( nElement, rAttribs ); +} + +void PivotCacheField::importPCDField( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> nFlags >> maFieldModel.mnNumFmtId; + maFieldModel.mnSqlType = rStrm.readInt16(); + rStrm >> maFieldModel.mnHierarchy >> maFieldModel.mnLevel >> maFieldModel.mnMappingCount >> maFieldModel.maName; + if( getFlag( nFlags, OOBIN_PCDFIELD_HASCAPTION ) ) + rStrm >> maFieldModel.maCaption; + if( getFlag( nFlags, OOBIN_PCDFIELD_HASFORMULA ) ) + rStrm.skip( ::std::max< sal_Int32 >( rStrm.readInt32(), 0 ) ); + if( maFieldModel.mnMappingCount > 0 ) + rStrm.skip( ::std::max< sal_Int32 >( rStrm.readInt32(), 0 ) ); + if( getFlag( nFlags, OOBIN_PCDFIELD_HASPROPERTYNAME ) ) + rStrm >> maFieldModel.maPropertyName; + + maFieldModel.mbDatabaseField = getFlag( nFlags, OOBIN_PCDFIELD_DATABASEFIELD ); + maFieldModel.mbServerField = getFlag( nFlags, OOBIN_PCDFIELD_SERVERFIELD ); + maFieldModel.mbUniqueList = !getFlag( nFlags, OOBIN_PCDFIELD_NOUNIQUEITEMS ); + maFieldModel.mbMemberPropField = getFlag( nFlags, OOBIN_PCDFIELD_MEMBERPROPFIELD ); +} + +void PivotCacheField::importPCDFSharedItems( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> nFlags; + maSharedItemsModel.mbHasSemiMixed = getFlag( nFlags, OOBIN_PCDFSITEMS_HASSEMIMIXED ); + maSharedItemsModel.mbHasNonDate = getFlag( nFlags, OOBIN_PCDFSITEMS_HASNONDATE ); + maSharedItemsModel.mbHasDate = getFlag( nFlags, OOBIN_PCDFSITEMS_HASDATE ); + maSharedItemsModel.mbHasString = getFlag( nFlags, OOBIN_PCDFSITEMS_HASSTRING ); + maSharedItemsModel.mbHasBlank = getFlag( nFlags, OOBIN_PCDFSITEMS_HASBLANK ); + maSharedItemsModel.mbHasMixed = getFlag( nFlags, OOBIN_PCDFSITEMS_HASMIXED ); + maSharedItemsModel.mbIsNumeric = getFlag( nFlags, OOBIN_PCDFSITEMS_ISNUMERIC ); + maSharedItemsModel.mbIsInteger = getFlag( nFlags, OOBIN_PCDFSITEMS_ISINTEGER ); + maSharedItemsModel.mbHasLongText = getFlag( nFlags, OOBIN_PCDFSITEMS_HASLONGTEXT ); +} + +void PivotCacheField::importPCDFSharedItem( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + maSharedItems.importItem( nRecId, rStrm ); +} + +void PivotCacheField::importPCDFieldGroup( RecordInputStream& rStrm ) +{ + rStrm >> maFieldGroupModel.mnParentField >> maFieldGroupModel.mnBaseField; +} + +void PivotCacheField::importPCDFRangePr( RecordInputStream& rStrm ) +{ + sal_uInt8 nGroupBy, nFlags; + rStrm >> nGroupBy >> nFlags >> maFieldGroupModel.mfStartValue >> maFieldGroupModel.mfEndValue >> maFieldGroupModel.mfInterval; + + maFieldGroupModel.setBinGroupBy( nGroupBy ); + maFieldGroupModel.mbRangeGroup = true; + maFieldGroupModel.mbDateGroup = getFlag( nFlags, OOBIN_PCDFRANGEPR_DATEGROUP ); + maFieldGroupModel.mbAutoStart = getFlag( nFlags, OOBIN_PCDFRANGEPR_AUTOSTART ); + maFieldGroupModel.mbAutoEnd = getFlag( nFlags, OOBIN_PCDFRANGEPR_AUTOEND ); + + OSL_ENSURE( maFieldGroupModel.mbDateGroup == (maFieldGroupModel.mnGroupBy != XML_range), "PivotCacheField::importPCDFRangePr - wrong date flag" ); + if( maFieldGroupModel.mbDateGroup ) + { + maFieldGroupModel.maStartDate = getUnitConverter().calcDateTimeFromSerial( maFieldGroupModel.mfStartValue ); + maFieldGroupModel.maEndDate = getUnitConverter().calcDateTimeFromSerial( maFieldGroupModel.mfEndValue ); + } +} + +void PivotCacheField::importPCDFDiscretePrItem( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + OSL_ENSURE( nRecId == OOBIN_ID_PCITEM_INDEX, "PivotCacheField::importPCDFDiscretePrItem - unexpected record" ); + if( nRecId == OOBIN_ID_PCITEM_INDEX ) + maDiscreteItems.push_back( rStrm.readInt32() ); +} + +void PivotCacheField::importPCDFGroupItem( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + maGroupItems.importItem( nRecId, rStrm ); +} + +void PivotCacheField::importPCDField( BiffInputStream& rStrm ) +{ + sal_uInt16 nFlags, nGroupItems, nBaseItems, nSharedItems; + rStrm >> nFlags; + maFieldGroupModel.mnParentField = rStrm.readuInt16(); + maFieldGroupModel.mnBaseField = rStrm.readuInt16(); + rStrm.skip( 2 ); // number of unique items (either shared or group) + rStrm >> nGroupItems >> nBaseItems >> nSharedItems; + maFieldModel.maName = (getBiff() == BIFF8) ? rStrm.readUniString() : rStrm.readByteStringUC( true, getTextEncoding() ); + + maFieldModel.mbServerField = getFlag( nFlags, BIFF_PCDFIELD_SERVERFIELD ); + maFieldModel.mbUniqueList = !getFlag( nFlags, BIFF_PCDFIELD_NOUNIQUEITEMS ); + maSharedItemsModel.mbHasSemiMixed = getFlag( nFlags, BIFF_PCDFIELD_HASSEMIMIXED ); + maSharedItemsModel.mbHasNonDate = getFlag( nFlags, BIFF_PCDFIELD_HASNONDATE ); + maSharedItemsModel.mbHasDate = getFlag( nFlags, BIFF_PCDFIELD_HASDATE ); + maSharedItemsModel.mbIsNumeric = getFlag( nFlags, BIFF_PCDFIELD_ISNUMERIC ); + maSharedItemsModel.mbHasLongIndexes = getFlag( nFlags, BIFF_PCDFIELD_HASLONGINDEX ); + maFieldGroupModel.mbRangeGroup = getFlag( nFlags, BIFF_PCDFIELD_RANGEGROUP ); + + // in BIFF, presence of parent group field is denoted by a flag + if( !getFlag( nFlags, BIFF_PCDFIELD_HASPARENT ) ) + maFieldGroupModel.mnParentField = -1; + + // following PCDFSQLTYPE record contains SQL type + if( (rStrm.getNextRecId() == BIFF_ID_PCDFSQLTYPE) && rStrm.startNextRecord() ) + maFieldModel.mnSqlType = rStrm.readInt16(); + + // read group items, if any + if( nGroupItems > 0 ) + { + OSL_ENSURE( getFlag( nFlags, BIFF_PCDFIELD_HASITEMS ), "PivotCacheField::importPCDField - missing items flag" ); + maGroupItems.importItemList( rStrm, nGroupItems ); + + sal_uInt16 nNextRecId = rStrm.getNextRecId(); + bool bHasRangePr = nNextRecId == BIFF_ID_PCDFRANGEPR; + bool bHasDiscretePr = nNextRecId == BIFF_ID_PCDFDISCRETEPR; + + OSL_ENSURE( bHasRangePr || bHasDiscretePr, "PivotCacheField::importPCDField - missing group properties record" ); + OSL_ENSURE( bHasRangePr == maFieldGroupModel.mbRangeGroup, "PivotCacheField::importPCDField - invalid range grouping flag" ); + if( bHasRangePr && rStrm.startNextRecord() ) + importPCDFRangePr( rStrm ); + else if( bHasDiscretePr && rStrm.startNextRecord() ) + importPCDFDiscretePr( rStrm ); + } + + // read the shared items, if any + if( nSharedItems > 0 ) + { + OSL_ENSURE( getFlag( nFlags, BIFF_PCDFIELD_HASITEMS ), "PivotCacheField::importPCDField - missing items flag" ); + maSharedItems.importItemList( rStrm, nSharedItems ); + } +} + +void PivotCacheField::importPCDFRangePr( BiffInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> nFlags; + maFieldGroupModel.setBinGroupBy( extractValue< sal_uInt8 >( nFlags, 2, 3 ) ); + maFieldGroupModel.mbRangeGroup = true; + maFieldGroupModel.mbDateGroup = maFieldGroupModel.mnGroupBy != XML_range; + maFieldGroupModel.mbAutoStart = getFlag( nFlags, BIFF_PCDFRANGEPR_AUTOSTART ); + maFieldGroupModel.mbAutoEnd = getFlag( nFlags, BIFF_PCDFRANGEPR_AUTOEND ); + + /* Start, end, and interval are stored in 3 separate item records. Type of + the items is dependent on numeric/date mode. Numeric groups expect + three PCITEM_DOUBLE records, date groups expect two PCITEM_DATE records + and one PCITEM_INT record. */ + PivotCacheItemList aLimits( *this ); + aLimits.importItemList( rStrm, 3 ); + OSL_ENSURE( aLimits.size() == 3, "PivotCacheField::importPCDFRangePr - missing grouping records" ); + const PivotCacheItem* pStartValue = aLimits.getCacheItem( 0 ); + const PivotCacheItem* pEndValue = aLimits.getCacheItem( 1 ); + const PivotCacheItem* pInterval = aLimits.getCacheItem( 2 ); + if( pStartValue && pEndValue && pInterval ) + { + if( maFieldGroupModel.mbDateGroup ) + { + bool bHasTypes = (pStartValue->getType() == XML_d) && (pEndValue->getType() == XML_d) && (pInterval->getType() == XML_i); + OSL_ENSURE( bHasTypes, "PivotCacheField::importPCDFRangePr - wrong data types in grouping items" ); + if( bHasTypes ) + { + maFieldGroupModel.maStartDate = pStartValue->getValue().get< DateTime >(); + maFieldGroupModel.maEndDate = pEndValue->getValue().get< DateTime >(); + maFieldGroupModel.mfInterval = pInterval->getValue().get< sal_Int16 >(); + } + } + else + { + bool bHasTypes = (pStartValue->getType() == XML_n) && (pEndValue->getType() == XML_n) && (pInterval->getType() == XML_n); + OSL_ENSURE( bHasTypes, "PivotCacheField::importPCDFRangePr - wrong data types in grouping items" ); + if( bHasTypes ) + { + maFieldGroupModel.mfStartValue = pStartValue->getValue().get< double >(); + maFieldGroupModel.mfEndValue = pEndValue->getValue().get< double >(); + maFieldGroupModel.mfInterval = pInterval->getValue().get< double >(); + } + } + } +} + +void PivotCacheField::importPCDFDiscretePr( BiffInputStream& rStrm ) +{ + sal_Int32 nCount = static_cast< sal_Int32 >( rStrm.getLength() / 2 ); + for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex ) + maDiscreteItems.push_back( rStrm.readuInt16() ); +} + +const PivotCacheItem* PivotCacheField::getCacheItem( sal_Int32 nItemIdx ) const +{ + if( hasGroupItems() ) + return maGroupItems.getCacheItem( nItemIdx ); + if( hasSharedItems() ) + return maSharedItems.getCacheItem( nItemIdx ); + return 0; +} + +void PivotCacheField::getCacheItemNames( ::std::vector< OUString >& orItemNames ) const +{ + if( hasGroupItems() ) + maGroupItems.getCacheItemNames( orItemNames ); + else if( hasSharedItems() ) + maSharedItems.getCacheItemNames( orItemNames ); +} + +void PivotCacheField::convertNumericGrouping( const Reference< XDataPilotField >& rxDPField ) const +{ + OSL_ENSURE( hasGroupItems() && hasNumericGrouping(), "PivotCacheField::convertNumericGrouping - not a numeric group field" ); + PropertySet aPropSet( rxDPField ); + if( hasGroupItems() && hasNumericGrouping() && aPropSet.is() ) + { + DataPilotFieldGroupInfo aGroupInfo; + aGroupInfo.HasAutoStart = maFieldGroupModel.mbAutoStart; + aGroupInfo.HasAutoEnd = maFieldGroupModel.mbAutoEnd; + aGroupInfo.HasDateValues = sal_False; + aGroupInfo.Start = maFieldGroupModel.mfStartValue; + aGroupInfo.End = maFieldGroupModel.mfEndValue; + aGroupInfo.Step = maFieldGroupModel.mfInterval; + aGroupInfo.GroupBy = 0; + aPropSet.setProperty( PROP_GroupInfo, aGroupInfo ); + } +} + +OUString PivotCacheField::createDateGroupField( const Reference< XDataPilotField >& rxBaseDPField ) const +{ + OSL_ENSURE( hasGroupItems() && hasDateGrouping(), "PivotCacheField::createDateGroupField - not a numeric group field" ); + Reference< XDataPilotField > xDPGroupField; + PropertySet aPropSet( rxBaseDPField ); + if( hasGroupItems() && hasDateGrouping() && aPropSet.is() ) + { + bool bDayRanges = (maFieldGroupModel.mnGroupBy == XML_days) && (maFieldGroupModel.mfInterval >= 2.0); + + DataPilotFieldGroupInfo aGroupInfo; + aGroupInfo.HasAutoStart = maFieldGroupModel.mbAutoStart; + aGroupInfo.HasAutoEnd = maFieldGroupModel.mbAutoEnd; + aGroupInfo.HasDateValues = sal_True; + aGroupInfo.Start = getUnitConverter().calcSerialFromDateTime( maFieldGroupModel.maStartDate ); + aGroupInfo.End = getUnitConverter().calcSerialFromDateTime( maFieldGroupModel.maEndDate ); + aGroupInfo.Step = bDayRanges ? maFieldGroupModel.mfInterval : 0.0; + + using namespace ::com::sun::star::sheet::DataPilotFieldGroupBy; + switch( maFieldGroupModel.mnGroupBy ) + { + case XML_years: aGroupInfo.GroupBy = YEARS; break; + case XML_quarters: aGroupInfo.GroupBy = QUARTERS; break; + case XML_months: aGroupInfo.GroupBy = MONTHS; break; + case XML_days: aGroupInfo.GroupBy = DAYS; break; + case XML_hours: aGroupInfo.GroupBy = HOURS; break; + case XML_minutes: aGroupInfo.GroupBy = MINUTES; break; + case XML_seconds: aGroupInfo.GroupBy = SECONDS; break; + default: OSL_ENSURE( false, "PivotCacheField::convertRangeGrouping - unknown date/time interval" ); + } + + try + { + Reference< XDataPilotFieldGrouping > xDPGrouping( rxBaseDPField, UNO_QUERY_THROW ); + xDPGroupField = xDPGrouping->createDateGroup( aGroupInfo ); + } + catch( Exception& ) + { + } + } + + Reference< XNamed > xFieldName( xDPGroupField, UNO_QUERY ); + return xFieldName.is() ? xFieldName->getName() : OUString(); +} + +OUString PivotCacheField::createParentGroupField( const Reference< XDataPilotField >& rxBaseDPField, PivotCacheGroupItemVector& orItemNames ) const +{ + OSL_ENSURE( hasGroupItems() && !maDiscreteItems.empty(), "PivotCacheField::createParentGroupField - not a group field" ); + OSL_ENSURE( maDiscreteItems.size() == orItemNames.size(), "PivotCacheField::createParentGroupField - number of item names does not match grouping info" ); + Reference< XDataPilotFieldGrouping > xDPGrouping( rxBaseDPField, UNO_QUERY ); + if( !xDPGrouping.is() ) return OUString(); + + // map the group item indexes from maGroupItems to all item indexes from maDiscreteItems + typedef ::std::vector< sal_Int32 > GroupItemList; + typedef ::std::vector< GroupItemList > GroupItemMap; + GroupItemMap aItemMap( maGroupItems.size() ); + for( IndexVector::const_iterator aBeg = maDiscreteItems.begin(), aIt = aBeg, aEnd = maDiscreteItems.end(); aIt != aEnd; ++aIt ) + if( GroupItemList* pItems = ContainerHelper::getVectorElement( aItemMap, *aIt ) ) + pItems->push_back( static_cast< sal_Int32 >( aIt - aBeg ) ); + + // process all groups + Reference< XDataPilotField > xDPGroupField; + for( GroupItemMap::iterator aBeg = aItemMap.begin(), aIt = aBeg, aEnd = aItemMap.end(); aIt != aEnd; ++aIt ) + { + OSL_ENSURE( !aIt->empty(), "PivotCacheField::createParentGroupField - item/group should not be empty" ); + // if the item count is greater than 1, the item is a group of items + if( aIt->size() > 1 ) + { + /* Insert the names of the items that are part of this group. Calc + expects the names of the members of the field whose members are + grouped (which may be the names of groups too). Excel provides + the names of the base field items instead (no group names + involved). Therefore, the passed collection of current item + names as they are already grouped is used here to resolve the + item names. */ + ::std::vector< OUString > aMembers; + for( GroupItemList::iterator aBeg2 = aIt->begin(), aIt2 = aBeg2, aEnd2 = aIt->end(); aIt2 != aEnd2; ++aIt2 ) + if( const PivotCacheGroupItem* pName = ContainerHelper::getVectorElement( orItemNames, *aIt2 ) ) + if( ::std::find( aMembers.begin(), aMembers.end(), pName->maGroupName ) == aMembers.end() ) + aMembers.push_back( pName->maGroupName ); + + /* Check again, that this is not just a group that is not grouped + further with other items. */ + if( aMembers.size() > 1 ) try + { + // only the first call of createNameGroup() returns the new field + Reference< XDataPilotField > xDPNewField = xDPGrouping->createNameGroup( ContainerHelper::vectorToSequence( aMembers ) ); + OSL_ENSURE( xDPGroupField.is() != xDPNewField.is(), "PivotCacheField::createParentGroupField - missing group field" ); + if( !xDPGroupField.is() ) + xDPGroupField = xDPNewField; + + // get current grouping info + DataPilotFieldGroupInfo aGroupInfo; + PropertySet aPropSet( xDPGroupField ); + aPropSet.getProperty( aGroupInfo, PROP_GroupInfo ); + + /* Find the group object and the auto-generated group name. + The returned field contains all groups derived from the + previous field if that is grouped too. To find the correct + group, the first item used to create the group is serached. + Calc provides the original item names of the base field + when the group is querried for its members. Its does not + provide the names of members that are already groups in the + field used to create the new groups. (Is this a bug?) + Therefore, a name from the passed list of original item + names is used to find the correct group. */ + OUString aFirstItem; + if( const PivotCacheGroupItem* pName = ContainerHelper::getVectorElement( orItemNames, aIt->front() ) ) + aFirstItem = pName->maOrigName; + Reference< XNamed > xGroupName; + OUString aAutoName; + Reference< XIndexAccess > xGroupsIA( aGroupInfo.Groups, UNO_QUERY_THROW ); + for( sal_Int32 nIdx = 0, nCount = xGroupsIA->getCount(); (nIdx < nCount) && (aAutoName.getLength() == 0); ++nIdx ) try + { + Reference< XNameAccess > xItemsNA( xGroupsIA->getByIndex( nIdx ), UNO_QUERY_THROW ); + if( xItemsNA->hasByName( aFirstItem ) ) + { + xGroupName.set( xGroupsIA->getByIndex( nIdx ), UNO_QUERY_THROW ); + aAutoName = xGroupName->getName(); + } + } + catch( Exception& ) + { + } + OSL_ENSURE( aAutoName.getLength() > 0, "PivotCacheField::createParentGroupField - cannot find auto-generated group name" ); + + // get the real group name from the list of group items + OUString aGroupName; + if( const PivotCacheItem* pGroupItem = maGroupItems.getCacheItem( static_cast< sal_Int32 >( aIt - aBeg ) ) ) + aGroupName = pGroupItem->getName(); + OSL_ENSURE( aGroupName.getLength() > 0, "PivotCacheField::createParentGroupField - cannot find group name" ); + if( aGroupName.getLength() == 0 ) + aGroupName = aAutoName; + + if( xGroupName.is() && (aGroupName.getLength() > 0) ) + { + // replace the auto-generated group name with the real name + if( aAutoName != aGroupName ) + { + xGroupName->setName( aGroupName ); + aPropSet.setProperty( PROP_GroupInfo, aGroupInfo ); + } + // replace original item names in passed vector with group name + for( GroupItemList::iterator aIt2 = aIt->begin(), aEnd2 = aIt->end(); aIt2 != aEnd2; ++aIt2 ) + if( PivotCacheGroupItem* pName = ContainerHelper::getVectorElement( orItemNames, *aIt2 ) ) + pName->maGroupName = aGroupName; + } + } + catch( Exception& ) + { + } + } + } + + Reference< XNamed > xFieldName( xDPGroupField, UNO_QUERY ); + return xFieldName.is() ? xFieldName->getName() : OUString(); +} + +void PivotCacheField::writeSourceHeaderCell( WorksheetHelper& rSheetHelper, sal_Int32 nCol, sal_Int32 nRow ) const +{ + rSheetHelper.setStringCell( rSheetHelper.getCell( CellAddress( rSheetHelper.getSheetIndex(), nCol, nRow ) ), maFieldModel.maName ); +} + +void PivotCacheField::writeSourceDataCell( WorksheetHelper& rSheetHelper, sal_Int32 nCol, sal_Int32 nRow, const PivotCacheItem& rItem ) const +{ + bool bHasIndex = rItem.getType() == XML_x; + OSL_ENSURE( bHasIndex != maSharedItems.empty(), "PivotCacheField::writeSourceDataCell - shared items missing or not expected" ); + if( bHasIndex ) + writeSharedItemToSourceDataCell( rSheetHelper, nCol, nRow, rItem.getValue().get< sal_Int32 >() ); + else + writeItemToSourceDataCell( rSheetHelper, nCol, nRow, rItem ); +} + +void PivotCacheField::importPCRecordItem( RecordInputStream& rStrm, WorksheetHelper& rSheetHelper, sal_Int32 nCol, sal_Int32 nRow ) const +{ + if( hasSharedItems() ) + { + writeSharedItemToSourceDataCell( rSheetHelper, nCol, nRow, rStrm.readInt32() ); + } + else + { + PivotCacheItem aItem; + if( maSharedItemsModel.mbIsNumeric ) + aItem.readDouble( rStrm ); + else if( maSharedItemsModel.mbHasDate && !maSharedItemsModel.mbHasString ) + aItem.readDate( rStrm ); + else + aItem.readString( rStrm ); + writeItemToSourceDataCell( rSheetHelper, nCol, nRow, aItem ); + } +} + +void PivotCacheField::importPCItemIndex( BiffInputStream& rStrm, WorksheetHelper& rSheetHelper, sal_Int32 nCol, sal_Int32 nRow ) const +{ + OSL_ENSURE( hasSharedItems(), "PivotCacheField::importPCItemIndex - invalid call, no shared items found" ); + sal_Int32 nIndex = maSharedItemsModel.mbHasLongIndexes ? rStrm.readuInt16() : rStrm.readuInt8(); + writeSharedItemToSourceDataCell( rSheetHelper, nCol, nRow, nIndex ); +} + +// private -------------------------------------------------------------------- + +void PivotCacheField::writeItemToSourceDataCell( WorksheetHelper& rSheetHelper, + sal_Int32 nCol, sal_Int32 nRow, const PivotCacheItem& rItem ) const +{ + if( rItem.getType() != XML_m ) + { + Reference< XCell > xCell = rSheetHelper.getCell( CellAddress( rSheetHelper.getSheetIndex(), nCol, nRow ) ); + if( xCell.is() ) switch( rItem.getType() ) + { + case XML_s: rSheetHelper.setStringCell( xCell, rItem.getValue().get< OUString >() ); break; + case XML_n: xCell->setValue( rItem.getValue().get< double >() ); break; + case XML_i: xCell->setValue( rItem.getValue().get< sal_Int16 >() ); break; + case XML_d: rSheetHelper.setDateTimeCell( xCell, rItem.getValue().get< DateTime >() ); break; + case XML_b: rSheetHelper.setBooleanCell( xCell, rItem.getValue().get< bool >() ); break; + case XML_e: rSheetHelper.setErrorCell( xCell, static_cast< sal_uInt8 >( rItem.getValue().get< sal_Int32 >() ) ); break; + default: OSL_ENSURE( false, "PivotCacheField::writeItemToSourceDataCell - unexpected item data type" ); + } + } +} + +void PivotCacheField::writeSharedItemToSourceDataCell( + WorksheetHelper& rSheetHelper, sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nItemIdx ) const +{ + if( const PivotCacheItem* pCacheItem = maSharedItems.getCacheItem( nItemIdx ) ) + writeItemToSourceDataCell( rSheetHelper, nCol, nRow, *pCacheItem ); +} + +// ============================================================================ + +PCDefinitionModel::PCDefinitionModel() : + mfRefreshedDate( 0.0 ), + mnRecords( 0 ), + mnMissItemsLimit( 0 ), + mnDatabaseFields( 0 ), + mbInvalid( false ), + mbSaveData( true ), + mbRefreshOnLoad( false ), + mbOptimizeMemory( false ), + mbEnableRefresh( true ), + mbBackgroundQuery( false ), + mbUpgradeOnRefresh( false ), + mbTupleCache( false ), + mbSupportSubquery( false ), + mbSupportDrill( false ) +{ +} + +// ---------------------------------------------------------------------------- + +PCSourceModel::PCSourceModel() : + mnSourceType( XML_TOKEN_INVALID ), + mnConnectionId( 0 ) +{ +} + +// ---------------------------------------------------------------------------- + +PCWorksheetSourceModel::PCWorksheetSourceModel() +{ + maRange.StartColumn = maRange.StartRow = maRange.EndColumn = maRange.EndRow = -1; +} + +// ---------------------------------------------------------------------------- + +PivotCache::PivotCache( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mbValidSource( false ), + mbDummySheet( false ) +{ +} + +void PivotCache::importPivotCacheDefinition( const AttributeList& rAttribs ) +{ + maDefModel.maRelId = rAttribs.getString( R_TOKEN( id ), OUString() ); + maDefModel.maRefreshedBy = rAttribs.getXString( XML_refreshedBy, OUString() ); + maDefModel.mfRefreshedDate = rAttribs.getDouble( XML_refreshedDate, 0.0 ); + maDefModel.mnRecords = rAttribs.getInteger( XML_recordCount, 0 ); + maDefModel.mnMissItemsLimit = rAttribs.getInteger( XML_missingItemsLimit, 0 ); + maDefModel.mbInvalid = rAttribs.getBool( XML_invalid, false ); + maDefModel.mbSaveData = rAttribs.getBool( XML_saveData, true ); + maDefModel.mbRefreshOnLoad = rAttribs.getBool( XML_refreshOnLoad, false ); + maDefModel.mbOptimizeMemory = rAttribs.getBool( XML_optimizeMemory, false ); + maDefModel.mbEnableRefresh = rAttribs.getBool( XML_enableRefresh, true ); + maDefModel.mbBackgroundQuery = rAttribs.getBool( XML_backgroundQuery, false ); + maDefModel.mbUpgradeOnRefresh = rAttribs.getBool( XML_upgradeOnRefresh, false ); + maDefModel.mbTupleCache = rAttribs.getBool( XML_tupleCache, false ); + maDefModel.mbSupportSubquery = rAttribs.getBool( XML_supportSubquery, false ); + maDefModel.mbSupportDrill = rAttribs.getBool( XML_supportAdvancedDrill, false ); +} + +void PivotCache::importCacheSource( const AttributeList& rAttribs ) +{ + maSourceModel.mnSourceType = rAttribs.getToken( XML_type, XML_TOKEN_INVALID ); + maSourceModel.mnConnectionId = rAttribs.getInteger( XML_connectionId, 0 ); +} + +void PivotCache::importWorksheetSource( const AttributeList& rAttribs, const Relations& rRelations ) +{ + maSheetSrcModel.maRelId = rAttribs.getString( R_TOKEN( id ), OUString() ); + maSheetSrcModel.maSheet = rAttribs.getXString( XML_sheet, OUString() ); + maSheetSrcModel.maDefName = rAttribs.getXString( XML_name, OUString() ); + + // resolve URL of external document + maTargetUrl = rRelations.getExternalTargetFromRelId( maSheetSrcModel.maRelId ); + // store range address unchecked with sheet index 0, will be resolved/checked later + getAddressConverter().convertToCellRangeUnchecked( maSheetSrcModel.maRange, rAttribs.getString( XML_ref, OUString() ), 0 ); +} + +void PivotCache::importPCDefinition( RecordInputStream& rStrm ) +{ + sal_uInt8 nFlags1, nFlags2; + rStrm.skip( 3 ); // create/refresh version id's + rStrm >> nFlags1 >> maDefModel.mnMissItemsLimit >> maDefModel.mfRefreshedDate >> nFlags2 >> maDefModel.mnRecords; + if( getFlag( nFlags2, OOBIN_PCDEFINITION_HASUSERNAME ) ) + rStrm >> maDefModel.maRefreshedBy; + if( getFlag( nFlags2, OOBIN_PCDEFINITION_HASRELID ) ) + rStrm >> maDefModel.maRelId; + + maDefModel.mbInvalid = getFlag( nFlags1, OOBIN_PCDEFINITION_INVALID ); + maDefModel.mbSaveData = getFlag( nFlags1, OOBIN_PCDEFINITION_SAVEDATA ); + maDefModel.mbRefreshOnLoad = getFlag( nFlags1, OOBIN_PCDEFINITION_REFRESHONLOAD ); + maDefModel.mbOptimizeMemory = getFlag( nFlags1, OOBIN_PCDEFINITION_OPTIMIZEMEMORY ); + maDefModel.mbEnableRefresh = getFlag( nFlags1, OOBIN_PCDEFINITION_ENABLEREFRESH ); + maDefModel.mbBackgroundQuery = getFlag( nFlags1, OOBIN_PCDEFINITION_BACKGROUNDQUERY ); + maDefModel.mbUpgradeOnRefresh = getFlag( nFlags1, OOBIN_PCDEFINITION_UPGRADEONREFR ); + maDefModel.mbTupleCache = getFlag( nFlags1, OOBIN_PCDEFINITION_TUPELCACHE ); + maDefModel.mbSupportSubquery = getFlag( nFlags2, OOBIN_PCDEFINITION_SUPPORTSUBQUERY ); + maDefModel.mbSupportDrill = getFlag( nFlags2, OOBIN_PCDEFINITION_SUPPORTDRILL ); +} + +void PivotCache::importPCDSource( RecordInputStream& rStrm ) +{ + sal_Int32 nSourceType; + rStrm >> nSourceType >> maSourceModel.mnConnectionId; + static const sal_Int32 spnSourceTypes[] = { XML_worksheet, XML_external, XML_consolidation, XML_scenario }; + maSourceModel.mnSourceType = STATIC_ARRAY_SELECT( spnSourceTypes, nSourceType, XML_TOKEN_INVALID ); +} + +void PivotCache::importPCDSheetSource( RecordInputStream& rStrm, const Relations& rRelations ) +{ + sal_uInt8 nIsDefName, nIsBuiltinName, nFlags; + rStrm >> nIsDefName >> nIsBuiltinName >> nFlags; + if( getFlag( nFlags, OOBIN_PCDWBSOURCE_HASSHEET ) ) + rStrm >> maSheetSrcModel.maSheet; + if( getFlag( nFlags, OOBIN_PCDWBSOURCE_HASRELID ) ) + rStrm >> maSheetSrcModel.maRelId; + + // read cell range or defined name + if( nIsDefName == 0 ) + { + BinRange aBinRange; + rStrm >> aBinRange; + // store range address unchecked with sheet index 0, will be resolved/checked later + getAddressConverter().convertToCellRangeUnchecked( maSheetSrcModel.maRange, aBinRange, 0 ); + } + else + { + rStrm >> maSheetSrcModel.maDefName; + if( nIsBuiltinName != 0 ) + maSheetSrcModel.maDefName = CREATE_OUSTRING( "_xlnm." ) + maSheetSrcModel.maDefName; + } + + // resolve URL of external document + maTargetUrl = rRelations.getExternalTargetFromRelId( maSheetSrcModel.maRelId ); +} + +void PivotCache::importPCDSource( BiffInputStream& rStrm ) +{ + switch( rStrm.readuInt16() ) + { + case BIFF_PCDSOURCE_WORKSHEET: + { + maSourceModel.mnSourceType = XML_worksheet; + sal_uInt16 nNextRecId = rStrm.getNextRecId(); + switch( nNextRecId ) + { + case BIFF_ID_DCONREF: if( rStrm.startNextRecord() ) importDConRef( rStrm ); break; + case BIFF_ID_DCONNAME: if( rStrm.startNextRecord() ) importDConName( rStrm ); break; + case BIFF_ID_DCONBINAME: if( rStrm.startNextRecord() ) importDConBIName( rStrm ); break; + } + } + break; + case BIFF_PCDSOURCE_EXTERNAL: + maSourceModel.mnSourceType = XML_external; + break; + case BIFF_PCDSOURCE_CONSOLIDATION: + maSourceModel.mnSourceType = XML_consolidation; + break; + case BIFF_PCDSOURCE_SCENARIO: + maSourceModel.mnSourceType = XML_scenario; + break; + default: + maSourceModel.mnSourceType = XML_TOKEN_INVALID; + } +} + +void PivotCache::importPCDefinition( BiffInputStream& rStrm ) +{ + sal_uInt16 nFlags, nUserNameLen; + rStrm >> maDefModel.mnRecords; + rStrm.skip( 2 ); // repeated cache ID + rStrm >> nFlags; + rStrm.skip( 2 ); // unused + rStrm >> maDefModel.mnDatabaseFields; + rStrm.skip( 6 ); // total field count, report record count, (repeated) cache type + rStrm >> nUserNameLen; + if( nUserNameLen != BIFF_PC_NOSTRING ) + maDefModel.maRefreshedBy = (getBiff() == BIFF8) ? + rStrm.readUniString( nUserNameLen ) : + rStrm.readCharArrayUC( nUserNameLen, getTextEncoding() ); + + maDefModel.mbInvalid = getFlag( nFlags, BIFF_PCDEFINITION_INVALID ); + maDefModel.mbSaveData = getFlag( nFlags, BIFF_PCDEFINITION_SAVEDATA ); + maDefModel.mbRefreshOnLoad = getFlag( nFlags, BIFF_PCDEFINITION_REFRESHONLOAD ); + maDefModel.mbOptimizeMemory = getFlag( nFlags, BIFF_PCDEFINITION_OPTIMIZEMEMORY ); + maDefModel.mbEnableRefresh = getFlag( nFlags, BIFF_PCDEFINITION_ENABLEREFRESH ); + maDefModel.mbBackgroundQuery = getFlag( nFlags, BIFF_PCDEFINITION_BACKGROUNDQUERY ); + + if( (rStrm.getNextRecId() == BIFF_ID_PCDEFINITION2) && rStrm.startNextRecord() ) + rStrm >> maDefModel.mfRefreshedDate; +} + +PivotCacheField& PivotCache::createCacheField( bool bInitDatabaseField ) +{ + bool bIsDatabaseField = !bInitDatabaseField || (maFields.size() < maDefModel.mnDatabaseFields); + PivotCacheFieldVector::value_type xCacheField( new PivotCacheField( *this, bIsDatabaseField ) ); + maFields.push_back( xCacheField ); + return *xCacheField; +} + +void PivotCache::finalizeImport() +{ + // collect all fields that are based on source data (needed to finalize source data below) + OSL_ENSURE( !maFields.empty(), "PivotCache::finalizeImport - no pivot cache fields found" ); + for( PivotCacheFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt ) + { + if( (*aIt)->isDatabaseField() ) + { + OSL_ENSURE( (aIt == maFields.begin()) || (*(aIt - 1))->isDatabaseField(), + "PivotCache::finalizeImport - database field follows a calculated field" ); + maDatabaseIndexes.push_back( static_cast< sal_Int32 >( maDatabaseFields.size() ) ); + maDatabaseFields.push_back( *aIt ); + } + else + { + maDatabaseIndexes.push_back( -1 ); + } + } + OSL_ENSURE( !maDatabaseFields.empty(), "PivotCache::finalizeImport - no pivot cache source fields found" ); + + // finalize source data depending on source type + switch( maSourceModel.mnSourceType ) + { + case XML_worksheet: + { + // decide whether an external document is used + bool bInternal = (maTargetUrl.getLength() == 0) && (maSheetSrcModel.maRelId.getLength() == 0); + bool bExternal = maTargetUrl.getLength() > 0; // relation ID may be empty, e.g. BIFF import + OSL_ENSURE( bInternal || bExternal, "PivotCache::finalizeImport - invalid external document URL" ); + if( bInternal ) + finalizeInternalSheetSource(); + else if( bExternal ) + finalizeExternalSheetSource(); + } + break; + + // currently, we only support worksheet data sources + case XML_external: + break; + case XML_consolidation: + break; + case XML_scenario: + break; + } +} + +sal_Int32 PivotCache::getCacheFieldCount() const +{ + return static_cast< sal_Int32 >( maFields.size() ); +} + +const PivotCacheField* PivotCache::getCacheField( sal_Int32 nFieldIdx ) const +{ + return maFields.get( nFieldIdx ).get(); +} + +sal_Int32 PivotCache::getCacheDatabaseIndex( sal_Int32 nFieldIdx ) const +{ + return ContainerHelper::getVectorElement< sal_Int32 >( maDatabaseIndexes, nFieldIdx, -1 ); +} + +void PivotCache::writeSourceHeaderCells( WorksheetHelper& rSheetHelper ) const +{ + OSL_ENSURE( static_cast< size_t >( maSheetSrcModel.maRange.EndColumn - maSheetSrcModel.maRange.StartColumn + 1 ) == maDatabaseFields.size(), + "PivotCache::writeSourceHeaderCells - source cell range width does not match number of source fields" ); + sal_Int32 nCol = maSheetSrcModel.maRange.StartColumn; + sal_Int32 nMaxCol = getAddressConverter().getMaxApiAddress().Column; + sal_Int32 nRow = maSheetSrcModel.maRange.StartRow; + for( PivotCacheFieldVector::const_iterator aIt = maDatabaseFields.begin(), aEnd = maDatabaseFields.end(); (aIt != aEnd) && (nCol <= nMaxCol); ++aIt, ++nCol ) + (*aIt)->writeSourceHeaderCell( rSheetHelper, nCol, nRow ); +} + +void PivotCache::writeSourceDataCell( WorksheetHelper& rSheetHelper, sal_Int32 nCol, sal_Int32 nRow, const PivotCacheItem& rItem ) const +{ + OSL_ENSURE( (0 <= nCol) && (nCol <= maSheetSrcModel.maRange.EndColumn - maSheetSrcModel.maRange.StartColumn), "PivotCache::writeSourceDataCell - invalid column index" ); + OSL_ENSURE( (0 < nRow) && (nRow <= maSheetSrcModel.maRange.EndRow - maSheetSrcModel.maRange.StartRow), "PivotCache::writeSourceDataCell - invalid row index" ); + if( const PivotCacheField* pCacheField = maDatabaseFields.get( nCol ).get() ) + pCacheField->writeSourceDataCell( rSheetHelper, maSheetSrcModel.maRange.StartColumn + nCol, maSheetSrcModel.maRange.StartRow + nRow, rItem ); +} + +void PivotCache::importPCRecord( RecordInputStream& rStrm, WorksheetHelper& rSheetHelper, sal_Int32 nRow ) const +{ + OSL_ENSURE( (0 < nRow) && (nRow <= maSheetSrcModel.maRange.EndRow - maSheetSrcModel.maRange.StartRow), "PivotCache::importPCRecord - invalid row index" ); + sal_Int32 nCol = maSheetSrcModel.maRange.StartColumn; + sal_Int32 nMaxCol = getAddressConverter().getMaxApiAddress().Column; + nRow += maSheetSrcModel.maRange.StartRow; + for( PivotCacheFieldVector::const_iterator aIt = maDatabaseFields.begin(), aEnd = maDatabaseFields.end(); !rStrm.isEof() && (aIt != aEnd) && (nCol <= nMaxCol); ++aIt, ++nCol ) + (*aIt)->importPCRecordItem( rStrm, rSheetHelper, nCol, nRow ); +} + +void PivotCache::importPCItemIndexList( BiffInputStream& rStrm, WorksheetHelper& rSheetHelper, sal_Int32 nRow ) const +{ + OSL_ENSURE( (0 < nRow) && (nRow <= maSheetSrcModel.maRange.EndRow - maSheetSrcModel.maRange.StartRow), "PivotCache::importPCItemIndexList - invalid row index" ); + sal_Int32 nCol = maSheetSrcModel.maRange.StartColumn; + sal_Int32 nMaxCol = getAddressConverter().getMaxApiAddress().Column; + nRow += maSheetSrcModel.maRange.StartRow; + for( PivotCacheFieldVector::const_iterator aIt = maDatabaseFields.begin(), aEnd = maDatabaseFields.end(); !rStrm.isEof() && (aIt != aEnd) && (nCol <= nMaxCol); ++aIt, ++nCol ) + if( (*aIt)->hasSharedItems() ) + (*aIt)->importPCItemIndex( rStrm, rSheetHelper, nCol, nRow ); +} + +// private -------------------------------------------------------------------- + +void PivotCache::importDConRef( BiffInputStream& rStrm ) +{ + BinRange aBinRange; + aBinRange.read( rStrm, false ); // always 8-bit column indexes + // store range address unchecked with sheet index 0, will be resolved/checked later + getAddressConverter().convertToCellRangeUnchecked( maSheetSrcModel.maRange, aBinRange, 0 ); + + // the URL with (required) sheet name and optional URL of an external document + importDConUrl( rStrm ); + OSL_ENSURE( maSheetSrcModel.maSheet.getLength() > 0, "PivotCache::importDConRef - missing sheet name" ); +} + +void PivotCache::importDConName( BiffInputStream& rStrm ) +{ + maSheetSrcModel.maDefName = (getBiff() == BIFF8) ? rStrm.readUniString() : rStrm.readByteStringUC( false, getTextEncoding() ); + OSL_ENSURE( maSheetSrcModel.maDefName.getLength() > 0, "PivotCache::importDConName - missing defined name" ); + importDConUrl( rStrm ); +} + +void PivotCache::importDConBIName( BiffInputStream& rStrm ) +{ + sal_uInt8 nNameId = rStrm.readuInt8(); + rStrm.skip( 3 ); + maSheetSrcModel.maDefName = OUString( sal_Unicode( nNameId ) ); + importDConUrl( rStrm ); +} + +void PivotCache::importDConUrl( BiffInputStream& rStrm ) +{ + // the URL with sheet name and optional URL of an external document + OUString aEncodedUrl; + if( getBiff() == BIFF8 ) + { + // empty string does not contain a flags byte, cannot use simple readUniString() here... + sal_uInt16 nChars = rStrm.readuInt16(); + if( nChars > 0 ) + aEncodedUrl = rStrm.readUniString( nChars ); + } + else + { + aEncodedUrl = rStrm.readByteStringUC( false, getTextEncoding() ); + } + + if( aEncodedUrl.getLength() > 0 ) + { + OUString aClassName; + getAddressConverter().parseBiffTargetUrl( aClassName, maTargetUrl, maSheetSrcModel.maSheet, aEncodedUrl, true ); + } +} + +void PivotCache::finalizeInternalSheetSource() +{ + // resolve sheet name to sheet index + sal_Int16 nSheet = getWorksheets().getCalcSheetIndex( maSheetSrcModel.maSheet ); + + // if cache is based on a defined name or table, try to resolve to cell range + if( maSheetSrcModel.maDefName.getLength() > 0 ) + { + // local or global defined name + if( const DefinedName* pDefName = getDefinedNames().getByModelName( maSheetSrcModel.maDefName, nSheet ).get() ) + { + mbValidSource = pDefName->getAbsoluteRange( maSheetSrcModel.maRange ); + } + // table + else if( const Table* pTable = getTables().getTable( maSheetSrcModel.maDefName ).get() ) + { + // get original range from table, but exclude the totals row(s) + maSheetSrcModel.maRange = pTable->getOriginalRange(); + mbValidSource = (pTable->getHeight() - pTable->getTotalsRows()) > 1; + if( mbValidSource ) + maSheetSrcModel.maRange.EndRow -= pTable->getTotalsRows(); + } + } + // else try the cell range (if the sheet exists) + else if( nSheet >= 0 ) + { + // insert sheet index into the range, range address will be checked below + maSheetSrcModel.maRange.Sheet = nSheet; + mbValidSource = true; + } + // else sheet has been deleted, generate the source data from cache + else if( maSheetSrcModel.maSheet.getLength() > 0 ) + { + prepareSourceDataSheet(); + // return here to skip the source range check below + return; + } + + // check range location, do not allow ranges that overflow the sheet partly + mbValidSource = mbValidSource && + getAddressConverter().checkCellRange( maSheetSrcModel.maRange, false, true ) && + (maSheetSrcModel.maRange.StartRow < maSheetSrcModel.maRange.EndRow); +} + +void PivotCache::finalizeExternalSheetSource() +{ + /* If pivot cache is based on external sheet data, try to restore sheet + data from cache records. No support for external defined names or tables, + sheet name and path to cache records fragment (OOX only) are required. */ + bool bHasRelation = (getFilterType() == FILTER_BIFF) || (maDefModel.maRelId.getLength() > 0); + if( bHasRelation && (maSheetSrcModel.maDefName.getLength() == 0) && (maSheetSrcModel.maSheet.getLength() > 0) ) + prepareSourceDataSheet(); +} + +void PivotCache::prepareSourceDataSheet() +{ + // data will be inserted in top-left cell, sheet index is still set to 0 (will be set below) + maSheetSrcModel.maRange.EndColumn -= maSheetSrcModel.maRange.StartColumn; + maSheetSrcModel.maRange.StartColumn = 0; + maSheetSrcModel.maRange.EndRow -= maSheetSrcModel.maRange.StartRow; + maSheetSrcModel.maRange.StartRow = 0; + // check range location, do not allow ranges that overflow the sheet partly + if( getAddressConverter().checkCellRange( maSheetSrcModel.maRange, false, true ) ) + { + OUString aSheetName = CREATE_OUSTRING( "DPCache_" ) + maSheetSrcModel.maSheet; + maSheetSrcModel.maRange.Sheet = getWorksheets().insertEmptySheet( aSheetName, false ); + mbValidSource = mbDummySheet = maSheetSrcModel.maRange.Sheet >= 0; + } +} + +// ============================================================================ + +PivotCacheBuffer::PivotCacheBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void PivotCacheBuffer::registerPivotCacheFragment( sal_Int32 nCacheId, const OUString& rFragmentPath ) +{ + OSL_ENSURE( nCacheId >= 0, "PivotCacheBuffer::registerPivotCacheFragment - invalid pivot cache identifier" ); + OSL_ENSURE( maFragmentPaths.count( nCacheId ) == 0, "PivotCacheBuffer::registerPivotCacheFragment - fragment path exists already" ); + if( (nCacheId >= 0) && (rFragmentPath.getLength() > 0) ) + maFragmentPaths[ nCacheId ] = rFragmentPath; +} + +void PivotCacheBuffer::importPivotCacheRef( BiffInputStream& rStrm ) +{ + // read the PIVOTCACHE record that contains the stream ID + sal_Int32 nCacheId = rStrm.readuInt16(); + OSL_ENSURE( maFragmentPaths.count( nCacheId ) == 0, "PivotCacheBuffer::importPivotCacheRef - cache stream exists already" ); + OUStringBuffer aStrmName; + static const sal_Unicode spcHexChars[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; + for( sal_uInt8 nBit = 0; nBit < 16; nBit += 4 ) + aStrmName.insert( 0, spcHexChars[ extractValue< size_t >( nCacheId, nBit, 4 ) ] ); + aStrmName.insert( 0, (getBiff() == BIFF8) ? CREATE_OUSTRING( "_SX_DB_CUR/" ) : CREATE_OUSTRING( "_SX_DB/" ) ); + maFragmentPaths[ nCacheId ] = aStrmName.makeStringAndClear(); + + // try to read PCDSOURCE record (will read following data location records too) + sal_uInt16 nNextRecId = rStrm.getNextRecId(); + OSL_ENSURE( nNextRecId == BIFF_ID_PCDSOURCE, "PivotCacheBuffer::importPivotCacheRef - PCDSOURCE record expected" ); + if( (nNextRecId == BIFF_ID_PCDSOURCE) && rStrm.startNextRecord() ) + createPivotCache( nCacheId ).importPCDSource( rStrm ); +} + +PivotCache* PivotCacheBuffer::importPivotCacheFragment( sal_Int32 nCacheId ) +{ + switch( getFilterType() ) + { + /* BIFF filter: Pivot table provides 0-based index into list of pivot + cache source links (PIVOTCACHE/PCDSOURCE/... record blocks in + workbook stream). First, this index has to be resolved to the cache + identifier that is used to manage the cache stream names (the + maFragmentPaths member). The cache object itself exists already + before the first call for the cache source index (see + PivotCacheBuffer::importPivotCacheRef() above), because source data + link is part of workbook data, not of the cache stream. To detect + subsequent calls with an already initialized cache, the entry in + maFragmentPaths will be removed after reading the cache stream. */ + case FILTER_BIFF: + { + /* Resolve cache index to cache identifier and try to find pivot + cache. Cache must exist already for a valid cache index. */ + nCacheId = ContainerHelper::getVectorElement< sal_Int32 >( maCacheIds, nCacheId, -1 ); + PivotCache* pCache = maCaches.get( nCacheId ).get(); + if( !pCache ) + return 0; + + /* Try to find fragment path entry (stream name). If missing, the + stream has been read already, and the cache can be returned. */ + FragmentPathMap::iterator aIt = maFragmentPaths.find( nCacheId ); + if( aIt != maFragmentPaths.end() ) + { + /* Import the cache stream. This may create a dummy data sheet + for external sheet sources. */ + BiffPivotCacheFragment( *this, aIt->second, *pCache ).importFragment(); + // remove the fragment entry to mark that the cache is initialized + maFragmentPaths.erase( aIt ); + } + return pCache; + } + + /* OOX/OOBIN filter: On first call for the cache ID, the pivot cache + object is created and inserted into maCaches. Then, the cache + definition fragment is read and the cache is returned. On + subsequent calls, the created cache will be found in maCaches and + returned immediately. */ + case FILTER_OOX: + { + // try to find an imported pivot cache + if( PivotCache* pCache = maCaches.get( nCacheId ).get() ) + return pCache; + + // check if a fragment path exists for the passed cache identifier + FragmentPathMap::iterator aIt = maFragmentPaths.find( nCacheId ); + if( aIt == maFragmentPaths.end() ) + return 0; + + /* Import the cache fragment. This may create a dummy data sheet + for external sheet sources. */ + PivotCache& rCache = createPivotCache( nCacheId ); + importOoxFragment( new OoxPivotCacheDefinitionFragment( *this, aIt->second, rCache ) ); + return &rCache; + } + + case FILTER_UNKNOWN: + OSL_ENSURE( false, "PivotCacheBuffer::importPivotCacheFragment - unknown filter type" ); + } + return 0; +} + +PivotCache& PivotCacheBuffer::createPivotCache( sal_Int32 nCacheId ) +{ + maCacheIds.push_back( nCacheId ); + PivotCacheMap::mapped_type& rxCache = maCaches[ nCacheId ]; + rxCache.reset( new PivotCache( *this ) ); + return *rxCache; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/pivotcachefragment.cxx b/oox/source/xls/pivotcachefragment.cxx new file mode 100644 index 000000000000..440fcd1e0903 --- /dev/null +++ b/oox/source/xls/pivotcachefragment.cxx @@ -0,0 +1,465 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/pivotcachefragment.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/pivotcachebuffer.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxPivotCacheFieldContext::OoxPivotCacheFieldContext( OoxWorkbookFragmentBase& rFragment, PivotCacheField& rCacheField ) : + OoxWorkbookContextBase( rFragment ), + mrCacheField( rCacheField ) +{ +} + +ContextHandlerRef OoxPivotCacheFieldContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( cacheField ): + if( nElement == XLS_TOKEN( sharedItems ) ) { mrCacheField.importSharedItems( rAttribs ); return this; } + if( nElement == XLS_TOKEN( fieldGroup ) ) { mrCacheField.importFieldGroup( rAttribs ); return this; } + break; + + case XLS_TOKEN( fieldGroup ): + switch( nElement ) + { + case XLS_TOKEN( rangePr ): mrCacheField.importRangePr( rAttribs ); break; + case XLS_TOKEN( discretePr ): return this; + case XLS_TOKEN( groupItems ): return this; + } + break; + + case XLS_TOKEN( sharedItems ): mrCacheField.importSharedItem( nElement, rAttribs ); break; + case XLS_TOKEN( discretePr ): mrCacheField.importDiscretePrItem( nElement, rAttribs ); break; + case XLS_TOKEN( groupItems ): mrCacheField.importGroupItem( nElement, rAttribs ); break; + } + return 0; +} + +void OoxPivotCacheFieldContext::onStartElement( const AttributeList& rAttribs ) +{ + if( isRootElement() ) + mrCacheField.importCacheField( rAttribs ); +} + +ContextHandlerRef OoxPivotCacheFieldContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_PCDFIELD: + switch( nRecId ) + { + case OOBIN_ID_PCDFSHAREDITEMS: mrCacheField.importPCDFSharedItems( rStrm ); return this; + case OOBIN_ID_PCDFIELDGROUP: mrCacheField.importPCDFieldGroup( rStrm ); return this; + } + break; + + case OOBIN_ID_PCDFIELDGROUP: + switch( nRecId ) + { + case OOBIN_ID_PCDFRANGEPR: mrCacheField.importPCDFRangePr( rStrm ); break; + case OOBIN_ID_PCDFDISCRETEPR: return this; + case OOBIN_ID_PCDFGROUPITEMS: return this; + } + break; + + case OOBIN_ID_PCDFSHAREDITEMS: mrCacheField.importPCDFSharedItem( nRecId, rStrm ); break; + case OOBIN_ID_PCDFDISCRETEPR: mrCacheField.importPCDFDiscretePrItem( nRecId, rStrm ); break; + case OOBIN_ID_PCDFGROUPITEMS: mrCacheField.importPCDFGroupItem( nRecId, rStrm ); break; + } + return 0; +} + +void OoxPivotCacheFieldContext::onStartRecord( RecordInputStream& rStrm ) +{ + if( isRootElement() ) + mrCacheField.importPCDField( rStrm ); +} + +// ============================================================================ + +OoxPivotCacheDefinitionFragment::OoxPivotCacheDefinitionFragment( + const WorkbookHelper& rHelper, const OUString& rFragmentPath, PivotCache& rPivotCache ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ), + mrPivotCache( rPivotCache ) +{ +} + +ContextHandlerRef OoxPivotCacheDefinitionFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( pivotCacheDefinition ) ) { mrPivotCache.importPivotCacheDefinition( rAttribs ); return this; } + break; + + case XLS_TOKEN( pivotCacheDefinition ): + switch( nElement ) + { + case XLS_TOKEN( cacheSource ): mrPivotCache.importCacheSource( rAttribs ); return this; + case XLS_TOKEN( cacheFields ): return this; + } + break; + + case XLS_TOKEN( cacheSource ): + if( nElement == XLS_TOKEN( worksheetSource ) ) mrPivotCache.importWorksheetSource( rAttribs, getRelations() ); + break; + + case XLS_TOKEN( cacheFields ): + if( nElement == XLS_TOKEN( cacheField ) ) return new OoxPivotCacheFieldContext( *this, mrPivotCache.createCacheField() ); + break; + } + return 0; +} + +ContextHandlerRef OoxPivotCacheDefinitionFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_PCDEFINITION ) { mrPivotCache.importPCDefinition( rStrm ); return this; } + break; + + case OOBIN_ID_PCDEFINITION: + switch( nRecId ) + { + case OOBIN_ID_PCDSOURCE: mrPivotCache.importPCDSource( rStrm ); return this; + case OOBIN_ID_PCDFIELDS: return this; + } + break; + + case OOBIN_ID_PCDSOURCE: + if( nRecId == OOBIN_ID_PCDSHEETSOURCE ) mrPivotCache.importPCDSheetSource( rStrm, getRelations() ); + break; + + case OOBIN_ID_PCDFIELDS: + if( nRecId == OOBIN_ID_PCDFIELD ) return new OoxPivotCacheFieldContext( *this, mrPivotCache.createCacheField() ); + break; + } + return 0; +} + +const RecordInfo* OoxPivotCacheDefinitionFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_PCDEFINITION, OOBIN_ID_PCDEFINITION + 1 }, + { OOBIN_ID_PCDFDISCRETEPR, OOBIN_ID_PCDFDISCRETEPR + 1 }, + { OOBIN_ID_PCDFGROUPITEMS, OOBIN_ID_PCDFGROUPITEMS + 1 }, + { OOBIN_ID_PCDFIELD, OOBIN_ID_PCDFIELD + 1 }, + { OOBIN_ID_PCDFIELDGROUP, OOBIN_ID_PCDFIELDGROUP + 1 }, + { OOBIN_ID_PCDFIELDS, OOBIN_ID_PCDFIELDS + 1 }, + { OOBIN_ID_PCDFRANGEPR, OOBIN_ID_PCDFRANGEPR + 1 }, + { OOBIN_ID_PCDFSHAREDITEMS, OOBIN_ID_PCDFSHAREDITEMS + 1 }, + { OOBIN_ID_PCITEM_ARRAY, OOBIN_ID_PCITEM_ARRAY + 1 }, + { OOBIN_ID_PCDSHEETSOURCE, OOBIN_ID_PCDSHEETSOURCE + 1 }, + { OOBIN_ID_PCDSOURCE, OOBIN_ID_PCDSOURCE + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +void OoxPivotCacheDefinitionFragment::finalizeImport() +{ + // finalize the cache (check source range etc.) + mrPivotCache.finalizeImport(); + + // load the cache records, if the cache is based on a deleted or an external worksheet + if( mrPivotCache.isValidDataSource() && mrPivotCache.isBasedOnDummySheet() ) + { + OUString aRecFragmentPath = getRelations().getFragmentPathFromRelId( mrPivotCache.getRecordsRelId() ); + if( aRecFragmentPath.getLength() > 0 ) + importOoxFragment( new OoxPivotCacheRecordsFragment( *this, aRecFragmentPath, mrPivotCache ) ); + } +} + +// ============================================================================ + +OoxPivotCacheRecordsFragment::OoxPivotCacheRecordsFragment( const WorkbookHelper& rHelper, + const OUString& rFragmentPath, const PivotCache& rPivotCache ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath, ISegmentProgressBarRef(), SHEETTYPE_WORKSHEET, rPivotCache.getSourceRange().Sheet ), + mrPivotCache( rPivotCache ), + mnCol( 0 ), + mnRow( 0 ), + mbInRecord( false ) +{ + // prepare sheet: insert column header names into top row + rPivotCache.writeSourceHeaderCells( *this ); +} + +ContextHandlerRef OoxPivotCacheRecordsFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( pivotCacheRecords ) ) return this; + break; + + case XLS_TOKEN( pivotCacheRecords ): + if( nElement == XLS_TOKEN( r ) ) { startCacheRecord(); return this; } + break; + + case XLS_TOKEN( r ): + { + PivotCacheItem aItem; + switch( nElement ) + { + case XLS_TOKEN( m ): break; + case XLS_TOKEN( s ): aItem.readString( rAttribs ); break; + case XLS_TOKEN( n ): aItem.readNumeric( rAttribs ); break; + case XLS_TOKEN( d ): aItem.readDate( rAttribs ); break; + case XLS_TOKEN( b ): aItem.readBool( rAttribs ); break; + case XLS_TOKEN( e ): aItem.readError( rAttribs, getUnitConverter() ); break; + case XLS_TOKEN( x ): aItem.readIndex( rAttribs ); break; + default: OSL_ENSURE( false, "OoxPivotCacheRecordsFragment::onCreateContext - unexpected element" ); + } + mrPivotCache.writeSourceDataCell( *this, mnCol, mnRow, aItem ); + ++mnCol; + } + break; + } + return 0; +} + +ContextHandlerRef OoxPivotCacheRecordsFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_PCRECORDS ) return this; + break; + + case OOBIN_ID_PCRECORDS: + switch( nRecId ) + { + case OOBIN_ID_PCRECORD: importPCRecord( rStrm ); break; + case OOBIN_ID_PCRECORDDT: startCacheRecord(); break; + default: importPCRecordItem( nRecId, rStrm ); break; + } + break; + } + return 0; +} + +const RecordInfo* OoxPivotCacheRecordsFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_PCRECORDS, OOBIN_ID_PCRECORDS + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +// private -------------------------------------------------------------------- + +void OoxPivotCacheRecordsFragment::startCacheRecord() +{ + mnCol = 0; + ++mnRow; + mbInRecord = true; +} + +void OoxPivotCacheRecordsFragment::importPCRecord( RecordInputStream& rStrm ) +{ + startCacheRecord(); + mrPivotCache.importPCRecord( rStrm, *this, mnRow ); + mbInRecord = false; +} + +void OoxPivotCacheRecordsFragment::importPCRecordItem( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + if( mbInRecord ) + { + PivotCacheItem aItem; + switch( nRecId ) + { + case OOBIN_ID_PCITEM_MISSING: break; + case OOBIN_ID_PCITEM_STRING: aItem.readString( rStrm ); break; + case OOBIN_ID_PCITEM_DOUBLE: aItem.readDouble( rStrm ); break; + case OOBIN_ID_PCITEM_DATE: aItem.readDate( rStrm ); break; + case OOBIN_ID_PCITEM_BOOL: aItem.readBool( rStrm ); break; + case OOBIN_ID_PCITEM_ERROR: aItem.readError( rStrm ); break; + case OOBIN_ID_PCITEM_INDEX: aItem.readIndex( rStrm ); break; + default: OSL_ENSURE( false, "OoxPivotCacheRecordsFragment::importPCRecordItem - unexpected record" ); + } + mrPivotCache.writeSourceDataCell( *this, mnCol, mnRow, aItem ); + ++mnCol; + } +} + +// ============================================================================ +// ============================================================================ + +namespace { + +bool lclSeekToPCDField( BiffInputStream& rStrm ) +{ + sal_Int64 nRecHandle = rStrm.getRecHandle(); + while( rStrm.startNextRecord() ) + if( rStrm.getRecId() == BIFF_ID_PCDFIELD ) + return true; + rStrm.startRecordByHandle( nRecHandle ); + return false; +} + +} // namespace + +// ---------------------------------------------------------------------------- + +BiffPivotCacheFragment::BiffPivotCacheFragment( + const WorkbookHelper& rHelper, const ::rtl::OUString& rStrmName, PivotCache& rPivotCache ) : + BiffWorkbookFragmentBase( rHelper, rStrmName, true ), + mrPivotCache( rPivotCache ) +{ +} + +bool BiffPivotCacheFragment::importFragment() +{ + if( mrStrm.startNextRecord() && (mrStrm.getRecId() == BIFF_ID_PCDEFINITION) ) + { + // read PCDEFINITION and optional PCDEFINITION2 records + mrPivotCache.importPCDefinition( mrStrm ); + + // read cache fields as long as another PCDFIELD record can be found + while( lclSeekToPCDField( mrStrm ) ) + mrPivotCache.createCacheField( true ).importPCDField( mrStrm ); + + // finalize the cache (check source range etc.) + mrPivotCache.finalizeImport(); + + // load the cache records, if the cache is based on a deleted or an external worksheet + if( mrPivotCache.isValidDataSource() && mrPivotCache.isBasedOnDummySheet() ) + { + /* Last call of lclSeekToPCDField() failed and kept stream position + unchanged. Stream should point to source data table now. */ + BiffPivotCacheRecordsContext aContext( *this, mrPivotCache ); + if( aContext.isValidSheet() ) + while( mrStrm.startNextRecord() && (mrStrm.getRecId() != BIFF_ID_EOF) ) + aContext.importRecord(); + } + } + + return mrStrm.getRecId() == BIFF_ID_EOF; +} + +// ============================================================================ + +BiffPivotCacheRecordsContext::BiffPivotCacheRecordsContext( + const BiffWorkbookFragmentBase& rFragment, const PivotCache& rPivotCache ) : + BiffWorksheetContextBase( rFragment, ISegmentProgressBarRef(), SHEETTYPE_WORKSHEET, rPivotCache.getSourceRange().Sheet ), + mrPivotCache( rPivotCache ), + mnColIdx( 0 ), + mnRow( 0 ), + mbHasShared( false ), + mbInRow( false ) +{ + // prepare sheet: insert column header names into top row + mrPivotCache.writeSourceHeaderCells( *this ); + + // find all fields without shared items, remember column indexes in source data + for( sal_Int32 nFieldIdx = 0, nFieldCount = mrPivotCache.getCacheFieldCount(), nCol = 0; nFieldIdx < nFieldCount; ++nFieldIdx ) + { + const PivotCacheField* pCacheField = mrPivotCache.getCacheField( nFieldIdx ); + if( pCacheField && pCacheField->isDatabaseField() ) + { + if( pCacheField->hasSharedItems() ) + mbHasShared = true; + else + maUnsharedCols.push_back( nCol ); + ++nCol; + } + } +} + +void BiffPivotCacheRecordsContext::importRecord() +{ + if( mrStrm.getRecId() == BIFF_ID_PCITEM_INDEXLIST ) + { + OSL_ENSURE( mbHasShared, "BiffPivotCacheRecordsContext::importRecord - unexpected PCITEM_INDEXLIST record" ); + // PCITEM_INDEXLIST record always in front of a new data row + startNextRow(); + mrPivotCache.importPCItemIndexList( mrStrm, *this, mnRow ); + mbInRow = !maUnsharedCols.empty(); // mbInRow remains true, if unshared items are expected + return; + } + + PivotCacheItem aItem; + switch( mrStrm.getRecId() ) + { + case BIFF_ID_PCITEM_MISSING: break; + case BIFF_ID_PCITEM_STRING: aItem.readString( mrStrm, *this ); break; + case BIFF_ID_PCITEM_DOUBLE: aItem.readDouble( mrStrm ); break; + case BIFF_ID_PCITEM_INTEGER: aItem.readInteger( mrStrm ); break; + case BIFF_ID_PCITEM_DATE: aItem.readDate( mrStrm ); break; + case BIFF_ID_PCITEM_BOOL: aItem.readBool( mrStrm ); break; + case BIFF_ID_PCITEM_ERROR: aItem.readError( mrStrm ); break; + default: return; // unknown record, ignore + } + + // find next column index, might start new row if no fields with shared items exist + if( mbInRow && (mnColIdx == maUnsharedCols.size()) ) + { + OSL_ENSURE( !mbHasShared, "BiffPivotCacheRecordsContext::importRecord - PCITEM_INDEXLIST record missing" ); + mbInRow = mbHasShared; // do not leave current row if PCITEM_INDEXLIST is expected + } + // start next row on first call, or on row wrap without shared items + if( !mbInRow ) + startNextRow(); + + // write the item data to the sheet cell + OSL_ENSURE( mnColIdx < maUnsharedCols.size(), "BiffPivotCacheRecordsContext::importRecord - invalid column index" ); + if( mnColIdx < maUnsharedCols.size() ) + mrPivotCache.writeSourceDataCell( *this, maUnsharedCols[ mnColIdx ], mnRow, aItem ); + ++mnColIdx; +} + +void BiffPivotCacheRecordsContext::startNextRow() +{ + mnColIdx = 0; + ++mnRow; + mbInRow = true; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/pivottablebuffer.cxx b/oox/source/xls/pivottablebuffer.cxx new file mode 100644 index 000000000000..b4e3e1c52a65 --- /dev/null +++ b/oox/source/xls/pivottablebuffer.cxx @@ -0,0 +1,1553 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/pivottablebuffer.hxx" +#include <set> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/sheet/CellFlags.hpp> +#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotFieldReference.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp> +#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp> +#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> +#include <com/sun/star/sheet/GeneralFunction.hpp> +#include <com/sun/star/sheet/XDataPilotDataLayoutFieldSupplier.hpp> +#include <com/sun/star/sheet/XDataPilotField.hpp> +#include <com/sun/star/sheet/XDataPilotTablesSupplier.hpp> +#include <com/sun/star/sheet/XSheetOperation.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +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::XPropertySet; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::container::XNamed; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::sheet::DataPilotFieldOrientation; +using ::com::sun::star::sheet::XDataPilotDataLayoutFieldSupplier; +using ::com::sun::star::sheet::XDataPilotDescriptor; +using ::com::sun::star::sheet::XDataPilotField; +using ::com::sun::star::sheet::XDataPilotTables; +using ::com::sun::star::sheet::XDataPilotTablesSupplier; +using ::com::sun::star::sheet::XSheetOperation; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_Int32 OOX_PT_DATALAYOUTFIELD = -2; /// Placeholder index of data layout field. + +const sal_Int32 OOX_PT_PREVIOUS_ITEM = 0x001000FC; /// Calculation of data item result is based on previous item. +const sal_Int32 OOX_PT_NEXT_ITEM = 0x001000FD; /// Calculation of data item result is based on next item. + +// ---------------------------------------------------------------------------- + +const sal_uInt32 OOBIN_PTFIELD_DATAFIELD = 0x00000008; +const sal_uInt32 OOBIN_PTFIELD_DEFAULT = 0x00000100; +const sal_uInt32 OOBIN_PTFIELD_SUM = 0x00000200; +const sal_uInt32 OOBIN_PTFIELD_COUNTA = 0x00000400; +const sal_uInt32 OOBIN_PTFIELD_AVERAGE = 0x00000800; +const sal_uInt32 OOBIN_PTFIELD_MAX = 0x00001000; +const sal_uInt32 OOBIN_PTFIELD_MIN = 0x00002000; +const sal_uInt32 OOBIN_PTFIELD_PRODUCT = 0x00004000; +const sal_uInt32 OOBIN_PTFIELD_COUNT = 0x00008000; +const sal_uInt32 OOBIN_PTFIELD_STDDEV = 0x00010000; +const sal_uInt32 OOBIN_PTFIELD_STDDEVP = 0x00020000; +const sal_uInt32 OOBIN_PTFIELD_VAR = 0x00040000; +const sal_uInt32 OOBIN_PTFIELD_VARP = 0x00080000; + +const sal_uInt32 OOBIN_PTFIELD_SHOWALL = 0x00000020; +const sal_uInt32 OOBIN_PTFIELD_OUTLINE = 0x00000040; +const sal_uInt32 OOBIN_PTFIELD_INSERTBLANKROW = 0x00000080; +const sal_uInt32 OOBIN_PTFIELD_SUBTOTALTOP = 0x00000100; +const sal_uInt32 OOBIN_PTFIELD_INSERTPAGEBREAK = 0x00000800; +const sal_uInt32 OOBIN_PTFIELD_AUTOSORT = 0x00001000; +const sal_uInt32 OOBIN_PTFIELD_SORTASCENDING = 0x00002000; +const sal_uInt32 OOBIN_PTFIELD_AUTOSHOW = 0x00004000; +const sal_uInt32 OOBIN_PTFIELD_AUTOSHOWTOP = 0x00008000; +const sal_uInt32 OOBIN_PTFIELD_MULTIPAGEITEMS = 0x00080000; + +const sal_uInt16 OOBIN_PTFITEM_HIDDEN = 0x0001; +const sal_uInt16 OOBIN_PTFITEM_HIDEDETAILS = 0x0002; + +const sal_uInt8 OOBIN_PTPAGEFIELD_HASNAME = 0x01; +const sal_uInt8 OOBIN_PTPAGEFIELD_HASOLAPCAPTION = 0x02; +const sal_Int32 OOBIN_PTPAGEFIELD_MULTIITEMS = 0x001000FE; + +const sal_uInt16 OOBIN_PTFILTER_HASNAME = 0x0001; +const sal_uInt16 OOBIN_PTFILTER_HASDESCRIPTION = 0x0002; +const sal_uInt16 OOBIN_PTFILTER_HASSTRVALUE1 = 0x0004; +const sal_uInt16 OOBIN_PTFILTER_HASSTRVALUE2 = 0x0008; + +const sal_uInt8 OOBIN_TOP10FILTER_TOP = 0x01; +const sal_uInt8 OOBIN_TOP10FILTER_PERCENT = 0x02; + +const sal_uInt32 OOBIN_PTDEF_SHOWITEMS = 0x00000100; +const sal_uInt32 OOBIN_PTDEF_DISABLEFIELDLIST = 0x00000400; +const sal_uInt32 OOBIN_PTDEF_HIDECALCMEMBERS = 0x00001000; +const sal_uInt32 OOBIN_PTDEF_WITHHIDDENTOTALS = 0x00002000; +const sal_uInt32 OOBIN_PTDEF_HIDEDRILL = 0x00100000; +const sal_uInt32 OOBIN_PTDEF_PRINTDRILL = 0x00200000; +const sal_uInt32 OOBIN_PTDEF_HIDEHEADERS = 0x80000000; + +const sal_uInt32 OOBIN_PTDEF_SHOWEMPTYROW = 0x00000004; +const sal_uInt32 OOBIN_PTDEF_SHOWEMPTYCOL = 0x00000008; +const sal_uInt32 OOBIN_PTDEF_ENABLEDRILL = 0x00000020; +const sal_uInt32 OOBIN_PTDEF_PRESERVEFORMATTING = 0x00000080; +const sal_uInt32 OOBIN_PTDEF_SHOWERROR = 0x00000200; +const sal_uInt32 OOBIN_PTDEF_SHOWMISSING = 0x00000400; +const sal_uInt32 OOBIN_PTDEF_PAGEOVERTHENDOWN = 0x00000800; +const sal_uInt32 OOBIN_PTDEF_SUBTOTALHIDDENITEMS = 0x00001000; +const sal_uInt32 OOBIN_PTDEF_ROWGRANDTOTALS = 0x00002000; +const sal_uInt32 OOBIN_PTDEF_COLGRANDTOTALS = 0x00004000; +const sal_uInt32 OOBIN_PTDEF_FIELDPRINTTITLES = 0x00008000; +const sal_uInt32 OOBIN_PTDEF_ITEMPRINTTITLES = 0x00020000; +const sal_uInt32 OOBIN_PTDEF_MERGEITEM = 0x00040000; +const sal_uInt32 OOBIN_PTDEF_HASDATACAPTION = 0x00080000; +const sal_uInt32 OOBIN_PTDEF_HASGRANDTOTALCAPTION = 0x00100000; +const sal_uInt32 OOBIN_PTDEF_HASPAGESTYLE = 0x00200000; +const sal_uInt32 OOBIN_PTDEF_HASPIVOTTABLESTYLE = 0x00400000; +const sal_uInt32 OOBIN_PTDEF_HASVACATEDSTYLE = 0x00800000; +const sal_uInt32 OOBIN_PTDEF_HASTAG = 0x40000000; + +const sal_uInt32 OOBIN_PTDEF_NOERRORCAPTION = 0x00000040; +const sal_uInt32 OOBIN_PTDEF_NOMISSINGCAPTION = 0x00000080; +const sal_uInt32 OOBIN_PTDEF_HASROWHEADERCAPTION = 0x00000400; +const sal_uInt32 OOBIN_PTDEF_HASCOLHEADERCAPTION = 0x00000800; +const sal_uInt32 OOBIN_PTDEF_FIELDLISTSORTASC = 0x00001000; +const sal_uInt32 OOBIN_PTDEF_NOCUSTOMLISTSORT = 0x00004000; + +const sal_uInt8 OOBIN_PTDEF_ROWAXIS = 1; +const sal_uInt8 OOBIN_PTDEF_COLAXIS = 2; + +// ---------------------------------------------------------------------------- + +const sal_uInt16 BIFF_PT_NOSTRING = 0xFFFF; + +const sal_uInt16 BIFF_PTFIELD_DATAFIELD = 0x0008; +const sal_uInt16 BIFF_PTFIELD_DEFAULT = 0x0001; +const sal_uInt16 BIFF_PTFIELD_SUM = 0x0002; +const sal_uInt16 BIFF_PTFIELD_COUNTA = 0x0004; +const sal_uInt16 BIFF_PTFIELD_AVERAGE = 0x0008; +const sal_uInt16 BIFF_PTFIELD_MAX = 0x0010; +const sal_uInt16 BIFF_PTFIELD_MIN = 0x0020; +const sal_uInt16 BIFF_PTFIELD_PRODUCT = 0x0040; +const sal_uInt16 BIFF_PTFIELD_COUNT = 0x0080; +const sal_uInt16 BIFF_PTFIELD_STDDEV = 0x0100; +const sal_uInt16 BIFF_PTFIELD_STDDEVP = 0x0200; +const sal_uInt16 BIFF_PTFIELD_VAR = 0x0400; +const sal_uInt16 BIFF_PTFIELD_VARP = 0x0800; + +const sal_uInt32 BIFF_PTFIELD2_SHOWALL = 0x00000001; +const sal_uInt32 BIFF_PTFIELD2_AUTOSORT = 0x00000200; +const sal_uInt32 BIFF_PTFIELD2_SORTASCENDING = 0x00000400; +const sal_uInt32 BIFF_PTFIELD2_AUTOSHOW = 0x00000800; +const sal_uInt32 BIFF_PTFIELD2_AUTOSHOWTOP = 0x00001000; +const sal_uInt32 BIFF_PTFIELD2_OUTLINE = 0x00200000; +const sal_uInt32 BIFF_PTFIELD2_INSERTBLANKROW = 0x00400000; +const sal_uInt32 BIFF_PTFIELD2_SUBTOTALTOP = 0x00800000; + +const sal_uInt16 BIFF_PTFITEM_HIDDEN = 0x0001; +const sal_uInt16 BIFF_PTFITEM_HIDEDETAILS = 0x0002; + +const sal_uInt16 BIFF_PTDEF_ROWGRANDTOTALS = 0x0001; +const sal_uInt16 BIFF_PTDEF_COLGRANDTOTALS = 0x0002; + +const sal_uInt8 BIFF_PTDEF_ROWAXIS = 1; +const sal_uInt8 BIFF_PTDEF_COLAXIS = 2; + +const sal_uInt32 BIFF_PTDEF2_PAGEOVERTHENDOWN = 0x00000001; +const sal_uInt32 BIFF_PTDE2F_ENABLEDRILL = 0x00020000; +const sal_uInt32 BIFF_PTDEF2_PRESERVEFORMATTING = 0x00080000; +const sal_uInt32 BIFF_PTDEF2_MERGEITEM = 0x00100000; +const sal_uInt32 BIFF_PTDEF2_SHOWERROR = 0x00200000; +const sal_uInt32 BIFF_PTDEF2_SHOWMISSING = 0x00400000; +const sal_uInt32 BIFF_PTDEF2_SUBTOTALHIDDENITEMS = 0x00800000; + +const sal_Int16 BIFF_PTPAGEFIELDS_ALLITEMS = 0x7FFD; + +const sal_Int16 BIFF_PTDATAFIELD_PREVIOUS = 0x7FFB; +const sal_Int16 BIFF_PTDATAFIELD_NEXT = 0x7FFC; + +// ---------------------------------------------------------------------------- + +OUString lclReadPivotString( const WorkbookHelper& rHelper, BiffInputStream& rStrm, sal_uInt16 nLen ) +{ + if( nLen == BIFF_PT_NOSTRING ) + return OUString(); + return (rHelper.getBiff() == BIFF8) ? rStrm.readUniStringBody( nLen ) : rStrm.readCharArrayUC( nLen, rHelper.getTextEncoding() ); +} + +} // namespace + +// ============================================================================ + +PTFieldItemModel::PTFieldItemModel() : + mnCacheItem( -1 ), + mnType( XML_data ), + mbShowDetails( true ), + mbHidden( false ) +{ +} + +void PTFieldItemModel::setBinType( sal_uInt16 nType ) +{ + static const sal_Int32 spnTypes[] = { XML_data, XML_default, + XML_sum, XML_countA, XML_avg, XML_max, XML_min, XML_product, XML_count, + XML_stdDev, XML_stdDevP, XML_var, XML_varP, XML_grand, XML_blank }; + mnType = STATIC_ARRAY_SELECT( spnTypes, nType, XML_data ); +} + +// ---------------------------------------------------------------------------- + +PTFieldModel::PTFieldModel() : + mnAxis( XML_TOKEN_INVALID ), + mnNumFmtId( 0 ), + mnAutoShowItems( 10 ), + mnAutoShowRankBy( -1 ), + mnSortType( XML_manual ), + mnSortRefField( -1 ), + mnSortRefItem( -1 ), + mbDataField( false ), + mbDefaultSubtotal( true ), + mbSumSubtotal( false ), + mbCountASubtotal( false ), + mbAverageSubtotal( false ), + mbMaxSubtotal( false ), + mbMinSubtotal( false ), + mbProductSubtotal( false ), + mbCountSubtotal( false ), + mbStdDevSubtotal( false ), + mbStdDevPSubtotal( false ), + mbVarSubtotal( false ), + mbVarPSubtotal( false ), + mbShowAll( true ), + mbOutline( true ), + mbSubtotalTop( true ), + mbInsertBlankRow( false ), + mbInsertPageBreak( false ), + mbAutoShow( false ), + mbTopAutoShow( true ), + mbMultiPageItems( false ) +{ +} + +void PTFieldModel::setBinAxis( sal_uInt8 nAxis ) +{ + /* Weird. The axis field is organized as bit field, but only one of the + row/col/page flags are allowed at the same time and refer to the values + 'axisRow', 'axisCol', and 'axisPage' of the XML attribute + 'pivotField@axis'. Additionally, the fourth bit determines if the field + is a data field, which may appear combined with the row/col/page flags. + Therefore, this bit is unrelated to the 'axisValues' value of the + 'pivotField@axis' attribute, but refers to the 'pivotField@dataField' + boolean attribute. */ + static const sal_Int32 spnAxisIds[] = { XML_TOKEN_INVALID, XML_axisRow, XML_axisCol, XML_TOKEN_INVALID, XML_axisPage }; + mnAxis = STATIC_ARRAY_SELECT( spnAxisIds, nAxis, XML_TOKEN_INVALID ); +} + +// ---------------------------------------------------------------------------- + +PTPageFieldModel::PTPageFieldModel() : + mnField( -1 ), + mnItem( OOBIN_PTPAGEFIELD_MULTIITEMS ) +{ +} + +// ---------------------------------------------------------------------------- + +PTDataFieldModel::PTDataFieldModel() : + mnField( -1 ), + mnSubtotal( XML_sum ), + mnShowDataAs( XML_normal ), + mnBaseField( -1 ), + mnBaseItem( -1 ), + mnNumFmtId( 0 ) +{ +} + +void PTDataFieldModel::setBinSubtotal( sal_Int32 nSubtotal ) +{ + static sal_Int32 spnSubtotals[] = { XML_sum, XML_count, XML_average, XML_max, XML_min, XML_product, XML_countNums, XML_stdDev, XML_stdDevp, XML_var, XML_varp }; + mnSubtotal = STATIC_ARRAY_SELECT( spnSubtotals, nSubtotal, XML_TOKEN_INVALID ); +} + +void PTDataFieldModel::setBinShowDataAs( sal_Int32 nShowDataAs ) +{ + static sal_Int32 spnShowDataAs[] = { XML_normal, XML_difference, XML_percent, XML_percentDiff, XML_runTotal, XML_percentOfRow, XML_percentOfCol, XML_percentOfTotal, XML_index }; + mnShowDataAs = STATIC_ARRAY_SELECT( spnShowDataAs, nShowDataAs, XML_TOKEN_INVALID ); +} + +// ---------------------------------------------------------------------------- + +PivotTableField::PivotTableField( PivotTable& rPivotTable, sal_Int32 nFieldIndex ) : + WorkbookHelper( rPivotTable ), + mrPivotTable( rPivotTable ), + mnFieldIndex( nFieldIndex ) +{ +} + +void PivotTableField::importPivotField( const AttributeList& rAttribs ) +{ + /* The documentation mentions a value 'axisValues' for the attribute + 'pivotField@axis'. But this value is not used to mark a data field, as + data fields may be inserted in one of the row/column/page dimensions at + the same time. Therefore, the boolean attribute 'pivotField@dataField' + is really used to mark data fields. */ + maModel.mnAxis = rAttribs.getToken( XML_axis, XML_TOKEN_INVALID ); + maModel.mnNumFmtId = rAttribs.getInteger( XML_numFmtId, 0 ); + maModel.mnAutoShowItems = rAttribs.getInteger( XML_itemPageCount, 10 ); + maModel.mnAutoShowRankBy = rAttribs.getInteger( XML_rankBy, -1 ); + maModel.mnSortType = rAttribs.getToken( XML_sortType, XML_manual ); + maModel.mbDataField = rAttribs.getBool( XML_dataField, false ); + maModel.mbDefaultSubtotal = rAttribs.getBool( XML_defaultSubtotal, true ); + maModel.mbSumSubtotal = rAttribs.getBool( XML_sumSubtotal, false ); + maModel.mbCountASubtotal = rAttribs.getBool( XML_countASubtotal, false ); + maModel.mbAverageSubtotal = rAttribs.getBool( XML_avgSubtotal, false ); + maModel.mbMaxSubtotal = rAttribs.getBool( XML_maxSubtotal, false ); + maModel.mbMinSubtotal = rAttribs.getBool( XML_minSubtotal, false ); + maModel.mbProductSubtotal = rAttribs.getBool( XML_productSubtotal, false ); + maModel.mbCountSubtotal = rAttribs.getBool( XML_countSubtotal, false ); + maModel.mbStdDevSubtotal = rAttribs.getBool( XML_stdDevSubtotal, false ); + maModel.mbStdDevPSubtotal = rAttribs.getBool( XML_stdDevPSubtotal, false ); + maModel.mbVarSubtotal = rAttribs.getBool( XML_varSubtotal, false ); + maModel.mbVarPSubtotal = rAttribs.getBool( XML_varPSubtotal, false ); + maModel.mbShowAll = rAttribs.getBool( XML_showAll, true ); + maModel.mbOutline = rAttribs.getBool( XML_outline, true ); + maModel.mbSubtotalTop = rAttribs.getBool( XML_subtotalTop, true ); + maModel.mbInsertBlankRow = rAttribs.getBool( XML_insertBlankRow, false ); + maModel.mbInsertPageBreak = rAttribs.getBool( XML_insertPageBreak, false ); + maModel.mbAutoShow = rAttribs.getBool( XML_autoShow, false ); + maModel.mbTopAutoShow = rAttribs.getBool( XML_topAutoShow, true ); + maModel.mbMultiPageItems = rAttribs.getBool( XML_multipleItemSelectionAllowed, false ); +} + +void PivotTableField::importItem( const AttributeList& rAttribs ) +{ + PTFieldItemModel aModel; + aModel.mnCacheItem = rAttribs.getInteger( XML_x, -1 ); + aModel.mnType = rAttribs.getToken( XML_t, XML_data ); + aModel.mbShowDetails = rAttribs.getBool( XML_sd, true ); + aModel.mbHidden = rAttribs.getBool( XML_h, false ); + maItems.push_back( aModel ); +} + +void PivotTableField::importReference( const AttributeList& rAttribs ) +{ + // field index is stored as unsigned integer + maModel.mnSortRefField = static_cast< sal_Int32 >( rAttribs.getUnsigned( XML_field, SAL_MAX_UINT32 ) ); +} + +void PivotTableField::importReferenceItem( const AttributeList& rAttribs ) +{ + maModel.mnSortRefItem = rAttribs.getInteger( XML_v, -1 ); +} + +void PivotTableField::importPTField( RecordInputStream& rStrm ) +{ + sal_uInt32 nFlags1, nFlags2; + rStrm >> nFlags1 >> maModel.mnNumFmtId >> nFlags2 >> maModel.mnAutoShowItems >> maModel.mnAutoShowRankBy; + + maModel.setBinAxis( extractValue< sal_uInt8 >( nFlags1, 0, 3 ) ); + maModel.mbDataField = getFlag( nFlags1, OOBIN_PTFIELD_DATAFIELD ); + maModel.mbDefaultSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_DEFAULT ); + maModel.mbSumSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_SUM ); + maModel.mbCountASubtotal = getFlag( nFlags1, OOBIN_PTFIELD_COUNTA ); + maModel.mbAverageSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_AVERAGE ); + maModel.mbMaxSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_MAX ); + maModel.mbMinSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_MIN ); + maModel.mbProductSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_PRODUCT ); + maModel.mbCountSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_COUNT ); + maModel.mbStdDevSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_STDDEV ); + maModel.mbStdDevPSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_STDDEVP ); + maModel.mbVarSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_VAR ); + maModel.mbVarPSubtotal = getFlag( nFlags1, OOBIN_PTFIELD_VARP ); + + maModel.mbShowAll = getFlag( nFlags2, OOBIN_PTFIELD_SHOWALL ); + maModel.mbOutline = getFlag( nFlags2, OOBIN_PTFIELD_OUTLINE ); + maModel.mbSubtotalTop = getFlag( nFlags2, OOBIN_PTFIELD_SUBTOTALTOP ); + maModel.mbInsertBlankRow = getFlag( nFlags2, OOBIN_PTFIELD_INSERTBLANKROW ); + maModel.mbInsertPageBreak = getFlag( nFlags2, OOBIN_PTFIELD_INSERTPAGEBREAK ); + maModel.mbAutoShow = getFlag( nFlags2, OOBIN_PTFIELD_AUTOSHOW ); + maModel.mbTopAutoShow = getFlag( nFlags2, OOBIN_PTFIELD_AUTOSHOWTOP ); + maModel.mbMultiPageItems = getFlag( nFlags2, OOBIN_PTFIELD_MULTIPAGEITEMS ); + + bool bAutoSort = getFlag( nFlags2, OOBIN_PTFIELD_AUTOSORT ); + bool bAscending = getFlag( nFlags2, OOBIN_PTFIELD_SORTASCENDING ); + maModel.mnSortType = bAutoSort ? (bAscending ? XML_ascending : XML_descending) : XML_manual; +} + +void PivotTableField::importPTFItem( RecordInputStream& rStrm ) +{ + PTFieldItemModel aModel; + sal_uInt8 nType; + sal_uInt16 nFlags; + rStrm >> nType >> nFlags >> aModel.mnCacheItem; + + aModel.setBinType( nType ); + aModel.mbShowDetails = !getFlag( nFlags, OOBIN_PTFITEM_HIDEDETAILS ); + aModel.mbHidden = getFlag( nFlags, OOBIN_PTFITEM_HIDDEN ); + + maItems.push_back( aModel ); +} + +void PivotTableField::importPTReference( RecordInputStream& rStrm ) +{ + rStrm >> maModel.mnSortRefField; +} + +void PivotTableField::importPTReferenceItem( RecordInputStream& rStrm ) +{ + rStrm >> maModel.mnSortRefItem; +} + +void PivotTableField::importPTField( BiffInputStream& rStrm ) +{ + sal_uInt16 nAxis, nSubtCount, nSubtotals; + rStrm >> nAxis >> nSubtCount >> nSubtotals; + rStrm.skip( 2 ); // item count + + maModel.setBinAxis( extractValue< sal_uInt8 >( nAxis, 0, 3 ) ); + maModel.mbDataField = getFlag( nAxis, BIFF_PTFIELD_DATAFIELD ); + + maModel.mbDefaultSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_DEFAULT ); + maModel.mbSumSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_SUM ); + maModel.mbCountASubtotal = getFlag( nSubtotals, BIFF_PTFIELD_COUNTA ); + maModel.mbAverageSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_AVERAGE ); + maModel.mbMaxSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_MAX ); + maModel.mbMinSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_MIN ); + maModel.mbProductSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_PRODUCT ); + maModel.mbCountSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_COUNT ); + maModel.mbStdDevSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_STDDEV ); + maModel.mbStdDevPSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_STDDEVP ); + maModel.mbVarSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_VAR ); + maModel.mbVarPSubtotal = getFlag( nSubtotals, BIFF_PTFIELD_VARP ); + + // set different defaults for BIFF + maModel.mbShowAll = maModel.mbOutline = maModel.mbSubtotalTop = false; + + // read following items + while( (rStrm.getNextRecId() == BIFF_ID_PTFITEM) && rStrm.startNextRecord() ) + importPTFItem( rStrm ); + + // read following PTFIELD2 record with additional field settings + if( (getBiff() == BIFF8) && (rStrm.getNextRecId() == BIFF_ID_PTFIELD2) && rStrm.startNextRecord() ) + importPTField2( rStrm ); +} + +void PivotTableField::importPTField2( BiffInputStream& rStrm ) +{ + sal_uInt32 nFlags; + rStrm >> nFlags; + maModel.mnSortRefItem = rStrm.readInt16(); + maModel.mnAutoShowRankBy = rStrm.readInt16(); + maModel.mnNumFmtId = rStrm.readuInt16(); + + maModel.mnAutoShowItems = extractValue< sal_Int32 >( nFlags, 24, 8 ); + maModel.mbShowAll = getFlag( nFlags, BIFF_PTFIELD2_SHOWALL ); + maModel.mbOutline = getFlag( nFlags, BIFF_PTFIELD2_OUTLINE ); + maModel.mbSubtotalTop = getFlag( nFlags, BIFF_PTFIELD2_SUBTOTALTOP ); + maModel.mbInsertBlankRow = getFlag( nFlags, BIFF_PTFIELD2_INSERTBLANKROW ); + maModel.mbAutoShow = getFlag( nFlags, BIFF_PTFIELD2_AUTOSHOW ); + maModel.mbTopAutoShow = getFlag( nFlags, BIFF_PTFIELD2_AUTOSHOWTOP ); + + bool bAutoSort = getFlag( nFlags, BIFF_PTFIELD2_AUTOSORT ); + bool bAscending = getFlag( nFlags, BIFF_PTFIELD2_SORTASCENDING ); + maModel.mnSortType = bAutoSort ? (bAscending ? XML_ascending : XML_descending) : XML_manual; + // mnSortRefField == OOX_PT_DATALAYOUTFIELD will indicate sorting by data field + if( maModel.mnSortRefItem >= 0 ) + maModel.mnSortRefField = OOX_PT_DATALAYOUTFIELD; +} + +void PivotTableField::importPTFItem( BiffInputStream& rStrm ) +{ + PTFieldItemModel aModel; + sal_uInt16 nType, nFlags; + sal_Int16 nCacheItem; + rStrm >> nType >> nFlags >> nCacheItem; + + aModel.setBinType( nType ); + aModel.mnCacheItem = nCacheItem; + aModel.mbShowDetails = !getFlag( nFlags, BIFF_PTFITEM_HIDEDETAILS ); + aModel.mbHidden = getFlag( nFlags, BIFF_PTFITEM_HIDDEN ); + + maItems.push_back( aModel ); +} + +void PivotTableField::finalizeImport( const Reference< XDataPilotDescriptor >& rxDPDesc ) +{ + /* Process all fields based on source data, other fields (e.g. group + fields) are processed from here. PivotCacahe::getDatabaseIndex() + returns -1 for all fields not based on source data. */ + Reference< XDataPilotField > xDPField; + sal_Int32 nDatabaseIdx = mrPivotTable.getCacheDatabaseIndex( mnFieldIndex ); + if( (nDatabaseIdx >= 0) && rxDPDesc.is() ) try + { + // try to get the source field and its name from passed DataPilot descriptor + Reference< XIndexAccess > xDPFieldsIA( rxDPDesc->getDataPilotFields(), UNO_SET_THROW ); + xDPField.set( xDPFieldsIA->getByIndex( nDatabaseIdx ), UNO_QUERY_THROW ); + Reference< XNamed > xDPFieldName( xDPField, UNO_QUERY_THROW ); + maDPFieldName = xDPFieldName->getName(); + OSL_ENSURE( maDPFieldName.getLength() > 0, "PivotTableField::finalizeImport - no field name in source data found" ); + + // try to convert grouping settings + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( mnFieldIndex ) ) + { + // numeric grouping is done inplace, no nested group fields will appear + if( pCacheField->hasNumericGrouping() ) + { + pCacheField->convertNumericGrouping( xDPField ); + } + else if( pCacheField->hasDateGrouping() ) + { + // first date group settings are inplace + pCacheField->createDateGroupField( xDPField ); + // create all nested group fields (if any) + mrPivotTable.finalizeDateGroupingImport( xDPField, mnFieldIndex ); + } + else if( pCacheField->hasParentGrouping() ) + { + // create a list of all item names, needed to map between original and group items + ::std::vector< OUString > aItems; + pCacheField->getCacheItemNames( aItems ); + PivotCacheGroupItemVector aItemNames; + for( ::std::vector< OUString >::iterator aIt = aItems.begin(), aEnd = aItems.end(); aIt != aEnd; ++aIt ) + aItemNames.push_back( PivotCacheGroupItem( *aIt ) ); + // create all nested group fields (if any) + mrPivotTable.finalizeParentGroupingImport( xDPField, *pCacheField, aItemNames ); + } + } + } + catch( Exception& ) + { + } +} + +void PivotTableField::finalizeDateGroupingImport( const Reference< XDataPilotField >& rxBaseDPField, sal_Int32 nBaseFieldIdx ) +{ + if( maDPFieldName.getLength() == 0 ) // prevent endless loops if file format is broken + { + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( mnFieldIndex ) ) + { + if( !pCacheField->isDatabaseField() && pCacheField->hasDateGrouping() && (pCacheField->getGroupBaseField() == nBaseFieldIdx) ) + { + maDPFieldName = pCacheField->createDateGroupField( rxBaseDPField ); + OSL_ENSURE( maDPFieldName.getLength() > 0, "PivotTableField::finalizeDateGroupingImport - cannot create date group field" ); + } + } + } +} + +void PivotTableField::finalizeParentGroupingImport( const Reference< XDataPilotField >& rxBaseDPField, PivotCacheGroupItemVector& orItemNames ) +{ + if( maDPFieldName.getLength() == 0 ) // prevent endless loops if file format is broken + { + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( mnFieldIndex ) ) + { + maDPFieldName = pCacheField->createParentGroupField( rxBaseDPField, orItemNames ); + // on success, try to create nested group fields + Reference< XDataPilotField > xDPField = mrPivotTable.getDataPilotField( maDPFieldName ); + if( xDPField.is() ) + mrPivotTable.finalizeParentGroupingImport( xDPField, *pCacheField, orItemNames ); + } + } +} + +void PivotTableField::convertRowField() +{ + convertRowColPageField( XML_axisRow ); +} + +void PivotTableField::convertColField() +{ + convertRowColPageField( XML_axisCol ); +} + +void PivotTableField::convertHiddenField() +{ + convertRowColPageField( XML_TOKEN_INVALID ); +} + +void PivotTableField::convertPageField( const PTPageFieldModel& rPageField ) +{ + OSL_ENSURE( rPageField.mnField == mnFieldIndex, "PivotTableField::convertPageField - wrong field index" ); + // convert all settings common for row/column/page fields + Reference< XDataPilotField > xDPField = convertRowColPageField( XML_axisPage ); + + if( xDPField.is() ) + { + PropertySet aPropSet( xDPField ); + using namespace ::com::sun::star::sheet; + + // find cache item used as 'selected page' + sal_Int32 nCacheItem = -1; + if( maModel.mbMultiPageItems ) + { + // multiple items may be selected + OSL_ENSURE( rPageField.mnItem == OOBIN_PTPAGEFIELD_MULTIITEMS, "PivotTableField::convertPageField - unexpected cache item index" ); + // try to find a single visible item + bool bHasMultiItems = false; + for( ItemModelVector::iterator aIt = maItems.begin(), aEnd = maItems.end(); (aIt != aEnd) && !bHasMultiItems; ++aIt ) + { + if( (aIt->mnType == XML_data) && !aIt->mbHidden ) + { + bHasMultiItems = nCacheItem >= 0; + nCacheItem = bHasMultiItems ? -1 : aIt->mnCacheItem; + } + } + } + else + { + // single item may be selected + if( (0 <= rPageField.mnItem) && (rPageField.mnItem < static_cast< sal_Int32 >( maItems.size() )) ) + nCacheItem = maItems[ rPageField.mnItem ].mnCacheItem; + } + + if( nCacheItem >= 0 ) + { + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( mnFieldIndex ) ) + { + if( const PivotCacheItem* pSharedItem = pCacheField->getCacheItem( nCacheItem ) ) + { + OUString aSelectedPage = pSharedItem->getName(); + if( aSelectedPage.getLength() > 0 ) + aPropSet.setProperty( PROP_SelectedPage, aSelectedPage ); + } + } + } + } +} + +void PivotTableField::convertDataField( const PTDataFieldModel& rDataField ) +{ + OSL_ENSURE( rDataField.mnField == mnFieldIndex, "PivotTableField::convertDataField - wrong field index" ); + OSL_ENSURE( maModel.mbDataField, "PivotTableField::convertDataField - not a data field" ); + Reference< XDataPilotField > xDPField = mrPivotTable.getDataPilotField( maDPFieldName ); + if( xDPField.is() ) + { + PropertySet aPropSet( xDPField ); + using namespace ::com::sun::star::sheet; + + // field orientation + aPropSet.setProperty( PROP_Orientation, DataPilotFieldOrientation_DATA ); + + /* Field aggregation function. Documentation is a little bit confused + about which names to use for the count functions. The name 'count' + means 'count all', and 'countNum' means 'count numbers'. On the + other hand, for subtotals, 'countA' means 'count all', and 'count' + means 'count numbers' (see above). */ + GeneralFunction eAggFunc = GeneralFunction_SUM; + switch( rDataField.mnSubtotal ) + { + case XML_sum: eAggFunc = GeneralFunction_SUM; break; + case XML_count: eAggFunc = GeneralFunction_COUNT; break; + case XML_average: eAggFunc = GeneralFunction_AVERAGE; break; + case XML_max: eAggFunc = GeneralFunction_MAX; break; + case XML_min: eAggFunc = GeneralFunction_MIN; break; + case XML_product: eAggFunc = GeneralFunction_PRODUCT; break; + case XML_countNums: eAggFunc = GeneralFunction_COUNTNUMS; break; + case XML_stdDev: eAggFunc = GeneralFunction_STDEV; break; + case XML_stdDevp: eAggFunc = GeneralFunction_STDEVP; break; + case XML_var: eAggFunc = GeneralFunction_VAR; break; + case XML_varp: eAggFunc = GeneralFunction_VARP; break; + default: OSL_ENSURE( false, "PivotTableField::convertDataField - unknown aggregation function" ); + } + aPropSet.setProperty( PROP_Function, eAggFunc ); + + // field reference ('show data as') + DataPilotFieldReference aReference; + aReference.ReferenceType = DataPilotFieldReferenceType::NONE; + switch( rDataField.mnShowDataAs ) + { + case XML_difference: aReference.ReferenceType = DataPilotFieldReferenceType::ITEM_DIFFERENCE; break; + case XML_percent: aReference.ReferenceType = DataPilotFieldReferenceType::ITEM_PERCENTAGE; break; + case XML_percentDiff: aReference.ReferenceType = DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE; break; + case XML_runTotal: aReference.ReferenceType = DataPilotFieldReferenceType::RUNNING_TOTAL; break; + case XML_percentOfRow: aReference.ReferenceType = DataPilotFieldReferenceType::ROW_PERCENTAGE; break; + case XML_percentOfCol: aReference.ReferenceType = DataPilotFieldReferenceType::COLUMN_PERCENTAGE; break; + case XML_percentOfTotal: aReference.ReferenceType = DataPilotFieldReferenceType::TOTAL_PERCENTAGE; break; + case XML_index: aReference.ReferenceType = DataPilotFieldReferenceType::INDEX; break; + } + if( aReference.ReferenceType != DataPilotFieldReferenceType::NONE ) + { + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( rDataField.mnBaseField ) ) + { + aReference.ReferenceField = pCacheField->getName(); + switch( rDataField.mnBaseItem ) + { + case OOX_PT_PREVIOUS_ITEM: + aReference.ReferenceItemType = DataPilotFieldReferenceItemType::PREVIOUS; + break; + case OOX_PT_NEXT_ITEM: + aReference.ReferenceItemType = DataPilotFieldReferenceItemType::NEXT; + break; + default: + aReference.ReferenceItemType = DataPilotFieldReferenceItemType::NAMED; + if( const PivotCacheItem* pCacheItem = pCacheField->getCacheItem( rDataField.mnBaseItem ) ) + aReference.ReferenceItemName = pCacheItem->getName(); + } + aPropSet.setProperty( PROP_Reference, aReference ); + } + } + } +} + +// private -------------------------------------------------------------------- + +Reference< XDataPilotField > PivotTableField::convertRowColPageField( sal_Int32 nAxis ) +{ + bool bDataLayout = mnFieldIndex == OOX_PT_DATALAYOUTFIELD; + Reference< XDataPilotField > xDPField = bDataLayout ? mrPivotTable.getDataLayoutField() : mrPivotTable.getDataPilotField( maDPFieldName ); + OSL_ENSURE( bDataLayout || (nAxis == maModel.mnAxis), "PivotTableField::convertRowColPageField - field axis mismatch" ); + + if( xDPField.is() ) + { + PropertySet aPropSet( xDPField ); + using namespace ::com::sun::star::sheet; + + // field orientation + DataPilotFieldOrientation eFieldOrient = DataPilotFieldOrientation_HIDDEN; + switch( nAxis ) + { + case XML_axisRow: eFieldOrient = DataPilotFieldOrientation_ROW; break; + case XML_axisCol: eFieldOrient = DataPilotFieldOrientation_COLUMN; break; + case XML_axisPage: eFieldOrient = DataPilotFieldOrientation_PAGE; break; + } + if( eFieldOrient != DataPilotFieldOrientation_HIDDEN ) + aPropSet.setProperty( PROP_Orientation, eFieldOrient ); + + // all other settings not for the data layout field + if( !bDataLayout ) + { + /* Field subtotal functions. Ignore the 'defaultSubtotal' flag, if + explicit functions are set. This is different behaviour between + XML (where 'defaultSubtotal' is set regardless of other + functions) and binary formats (where 'defaultSubtotal' is not + set if other functions are set). */ + ::std::vector< GeneralFunction > aSubtotals; + /* Order of subtotals is fixed in Excel. Documentation is a little + bit confused about which names to use for the count functions. + For subtotals, 'countA' means 'count all', and 'count' means + 'count numbers'. On the other hand, for the data field + aggregation function, 'count' means 'count all', and 'countNum' + means 'count numbers' (see below). */ + if( maModel.mbSumSubtotal ) aSubtotals.push_back( GeneralFunction_SUM ); + if( maModel.mbCountASubtotal ) aSubtotals.push_back( GeneralFunction_COUNT ); + if( maModel.mbAverageSubtotal ) aSubtotals.push_back( GeneralFunction_AVERAGE ); + if( maModel.mbMaxSubtotal ) aSubtotals.push_back( GeneralFunction_MAX ); + if( maModel.mbMinSubtotal ) aSubtotals.push_back( GeneralFunction_MIN ); + if( maModel.mbProductSubtotal ) aSubtotals.push_back( GeneralFunction_PRODUCT ); + if( maModel.mbCountSubtotal ) aSubtotals.push_back( GeneralFunction_COUNTNUMS ); + if( maModel.mbStdDevSubtotal ) aSubtotals.push_back( GeneralFunction_STDEV ); + if( maModel.mbStdDevPSubtotal ) aSubtotals.push_back( GeneralFunction_STDEVP ); + if( maModel.mbVarSubtotal ) aSubtotals.push_back( GeneralFunction_VAR ); + if( maModel.mbVarPSubtotal ) aSubtotals.push_back( GeneralFunction_VARP ); + // if no function is set manually, check the 'defaultSubtotal' flag + if( aSubtotals.empty() && maModel.mbDefaultSubtotal ) + aSubtotals.push_back( GeneralFunction_AUTO ); + aPropSet.setProperty( PROP_Subtotals, ContainerHelper::vectorToSequence( aSubtotals ) ); + + // layout settings + DataPilotFieldLayoutInfo aLayoutInfo; + aLayoutInfo.LayoutMode = maModel.mbOutline ? + (maModel.mbSubtotalTop ? DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_TOP : DataPilotFieldLayoutMode::OUTLINE_SUBTOTALS_BOTTOM) : + DataPilotFieldLayoutMode::TABULAR_LAYOUT; + aLayoutInfo.AddEmptyLines = maModel.mbInsertBlankRow; + aPropSet.setProperty( PROP_LayoutInfo, aLayoutInfo ); + aPropSet.setProperty( PROP_ShowEmpty, maModel.mbShowAll ); + + // auto show (OOXML3/OOBIN3 only) + if( maModel.mbAutoShow ) + { + DataPilotFieldAutoShowInfo aAutoShowInfo; + aAutoShowInfo.IsEnabled = sal_True; + aAutoShowInfo.ShowItemsMode = maModel.mbTopAutoShow ? DataPilotFieldShowItemsMode::FROM_TOP : DataPilotFieldShowItemsMode::FROM_BOTTOM; + aAutoShowInfo.ItemCount = maModel.mnAutoShowItems; + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheFieldOfDataField( maModel.mnAutoShowRankBy ) ) + aAutoShowInfo.DataField = pCacheField->getName(); + aPropSet.setProperty( PROP_AutoShowInfo, aAutoShowInfo ); + } + + // auto sort + DataPilotFieldSortInfo aSortInfo; + aSortInfo.IsAscending = maModel.mnSortType == XML_ascending; + if( (maModel.mnSortType != XML_ascending) && (maModel.mnSortType != XML_descending) ) + { + aSortInfo.Mode = DataPilotFieldSortMode::MANUAL; + } + else + { + const PivotCacheField* pCacheField = (maModel.mnSortRefField == OOX_PT_DATALAYOUTFIELD) ? + mrPivotTable.getCacheFieldOfDataField( maModel.mnSortRefItem ) : 0; + if( pCacheField ) + { + aSortInfo.Mode = DataPilotFieldSortMode::DATA; + aSortInfo.Field = pCacheField->getName(); + } + else + { + aSortInfo.Mode = DataPilotFieldSortMode::NAME; + } + } + aPropSet.setProperty( PROP_SortInfo, aSortInfo ); + + // item settings + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( mnFieldIndex ) ) try + { + Reference< XNameAccess > xDPItemsNA( xDPField->getItems(), UNO_QUERY_THROW ); + for( ItemModelVector::iterator aIt = maItems.begin(), aEnd = maItems.end(); aIt != aEnd; ++aIt ) + { + if( aIt->mnType == XML_data ) + { + if( const PivotCacheItem* pSharedItem = pCacheField->getCacheItem( aIt->mnCacheItem ) ) try + { + PropertySet aItemProp( xDPItemsNA->getByName( pSharedItem->getName() ) ); + aItemProp.setProperty( PROP_ShowDetail, aIt->mbShowDetails ); + aItemProp.setProperty( PROP_IsHidden, aIt->mbHidden ); + } + catch( Exception& ) + { + // catch every failed container access to be able to process following items + } + } + } + } + catch( Exception& ) + { + } + } + } + return xDPField; +} + +// ============================================================================ + +PTFilterModel::PTFilterModel() : + mfValue( 0.0 ), + mnField( -1 ), + mnMemPropField( -1 ), + mnType( XML_TOKEN_INVALID ), + mnEvalOrder( 0 ), + mnId( -1 ), + mnMeasureField( -1 ), + mnMeasureHier( -1 ), + mbTopFilter( true ) +{ +} + +// ---------------------------------------------------------------------------- + +PivotTableFilter::PivotTableFilter( const PivotTable& rPivotTable ) : + WorkbookHelper( rPivotTable ), + mrPivotTable( rPivotTable ) +{ +} + +void PivotTableFilter::importFilter( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + maModel.maDescription = rAttribs.getXString( XML_description, OUString() ); + maModel.maStrValue1 = rAttribs.getXString( XML_stringValue1, OUString() ); + maModel.maStrValue2 = rAttribs.getXString( XML_stringValue2, OUString() ); + maModel.mnField = rAttribs.getInteger( XML_fld, -1 ); + maModel.mnMemPropField = rAttribs.getInteger( XML_mpFld, -1 ); + maModel.mnType = rAttribs.getToken( XML_type, XML_TOKEN_INVALID ); + maModel.mnEvalOrder = rAttribs.getInteger( XML_evalOrder, 0 ); + maModel.mnId = rAttribs.getInteger( XML_id, -1 ); + maModel.mnMeasureField = rAttribs.getInteger( XML_iMeasureFld, -1 ); + maModel.mnMeasureHier = rAttribs.getInteger( XML_iMeasureHier, -1 ); +} + +void PivotTableFilter::importTop10( const AttributeList& rAttribs ) +{ + OSL_ENSURE( rAttribs.getBool( XML_percent, false ) == (maModel.mnType == XML_percent), + "PivotTableFilter::importTop10 - unexpected value of percent attribute" ); + maModel.mfValue = rAttribs.getDouble( XML_val, 0.0 ); + maModel.mbTopFilter = rAttribs.getBool( XML_top, true ); +} + +void PivotTableFilter::importPTFilter( RecordInputStream& rStrm ) +{ + sal_Int32 nType; + sal_uInt16 nFlags; + rStrm >> maModel.mnField >> maModel.mnMemPropField >> nType; + rStrm.skip( 4 ); // unused + rStrm >> maModel.mnId >> maModel.mnMeasureField >> maModel.mnMeasureHier >> nFlags; + if( getFlag( nFlags, OOBIN_PTFILTER_HASNAME ) ) + rStrm >> maModel.maName; + if( getFlag( nFlags, OOBIN_PTFILTER_HASDESCRIPTION ) ) + rStrm >> maModel.maDescription; + if( getFlag( nFlags, OOBIN_PTFILTER_HASSTRVALUE1 ) ) + rStrm >> maModel.maStrValue1; + if( getFlag( nFlags, OOBIN_PTFILTER_HASSTRVALUE2 ) ) + rStrm >> maModel.maStrValue2; + + static sal_Int32 spnTypes[] = + { + XML_unknown, + // data field top10 filter (1-3) + XML_count, XML_percent, XML_sum, + // caption filter (4-17) + XML_captionEqual, XML_captionNotEqual, + XML_captionBeginsWith, XML_captionNotBeginsWith, XML_captionEndsWith, XML_captionNotEndsWith, + XML_captionContains, XML_captionNotContains, XML_captionGreaterThan, XML_captionGreaterThanOrEqual, + XML_captionLessThan, XML_captionLessThanOrEqual, XML_captionBetween, XML_captionNotBetween, + // value filter (18-25) + XML_valueEqual, XML_valueNotEqual, XML_valueGreaterThan, XML_valueGreaterThanOrEqual, + XML_valueLessThan, XML_valueLessThanOrEqual, XML_valueBetween, XML_valueNotBetween, + // date filter (26-65) + XML_dateEqual, XML_dateOlderThan, XML_dateNewerThan, XML_dateBetween, + XML_tomorrow, XML_today, XML_yesterday, XML_nextWeek, XML_thisWeek, XML_lastWeek, + XML_nextMonth, XML_thisMonth, XML_lastMonth, XML_nextQuarter, XML_thisQuarter, XML_lastQuarter, + XML_nextYear, XML_thisYear, XML_lastYear, XML_yearToDate, XML_Q1, XML_Q2, XML_Q3, XML_Q4, + XML_M1, XML_M2, XML_M3, XML_M4, XML_M5, XML_M6, XML_M7, XML_M8, XML_M9, XML_M10, XML_M11, XML_M12, + XML_dateNotEqual, XML_dateOlderThanOrEqual, XML_dateNewerThanOrEqual, XML_dateNotBetween + }; + maModel.mnType = STATIC_ARRAY_SELECT( spnTypes, nType, XML_TOKEN_INVALID ); +} + +void PivotTableFilter::importTop10Filter( RecordInputStream& rStrm ) +{ + sal_uInt8 nFlags; + rStrm >> nFlags >> maModel.mfValue; + + OSL_ENSURE( getFlag( nFlags, OOBIN_TOP10FILTER_PERCENT ) == (maModel.mnType == XML_percent), + "PivotTableFilter::importTop10 - unexpected value of percent attribute" ); + maModel.mbTopFilter = getFlag( nFlags, OOBIN_TOP10FILTER_TOP ); +} + +void PivotTableFilter::finalizeImport() +{ + // only simple top10 filter supported + if( maModel.mnType == XML_count ) + { + PropertySet aPropSet( mrPivotTable.getDataPilotField( maModel.mnField ) ); + if( aPropSet.is() ) + { + using namespace ::com::sun::star::sheet; + DataPilotFieldAutoShowInfo aAutoShowInfo; + aAutoShowInfo.IsEnabled = sal_True; + aAutoShowInfo.ShowItemsMode = maModel.mbTopFilter ? DataPilotFieldShowItemsMode::FROM_TOP : DataPilotFieldShowItemsMode::FROM_BOTTOM; + aAutoShowInfo.ItemCount = getLimitedValue< sal_Int32, double >( maModel.mfValue, 0, SAL_MAX_INT32 ); + if( const PivotCacheField* pCacheField = mrPivotTable.getCacheFieldOfDataField( maModel.mnMeasureField ) ) + aAutoShowInfo.DataField = pCacheField->getName(); + aPropSet.setProperty( PROP_AutoShowInfo, aAutoShowInfo ); + } + } +} + +// ============================================================================ + +PTDefinitionModel::PTDefinitionModel() : + mnCacheId( -1 ), + mnDataPosition( 0 ), + mnPageWrap( 0 ), + mnIndent( 1 ), + mnChartFormat( 0 ), + mnRowFields( 0 ), + mnColFields( 0 ), + mbDataOnRows( false ), + mbShowError( false ), + mbShowMissing( true ), + mbShowItems( true ), + mbDisableFieldList( false ), + mbShowCalcMembers( true ), + mbVisualTotals( true ), + mbShowDrill( true ), + mbPrintDrill( false ), + mbEnableDrill( true ), + mbPreserveFormatting( true ), + mbPageOverThenDown( false ), + mbSubtotalHiddenItems( false ), + mbRowGrandTotals( true ), + mbColGrandTotals( true ), + mbFieldPrintTitles( false ), + mbItemPrintTitles( false ), + mbMergeItem( false ), + mbShowEmptyRow( false ), + mbShowEmptyCol( false ), + mbShowHeaders( true ), + mbFieldListSortAsc( false ), + mbCustomListSort( true ) +{ +} + +// ---------------------------------------------------------------------------- + +PTLocationModel::PTLocationModel() : + mnFirstHeaderRow( 0 ), + mnFirstDataRow( 0 ), + mnFirstDataCol( 0 ), + mnRowPageCount( 0 ), + mnColPageCount( 0 ) +{ +} + +// ---------------------------------------------------------------------------- + +PivotTable::PivotTable( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + maDataField( *this, OOX_PT_DATALAYOUTFIELD ), + mpPivotCache( 0 ) +{ +} + +void PivotTable::importPivotTableDefinition( const AttributeList& rAttribs ) +{ + maDefModel.maName = rAttribs.getXString( XML_name, OUString() ); + maDefModel.maDataCaption = rAttribs.getXString( XML_dataCaption , OUString() ); + maDefModel.maGrandTotalCaption = rAttribs.getXString( XML_grandTotalCaption, OUString() ); + maDefModel.maRowHeaderCaption = rAttribs.getXString( XML_rowHeaderCaption, OUString() ); + maDefModel.maColHeaderCaption = rAttribs.getXString( XML_colHeaderCaption, OUString() ); + maDefModel.maErrorCaption = rAttribs.getXString( XML_errorCaption, OUString() ); + maDefModel.maMissingCaption = rAttribs.getXString( XML_missingCaption, OUString() ); + maDefModel.maPageStyle = rAttribs.getXString( XML_pageStyle, OUString() ); + maDefModel.maPivotTableStyle = rAttribs.getXString( XML_pivotTableStyle, OUString() ); + maDefModel.maVacatedStyle = rAttribs.getXString( XML_vacatedStyle, OUString() ); + maDefModel.maTag = rAttribs.getXString( XML_tag, OUString() ); + maDefModel.mnCacheId = rAttribs.getInteger( XML_cacheId, -1 ); + maDefModel.mnDataPosition = rAttribs.getInteger( XML_dataPosition, 0 ); + maDefModel.mnPageWrap = rAttribs.getInteger( XML_pageWrap, 0 ); + maDefModel.mnIndent = rAttribs.getInteger( XML_indent, 1 ); + maDefModel.mnChartFormat = rAttribs.getInteger( XML_chartFormat, 0 ); + maDefModel.mbDataOnRows = rAttribs.getBool( XML_dataOnRows, false ); + maDefModel.mbShowError = rAttribs.getBool( XML_showError, false ); + maDefModel.mbShowMissing = rAttribs.getBool( XML_showMissing, true ); + maDefModel.mbShowItems = rAttribs.getBool( XML_showItems, true ); + maDefModel.mbDisableFieldList = rAttribs.getBool( XML_disableFieldList, false ); + maDefModel.mbShowCalcMembers = rAttribs.getBool( XML_showCalcMbrs, true ); + maDefModel.mbVisualTotals = rAttribs.getBool( XML_visualTotals, true ); + maDefModel.mbShowDrill = rAttribs.getBool( XML_showDrill, true ); + maDefModel.mbPrintDrill = rAttribs.getBool( XML_printDrill, false ); + maDefModel.mbEnableDrill = rAttribs.getBool( XML_enableDrill, true ); + maDefModel.mbPreserveFormatting = rAttribs.getBool( XML_preserveFormatting, true ); + maDefModel.mbPageOverThenDown = rAttribs.getBool( XML_pageOverThenDown, false ); + maDefModel.mbSubtotalHiddenItems = rAttribs.getBool( XML_subtotalHiddenItems, false ); + maDefModel.mbRowGrandTotals = rAttribs.getBool( XML_rowGrandTotals, true ); + maDefModel.mbColGrandTotals = rAttribs.getBool( XML_colGrandTotals, true ); + maDefModel.mbFieldPrintTitles = rAttribs.getBool( XML_fieldPrintTitles, false ); + maDefModel.mbItemPrintTitles = rAttribs.getBool( XML_itemPrintTitles, false ); + maDefModel.mbMergeItem = rAttribs.getBool( XML_mergeItem, false ); + maDefModel.mbShowEmptyRow = rAttribs.getBool( XML_showEmptyRow, false ); + maDefModel.mbShowEmptyCol = rAttribs.getBool( XML_showEmptyCol, false ); + maDefModel.mbShowHeaders = rAttribs.getBool( XML_showHeaders, true ); + maDefModel.mbFieldListSortAsc = rAttribs.getBool( XML_fieldListSortAscending, false ); + maDefModel.mbCustomListSort = rAttribs.getBool( XML_customListSort, true ); +} + +void PivotTable::importLocation( const AttributeList& rAttribs, sal_Int16 nSheet ) +{ + getAddressConverter().convertToCellRangeUnchecked( maLocationModel.maRange, rAttribs.getString( XML_ref, OUString() ), nSheet ); + maLocationModel.mnFirstHeaderRow = rAttribs.getInteger( XML_firstHeaderRow, 0 ); + maLocationModel.mnFirstDataRow = rAttribs.getInteger( XML_firstDataRow, 0 ); + maLocationModel.mnFirstDataCol = rAttribs.getInteger( XML_firstDataCol, 0 ); + maLocationModel.mnRowPageCount = rAttribs.getInteger( XML_rowPageCount, 0 ); + maLocationModel.mnColPageCount = rAttribs.getInteger( XML_colPageCount, 0 ); +} + +void PivotTable::importRowField( const AttributeList& rAttribs ) +{ + importField( maRowFields, rAttribs ); +} + +void PivotTable::importColField( const AttributeList& rAttribs ) +{ + importField( maColFields, rAttribs ); +} + +void PivotTable::importPageField( const AttributeList& rAttribs ) +{ + PTPageFieldModel aModel; + aModel.maName = rAttribs.getXString( XML_name, OUString() ); + aModel.mnField = rAttribs.getInteger( XML_fld, -1 ); + // specification is wrong, XML_item is not the cache item, but the field item + aModel.mnItem = rAttribs.getInteger( XML_item, OOBIN_PTPAGEFIELD_MULTIITEMS ); + maPageFields.push_back( aModel ); +} + +void PivotTable::importDataField( const AttributeList& rAttribs ) +{ + PTDataFieldModel aModel; + aModel.maName = rAttribs.getXString( XML_name, OUString() ); + aModel.mnField = rAttribs.getInteger( XML_fld, -1 ); + aModel.mnSubtotal = rAttribs.getToken( XML_subtotal, XML_sum ); + aModel.mnShowDataAs = rAttribs.getToken( XML_showDataAs, XML_normal ); + aModel.mnBaseField = rAttribs.getInteger( XML_baseField, -1 ); + aModel.mnBaseItem = rAttribs.getInteger( XML_baseItem, -1 ); + aModel.mnNumFmtId = rAttribs.getInteger( XML_numFmtId, 0 ); + maDataFields.push_back( aModel ); +} + +void PivotTable::importPTDefinition( RecordInputStream& rStrm ) +{ + sal_uInt32 nFlags1, nFlags2, nFlags3; + sal_uInt8 nDataAxis; + rStrm >> nFlags1 >> nFlags2 >> nFlags3 >> nDataAxis; + maDefModel.mnPageWrap = rStrm.readuInt8(); + rStrm.skip( 2 ); // refresh versions + rStrm >> maDefModel.mnDataPosition; + rStrm.skip( 4 ); // 2 bytes autoformat id, 2 bytes unused + rStrm >> maDefModel.mnChartFormat >> maDefModel.mnCacheId >> maDefModel.maName; + if( getFlag( nFlags2, OOBIN_PTDEF_HASDATACAPTION ) ) + rStrm >> maDefModel.maDataCaption; + if( getFlag( nFlags2, OOBIN_PTDEF_HASGRANDTOTALCAPTION ) ) + rStrm >> maDefModel.maGrandTotalCaption; + if( !getFlag( nFlags3, OOBIN_PTDEF_NOERRORCAPTION ) ) // missing flag indicates existing string + rStrm >> maDefModel.maErrorCaption; + if( !getFlag( nFlags3, OOBIN_PTDEF_NOMISSINGCAPTION ) ) // missing flag indicates existing string + rStrm >> maDefModel.maMissingCaption; + if( getFlag( nFlags2, OOBIN_PTDEF_HASPAGESTYLE ) ) + rStrm >> maDefModel.maPageStyle; + if( getFlag( nFlags2, OOBIN_PTDEF_HASPIVOTTABLESTYLE ) ) + rStrm >> maDefModel.maPivotTableStyle; + if( getFlag( nFlags2, OOBIN_PTDEF_HASVACATEDSTYLE ) ) + rStrm >> maDefModel.maVacatedStyle; + if( getFlag( nFlags2, OOBIN_PTDEF_HASTAG ) ) + rStrm >> maDefModel.maTag; + if( getFlag( nFlags3, OOBIN_PTDEF_HASCOLHEADERCAPTION ) ) // TODO: right order (col/row)? spec is unclear + rStrm >> maDefModel.maColHeaderCaption; + if( getFlag( nFlags3, OOBIN_PTDEF_HASROWHEADERCAPTION ) ) + rStrm >> maDefModel.maRowHeaderCaption; + + OSL_ENSURE( (nDataAxis == OOBIN_PTDEF_ROWAXIS) || (nDataAxis == OOBIN_PTDEF_COLAXIS), + "PivotTable::importPTDefinition - unexpected axis position for data field" ); + + maDefModel.mnIndent = extractValue< sal_uInt8 >( nFlags1, 24, 7 ); + maDefModel.mbDataOnRows = nDataAxis == OOBIN_PTDEF_ROWAXIS; + maDefModel.mbShowError = getFlag( nFlags2, OOBIN_PTDEF_SHOWERROR ); + maDefModel.mbShowMissing = getFlag( nFlags2, OOBIN_PTDEF_SHOWMISSING ); + maDefModel.mbShowItems = getFlag( nFlags1, OOBIN_PTDEF_SHOWITEMS ); + maDefModel.mbDisableFieldList = getFlag( nFlags1, OOBIN_PTDEF_DISABLEFIELDLIST ); + maDefModel.mbShowCalcMembers = !getFlag( nFlags1, OOBIN_PTDEF_HIDECALCMEMBERS ); + maDefModel.mbVisualTotals = !getFlag( nFlags1, OOBIN_PTDEF_WITHHIDDENTOTALS ); + maDefModel.mbShowDrill = !getFlag( nFlags1, OOBIN_PTDEF_HIDEDRILL ); + maDefModel.mbPrintDrill = getFlag( nFlags1, OOBIN_PTDEF_PRINTDRILL ); + maDefModel.mbEnableDrill = getFlag( nFlags2, OOBIN_PTDEF_ENABLEDRILL ); + maDefModel.mbPreserveFormatting = getFlag( nFlags2, OOBIN_PTDEF_PRESERVEFORMATTING ); + maDefModel.mbPageOverThenDown = getFlag( nFlags2, OOBIN_PTDEF_PAGEOVERTHENDOWN ); + maDefModel.mbSubtotalHiddenItems = getFlag( nFlags2, OOBIN_PTDEF_SUBTOTALHIDDENITEMS ); + maDefModel.mbRowGrandTotals = getFlag( nFlags2, OOBIN_PTDEF_ROWGRANDTOTALS ); + maDefModel.mbColGrandTotals = getFlag( nFlags2, OOBIN_PTDEF_COLGRANDTOTALS ); + maDefModel.mbFieldPrintTitles = getFlag( nFlags2, OOBIN_PTDEF_FIELDPRINTTITLES ); + maDefModel.mbItemPrintTitles = getFlag( nFlags2, OOBIN_PTDEF_ITEMPRINTTITLES ); + maDefModel.mbMergeItem = getFlag( nFlags2, OOBIN_PTDEF_MERGEITEM ); + maDefModel.mbShowEmptyRow = getFlag( nFlags2, OOBIN_PTDEF_SHOWEMPTYROW ); + maDefModel.mbShowEmptyCol = getFlag( nFlags2, OOBIN_PTDEF_SHOWEMPTYCOL ); + maDefModel.mbShowHeaders = !getFlag( nFlags1, OOBIN_PTDEF_HIDEHEADERS ); + maDefModel.mbFieldListSortAsc = getFlag( nFlags3, OOBIN_PTDEF_FIELDLISTSORTASC ); + maDefModel.mbCustomListSort = !getFlag( nFlags3, OOBIN_PTDEF_NOCUSTOMLISTSORT ); +} + +void PivotTable::importPTLocation( RecordInputStream& rStrm, sal_Int16 nSheet ) +{ + BinRange aBinRange; + rStrm >> aBinRange >> maLocationModel.mnFirstHeaderRow + >> maLocationModel.mnFirstDataRow >> maLocationModel.mnFirstDataCol + >> maLocationModel.mnRowPageCount >> maLocationModel.mnColPageCount; + getAddressConverter().convertToCellRangeUnchecked( maLocationModel.maRange, aBinRange, nSheet ); +} + +void PivotTable::importPTRowFields( RecordInputStream& rStrm ) +{ + importFields( maRowFields, rStrm ); +} + +void PivotTable::importPTColFields( RecordInputStream& rStrm ) +{ + importFields( maColFields, rStrm ); +} + +void PivotTable::importPTPageField( RecordInputStream& rStrm ) +{ + PTPageFieldModel aModel; + sal_uInt8 nFlags; + rStrm >> aModel.mnField >> aModel.mnItem; + rStrm.skip( 4 ); // hierarchy + rStrm >> nFlags; + if( getFlag( nFlags, OOBIN_PTPAGEFIELD_HASNAME ) ) + rStrm >> aModel.maName; + maPageFields.push_back( aModel ); +} + +void PivotTable::importPTDataField( RecordInputStream& rStrm ) +{ + PTDataFieldModel aModel; + sal_Int32 nSubtotal, nShowDataAs; + sal_uInt8 nHasName; + rStrm >> aModel.mnField >> nSubtotal >> nShowDataAs >> aModel.mnBaseField >> aModel.mnBaseItem >> aModel.mnNumFmtId >> nHasName; + if( nHasName == 1 ) + rStrm >> aModel.maName; + aModel.setBinSubtotal( nSubtotal ); + aModel.setBinShowDataAs( nShowDataAs ); + maDataFields.push_back( aModel ); +} + +void PivotTable::importPTDefinition( BiffInputStream& rStrm, sal_Int16 nSheet ) +{ + BinRange aBinRange; + sal_uInt16 nFlags, nTabNameLen, nDataNameLen; + rStrm >> aBinRange; + maLocationModel.mnFirstHeaderRow = rStrm.readuInt16(); + maLocationModel.mnFirstDataRow = rStrm.readuInt16(); + maLocationModel.mnFirstDataCol = rStrm.readuInt16(); + maDefModel.mnCacheId = rStrm.readuInt16(); + rStrm.skip( 2 ); // unused + maDefModel.mbDataOnRows = rStrm.readuInt16() == BIFF_PTDEF_ROWAXIS; + maDefModel.mnDataPosition = rStrm.readInt16(); + rStrm.skip( 2 ); // number of fields + rStrm >> maDefModel.mnRowFields >> maDefModel.mnColFields; + rStrm.skip( 8 ); // number of page fields, data fields, data rows, data columns + rStrm >> nFlags; + maDefModel.mnChartFormat = rStrm.readuInt16(); + rStrm >> nTabNameLen >> nDataNameLen; + maDefModel.maName = lclReadPivotString( *this, rStrm, nTabNameLen ); + maDefModel.maDataCaption = lclReadPivotString( *this, rStrm, nDataNameLen ); + + maDefModel.mbRowGrandTotals = getFlag( nFlags, BIFF_PTDEF_ROWGRANDTOTALS ); + maDefModel.mbColGrandTotals = getFlag( nFlags, BIFF_PTDEF_COLGRANDTOTALS ); + + getAddressConverter().convertToCellRangeUnchecked( maLocationModel.maRange, aBinRange, nSheet ); +} + +void PivotTable::importPTDefinition2( BiffInputStream& rStrm ) +{ + if( getBiff() == BIFF8 ) + { + sal_uInt16 nErrCaptLen, nMissCaptLen, nTagLen, nPageStyleLen, nTabStyleLen, nVacStyleLen; + sal_uInt32 nFlags; + rStrm.skip( 2 ); // number of formatting records + rStrm >> nErrCaptLen >> nMissCaptLen >> nTagLen; + rStrm.skip( 6 ); // number of selection records, page rows, page columns + rStrm >> nFlags >> nPageStyleLen >> nTabStyleLen >> nVacStyleLen; + maDefModel.maErrorCaption = lclReadPivotString( *this, rStrm, nErrCaptLen ); + maDefModel.maMissingCaption = lclReadPivotString( *this, rStrm, nMissCaptLen ); + maDefModel.maTag = lclReadPivotString( *this, rStrm, nTagLen ); + maDefModel.maPageStyle = lclReadPivotString( *this, rStrm, nPageStyleLen ); + maDefModel.maPivotTableStyle = lclReadPivotString( *this, rStrm, nTabStyleLen ); + maDefModel.maVacatedStyle = lclReadPivotString( *this, rStrm, nVacStyleLen ); + + maDefModel.mbShowError = getFlag( nFlags, BIFF_PTDEF2_SHOWERROR ); + maDefModel.mbShowMissing = getFlag( nFlags, BIFF_PTDEF2_SHOWMISSING ); + maDefModel.mbEnableDrill = getFlag( nFlags, BIFF_PTDE2F_ENABLEDRILL ); + maDefModel.mbPreserveFormatting = getFlag( nFlags, BIFF_PTDEF2_PRESERVEFORMATTING ); + maDefModel.mbPageOverThenDown = getFlag( nFlags, BIFF_PTDEF2_PAGEOVERTHENDOWN ); + maDefModel.mbSubtotalHiddenItems = getFlag( nFlags, BIFF_PTDEF2_SUBTOTALHIDDENITEMS ); + maDefModel.mbMergeItem = getFlag( nFlags, BIFF_PTDEF2_MERGEITEM ); + } +} + +void PivotTable::importPTRowColFields( BiffInputStream& rStrm ) +{ + // first PTROWCOLFIELDS record contains row fields unless there are no row fields + if( (maDefModel.mnRowFields > 0) && maRowFields.empty() ) + importFields( maRowFields, rStrm, maDefModel.mnRowFields ); + else if( (maDefModel.mnColFields > 0) && maColFields.empty() ) + importFields( maColFields, rStrm, maDefModel.mnColFields ); +} + +void PivotTable::importPTPageFields( BiffInputStream& rStrm ) +{ + while( rStrm.getRemaining() >= 6 ) + { + PTPageFieldModel aModel; + sal_Int16 nField, nItem; + rStrm >> nField >> nItem; + rStrm.skip( 2 ); // dropdown object ID + aModel.mnField = nField; + aModel.mnItem = (nItem == BIFF_PTPAGEFIELDS_ALLITEMS) ? OOBIN_PTPAGEFIELD_MULTIITEMS : nItem; + maPageFields.push_back( aModel ); + } +} + +void PivotTable::importPTDataField( BiffInputStream& rStrm ) +{ + PTDataFieldModel aModel; + sal_Int16 nField, nBaseField, nBaseItem; + sal_uInt16 nSubtotal, nShowDataAs, nNumFmt, nNameLen; + rStrm >> nField >> nSubtotal >> nShowDataAs >> nBaseField >> nBaseItem >> nNumFmt >> nNameLen; + aModel.maName = lclReadPivotString( *this, rStrm, nNameLen ); + + aModel.mnField = nField; + aModel.setBinSubtotal( nSubtotal ); + aModel.setBinShowDataAs( nShowDataAs ); + aModel.mnBaseField = nBaseField; + switch( nBaseItem ) + { + case BIFF_PTDATAFIELD_PREVIOUS: aModel.mnBaseItem = OOX_PT_PREVIOUS_ITEM; break; + case BIFF_PTDATAFIELD_NEXT: aModel.mnBaseItem = OOX_PT_NEXT_ITEM; break; + default: aModel.mnBaseItem = nBaseItem; + } + aModel.mnNumFmtId = nNumFmt; + + maDataFields.push_back( aModel ); +} + +PivotTableField& PivotTable::createTableField() +{ + sal_Int32 nFieldIndex = static_cast< sal_Int32 >( maFields.size() ); + PivotTableFieldVector::value_type xTableField( new PivotTableField( *this, nFieldIndex ) ); + maFields.push_back( xTableField ); + return *xTableField; +} + +PivotTableFilter& PivotTable::createTableFilter() +{ + PivotTableFilterVector::value_type xTableFilter( new PivotTableFilter( *this ) ); + maFilters.push_back( xTableFilter ); + return *xTableFilter; +} + +void PivotTable::finalizeImport() +{ + if( getAddressConverter().validateCellRange( maLocationModel.maRange, true, true ) ) + { + mpPivotCache = getPivotCaches().importPivotCacheFragment( maDefModel.mnCacheId ); + if( mpPivotCache && mpPivotCache->isValidDataSource() && (maDefModel.maName.getLength() > 0) ) + { + // clear destination area of the original pivot table + try + { + Reference< XSheetOperation > xSheetOp( getCellRangeFromDoc( maLocationModel.maRange ), UNO_QUERY_THROW ); + using namespace ::com::sun::star::sheet::CellFlags; + xSheetOp->clearContents( VALUE | DATETIME | STRING | FORMULA | HARDATTR | STYLES | EDITATTR | FORMATTED ); + } + catch( Exception& ) + { + } + + try + { + // create a new data pilot descriptor based on the source data + Reference< XDataPilotTablesSupplier > xDPTablesSupp( getSheetFromDoc( maLocationModel.maRange.Sheet ), UNO_QUERY_THROW ); + Reference< XDataPilotTables > xDPTables( xDPTablesSupp->getDataPilotTables(), UNO_SET_THROW ); + mxDPDescriptor.set( xDPTables->createDataPilotDescriptor(), UNO_SET_THROW ); + mxDPDescriptor->setSourceRange( mpPivotCache->getSourceRange() ); + mxDPDescriptor->setTag( maDefModel.maTag ); + + // global data pilot properties + PropertySet aDescProp( mxDPDescriptor ); + aDescProp.setProperty( PROP_ColumnGrand, maDefModel.mbColGrandTotals ); + aDescProp.setProperty( PROP_RowGrand, maDefModel.mbRowGrandTotals ); + aDescProp.setProperty( PROP_ShowFilterButton, false ); + aDescProp.setProperty( PROP_DrillDownOnDoubleClick, maDefModel.mbEnableDrill ); + + // finalize all fields, this finds field names and creates grouping fields + maFields.forEachMem( &PivotTableField::finalizeImport, ::boost::cref( mxDPDescriptor ) ); + + // all row fields + for( IndexVector::iterator aIt = maRowFields.begin(), aEnd = maRowFields.end(); aIt != aEnd; ++aIt ) + if( PivotTableField* pField = getTableField( *aIt ) ) + pField->convertRowField(); + + // all column fields + for( IndexVector::iterator aIt = maColFields.begin(), aEnd = maColFields.end(); aIt != aEnd; ++aIt ) + if( PivotTableField* pField = getTableField( *aIt ) ) + pField->convertColField(); + + // all page fields + for( PageFieldVector::iterator aIt = maPageFields.begin(), aEnd = maPageFields.end(); aIt != aEnd; ++aIt ) + if( PivotTableField* pField = getTableField( aIt->mnField ) ) + pField->convertPageField( *aIt ); + + // all hidden fields + ::std::set< sal_Int32 > aVisFields; + aVisFields.insert( maRowFields.begin(), maRowFields.end() ); + aVisFields.insert( maColFields.begin(), maColFields.end() ); + for( PageFieldVector::iterator aIt = maPageFields.begin(), aEnd = maPageFields.end(); aIt != aEnd; ++aIt ) + aVisFields.insert( aIt->mnField ); + for( PivotTableFieldVector::iterator aBeg = maFields.begin(), aIt = aBeg, aEnd = maFields.end(); aIt != aEnd; ++aIt ) + if( aVisFields.count( static_cast< sal_Int32 >( aIt - aBeg ) ) == 0 ) + (*aIt)->convertHiddenField(); + + // all data fields + for( DataFieldVector::iterator aIt = maDataFields.begin(), aEnd = maDataFields.end(); aIt != aEnd; ++aIt ) + if( PivotTableField* pField = getTableField( aIt->mnField ) ) + pField->convertDataField( *aIt ); + + // filters + maFilters.forEachMem( &PivotTableFilter::finalizeImport ); + + // calculate base position of table + CellAddress aPos( maLocationModel.maRange.Sheet, maLocationModel.maRange.StartColumn, maLocationModel.maRange.StartRow ); + /* If page fields exist, include them into the destination + area (they are excluded in Excel). Add an extra blank row. */ + if( !maPageFields.empty() ) + aPos.Row = ::std::max< sal_Int32 >( static_cast< sal_Int32 >( aPos.Row - maPageFields.size() - 1 ), 0 ); + + // insert the DataPilot table into the sheet + xDPTables->insertNewByName( maDefModel.maName, aPos, mxDPDescriptor ); + } + catch( Exception& ) + { + OSL_ENSURE( false, "PivotTable::finalizeImport - exception while creating the DataPilot table" ); + } + } + } +} + +void PivotTable::finalizeDateGroupingImport( const Reference< XDataPilotField >& rxBaseDPField, sal_Int32 nBaseFieldIdx ) +{ + // process all fields, there is no chaining information in the cache fields + maFields.forEachMem( &PivotTableField::finalizeDateGroupingImport, ::boost::cref( rxBaseDPField ), nBaseFieldIdx ); +} + +void PivotTable::finalizeParentGroupingImport( const Reference< XDataPilotField >& rxBaseDPField, + const PivotCacheField& rBaseCacheField, PivotCacheGroupItemVector& orItemNames ) +{ + // try to create parent group fields that group the items of the passed base field + if( PivotTableField* pParentTableField = maFields.get( rBaseCacheField.getParentGroupField() ).get() ) + pParentTableField->finalizeParentGroupingImport( rxBaseDPField, orItemNames ); +} + +Reference< XDataPilotField > PivotTable::getDataPilotField( const OUString& rFieldName ) const +{ + Reference< XDataPilotField > xDPField; + if( (rFieldName.getLength() > 0) && mxDPDescriptor.is() ) try + { + Reference< XNameAccess > xDPFieldsNA( mxDPDescriptor->getDataPilotFields(), UNO_QUERY_THROW ); + xDPField.set( xDPFieldsNA->getByName( rFieldName ), UNO_QUERY ); + } + catch( Exception& ) + { + } + return xDPField; +} + +Reference< XDataPilotField > PivotTable::getDataPilotField( sal_Int32 nFieldIdx ) const +{ + Reference< XDataPilotField > xDPField; + if( const PivotTableField* pTableField = maFields.get( nFieldIdx ).get() ) + xDPField = getDataPilotField( pTableField->getDPFieldName() ); + return xDPField; +} + +Reference< XDataPilotField > PivotTable::getDataLayoutField() const +{ + Reference< XDataPilotField > xDPField; + try + { + Reference< XDataPilotDataLayoutFieldSupplier > xDPDataFieldSupp( mxDPDescriptor, UNO_QUERY_THROW ); + xDPField = xDPDataFieldSupp->getDataLayoutField(); + } + catch( Exception& ) + { + } + return xDPField; +} + +const PivotCacheField* PivotTable::getCacheField( sal_Int32 nFieldIdx ) const +{ + return mpPivotCache ? mpPivotCache->getCacheField( nFieldIdx ) : 0; +} + +const PivotCacheField* PivotTable::getCacheFieldOfDataField( sal_Int32 nDataItemIdx ) const +{ + const PTDataFieldModel* pDataField = ContainerHelper::getVectorElement( maDataFields, nDataItemIdx ); + return pDataField ? getCacheField( pDataField->mnField ) : 0; +} + +sal_Int32 PivotTable::getCacheDatabaseIndex( sal_Int32 nFieldIdx ) const +{ + return mpPivotCache ? mpPivotCache->getCacheDatabaseIndex( nFieldIdx ) : -1; +} + +// private -------------------------------------------------------------------- + +PivotTableField* PivotTable::getTableField( sal_Int32 nFieldIdx ) +{ + return (nFieldIdx == OOX_PT_DATALAYOUTFIELD) ? &maDataField : maFields.get( nFieldIdx ).get(); +} + +void PivotTable::importField( IndexVector& orFields, const AttributeList& rAttribs ) +{ + orFields.push_back( rAttribs.getInteger( XML_x, -1 ) ); +} + +void PivotTable::importFields( IndexVector& orFields, RecordInputStream& rStrm ) +{ + OSL_ENSURE( orFields.empty(), "PivotTable::importFields - multiple record instances" ); + orFields.clear(); + sal_Int32 nCount = rStrm.readInt32(); + OSL_ENSURE( 4 * nCount == rStrm.getRemaining(), "PivotTable::importFields - invalid field count" ); + nCount = static_cast< sal_Int32 >( rStrm.getRemaining() / 4 ); + for( sal_Int32 nIdx = 0; nIdx < nCount; ++nIdx ) + orFields.push_back( rStrm.readInt32() ); +} + +void PivotTable::importFields( IndexVector& orFields, BiffInputStream& rStrm, sal_Int32 nCount ) +{ + OSL_ENSURE( orFields.empty(), "PivotTable::importFields - multiple record instances" ); + orFields.clear(); + OSL_ENSURE( 2 * nCount == rStrm.getRemaining(), "PivotTable::importFields - invalid field count" ); + nCount = static_cast< sal_Int32 >( rStrm.getRemaining() / 2 ); + for( sal_Int32 nIdx = 0; nIdx < nCount; ++nIdx ) + orFields.push_back( rStrm.readInt16() ); +} + +// ============================================================================ + +PivotTableBuffer::PivotTableBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +PivotTable& PivotTableBuffer::createPivotTable() +{ + PivotTableVector::value_type xTable( new PivotTable( *this ) ); + maTables.push_back( xTable ); + return *xTable; +} + +void PivotTableBuffer::finalizeImport() +{ + maTables.forEachMem( &PivotTable::finalizeImport ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/pivottablefragment.cxx b/oox/source/xls/pivottablefragment.cxx new file mode 100644 index 000000000000..9b34e1bda593 --- /dev/null +++ b/oox/source/xls/pivottablefragment.cxx @@ -0,0 +1,317 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/pivottablefragment.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/pivottablebuffer.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxPivotTableFieldContext::OoxPivotTableFieldContext( OoxWorksheetFragmentBase& rFragment, PivotTableField& rTableField ) : + OoxWorksheetContextBase( rFragment ), + mrTableField( rTableField ) +{ +} + +ContextHandlerRef OoxPivotTableFieldContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( pivotField ): + switch( nElement ) + { + case XLS_TOKEN( items ): return this; + case XLS_TOKEN( autoSortScope ): return this; + } + break; + case XLS_TOKEN( items ): + if( nElement == XLS_TOKEN( item ) ) mrTableField.importItem( rAttribs ); + break; + case XLS_TOKEN( autoSortScope ): + if( nElement == XLS_TOKEN( pivotArea ) ) return this; + break; + case XLS_TOKEN( pivotArea ): + if( nElement == XLS_TOKEN( references ) ) return this; + break; + case XLS_TOKEN( references ): + if( nElement == XLS_TOKEN( reference ) ) { mrTableField.importReference( rAttribs ); return this; } + break; + case XLS_TOKEN( reference ): + if( nElement == XLS_TOKEN( x ) ) mrTableField.importReferenceItem( rAttribs ); + break; + } + return 0; +} + +void OoxPivotTableFieldContext::onStartElement( const AttributeList& rAttribs ) +{ + if( isRootElement() ) + mrTableField.importPivotField( rAttribs ); +} + +ContextHandlerRef OoxPivotTableFieldContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_PTFIELD: + switch( nRecId ) + { + case OOBIN_ID_PTFITEMS: return this; + case OOBIN_ID_AUTOSORTSCOPE: return this; + } + break; + case OOBIN_ID_PTFITEMS: + if( nRecId == OOBIN_ID_PTFITEM ) mrTableField.importPTFItem( rStrm ); + break; + case OOBIN_ID_AUTOSORTSCOPE: + if( nRecId == OOBIN_ID_PIVOTAREA ) return this; + break; + case OOBIN_ID_PIVOTAREA: + if( nRecId == OOBIN_ID_PTREFERENCES ) return this; + break; + case OOBIN_ID_PTREFERENCES: + if( nRecId == OOBIN_ID_PTREFERENCE ) { mrTableField.importPTReference( rStrm ); return this; } + break; + case OOBIN_ID_PTREFERENCE: + if( nRecId == OOBIN_ID_PTREFERENCEITEM ) mrTableField.importPTReferenceItem( rStrm ); + break; + } + return 0; +} + +void OoxPivotTableFieldContext::onStartRecord( RecordInputStream& rStrm ) +{ + if( isRootElement() ) + mrTableField.importPTField( rStrm ); +} + +// ============================================================================ + +OoxPivotTableFilterContext::OoxPivotTableFilterContext( OoxWorksheetFragmentBase& rFragment, PivotTableFilter& rTableFilter ) : + OoxWorksheetContextBase( rFragment ), + mrTableFilter( rTableFilter ) +{ +} + +ContextHandlerRef OoxPivotTableFilterContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( filter ): + if( nElement == XLS_TOKEN( autoFilter ) ) return this; + break; + case XLS_TOKEN( autoFilter ): + if( nElement == XLS_TOKEN( filterColumn ) ) return this; + break; + case XLS_TOKEN( filterColumn ): + if( nElement == XLS_TOKEN( top10 ) ) mrTableFilter.importTop10( rAttribs ); + break; + } + return 0; +} + +void OoxPivotTableFilterContext::onStartElement( const AttributeList& rAttribs ) +{ + if( isRootElement() ) + mrTableFilter.importFilter( rAttribs ); +} + +ContextHandlerRef OoxPivotTableFilterContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_PTFILTER: + if( nRecId == OOBIN_ID_AUTOFILTER ) return this; + break; + case OOBIN_ID_AUTOFILTER: + if( nRecId == OOBIN_ID_FILTERCOLUMN ) return this; + break; + case OOBIN_ID_FILTERCOLUMN: + if( nRecId == OOBIN_ID_TOP10FILTER ) mrTableFilter.importTop10Filter( rStrm ); + break; + } + return 0; +} + +void OoxPivotTableFilterContext::onStartRecord( RecordInputStream& rStrm ) +{ + if( isRootElement() ) + mrTableFilter.importPTFilter( rStrm ); +} + +// ============================================================================ + +OoxPivotTableFragment::OoxPivotTableFragment( const WorksheetHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath ), + mrPivotTable( rHelper.getPivotTables().createPivotTable() ) +{ +} + +ContextHandlerRef OoxPivotTableFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( pivotTableDefinition ) ) { mrPivotTable.importPivotTableDefinition( rAttribs ); return this; } + break; + + case XLS_TOKEN( pivotTableDefinition ): + switch( nElement ) + { + case XLS_TOKEN( location ): mrPivotTable.importLocation( rAttribs, getSheetIndex() ); break; + case XLS_TOKEN( pivotFields ): return this; + case XLS_TOKEN( rowFields ): return this; + case XLS_TOKEN( colFields ): return this; + case XLS_TOKEN( pageFields ): return this; + case XLS_TOKEN( dataFields ): return this; + case XLS_TOKEN( filters ): return this; + } + break; + + case XLS_TOKEN( pivotFields ): + if( nElement == XLS_TOKEN( pivotField ) ) return new OoxPivotTableFieldContext( *this, mrPivotTable.createTableField() ); + break; + case XLS_TOKEN( rowFields ): + if( nElement == XLS_TOKEN( field ) ) mrPivotTable.importRowField( rAttribs ); + break; + case XLS_TOKEN( colFields ): + if( nElement == XLS_TOKEN( field ) ) mrPivotTable.importColField( rAttribs ); + break; + case XLS_TOKEN( pageFields ): + if( nElement == XLS_TOKEN( pageField ) ) mrPivotTable.importPageField( rAttribs ); + break; + case XLS_TOKEN( dataFields ): + if( nElement == XLS_TOKEN( dataField ) ) mrPivotTable.importDataField( rAttribs ); + break; + case XLS_TOKEN( filters ): + if( nElement == XLS_TOKEN( filter ) ) return new OoxPivotTableFilterContext( *this, mrPivotTable.createTableFilter() ); + break; + } + return 0; +} + +ContextHandlerRef OoxPivotTableFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_PTDEFINITION ) { mrPivotTable.importPTDefinition( rStrm ); return this; } + break; + + case OOBIN_ID_PTDEFINITION: + switch( nRecId ) + { + case OOBIN_ID_PTLOCATION: mrPivotTable.importPTLocation( rStrm, getSheetIndex() ); break; + case OOBIN_ID_PTFIELDS: return this; + case OOBIN_ID_PTROWFIELDS: mrPivotTable.importPTRowFields( rStrm ); break; + case OOBIN_ID_PTCOLFIELDS: mrPivotTable.importPTColFields( rStrm ); break; + case OOBIN_ID_PTPAGEFIELDS: return this; + case OOBIN_ID_PTDATAFIELDS: return this; + case OOBIN_ID_PTFILTERS: return this; + } + break; + + case OOBIN_ID_PTFIELDS: + if( nRecId == OOBIN_ID_PTFIELD ) return new OoxPivotTableFieldContext( *this, mrPivotTable.createTableField() ); + break; + case OOBIN_ID_PTPAGEFIELDS: + if( nRecId == OOBIN_ID_PTPAGEFIELD ) mrPivotTable.importPTPageField( rStrm ); + break; + case OOBIN_ID_PTDATAFIELDS: + if( nRecId == OOBIN_ID_PTDATAFIELD ) mrPivotTable.importPTDataField( rStrm ); + break; + case OOBIN_ID_PTFILTERS: + if( nRecId == OOBIN_ID_PTFILTER ) return new OoxPivotTableFilterContext( *this, mrPivotTable.createTableFilter() ); + break; + } + return 0; +} + +const RecordInfo* OoxPivotTableFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_AUTOFILTER, OOBIN_ID_AUTOFILTER + 1 }, + { OOBIN_ID_AUTOSORTSCOPE, OOBIN_ID_AUTOSORTSCOPE + 1 }, + { OOBIN_ID_FILTERCOLUMN, OOBIN_ID_FILTERCOLUMN + 1 }, + { OOBIN_ID_PIVOTAREA, OOBIN_ID_PIVOTAREA + 1 }, + { OOBIN_ID_PTCOLFIELDS, OOBIN_ID_PTCOLFIELDS + 1 }, + { OOBIN_ID_PTDATAFIELD, OOBIN_ID_PTDATAFIELD + 1 }, + { OOBIN_ID_PTDATAFIELDS, OOBIN_ID_PTDATAFIELDS + 1 }, + { OOBIN_ID_PTDEFINITION, OOBIN_ID_PTDEFINITION + 35 }, + { OOBIN_ID_PTFIELD, OOBIN_ID_PTFIELD + 1 }, + { OOBIN_ID_PTFIELDS, OOBIN_ID_PTFIELDS + 1 }, + { OOBIN_ID_PTFILTER, OOBIN_ID_PTFILTER + 1 }, + { OOBIN_ID_PTFILTERS, OOBIN_ID_PTFILTERS + 1 }, + { OOBIN_ID_PTFITEM, OOBIN_ID_PTFITEM - 1 }, + { OOBIN_ID_PTFITEMS, OOBIN_ID_PTFITEMS + 1 }, + { OOBIN_ID_PTLOCATION, OOBIN_ID_PTLOCATION - 1 }, + { OOBIN_ID_PTPAGEFIELD, OOBIN_ID_PTPAGEFIELD + 1 }, + { OOBIN_ID_PTPAGEFIELDS, OOBIN_ID_PTPAGEFIELDS + 1 }, + { OOBIN_ID_PTREFERENCE, OOBIN_ID_PTREFERENCE + 1 }, + { OOBIN_ID_PTREFERENCEITEM, OOBIN_ID_PTREFERENCEITEM + 1 }, + { OOBIN_ID_PTREFERENCES, OOBIN_ID_PTREFERENCES + 1 }, + { OOBIN_ID_PTROWFIELDS, OOBIN_ID_PTROWFIELDS + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +// ============================================================================ +// ============================================================================ + +BiffPivotTableContext::BiffPivotTableContext( const BiffWorksheetFragmentBase& rFragment, PivotTable& rPivotTable ) : + BiffWorksheetContextBase( rFragment ), + mrPivotTable( rPivotTable ) +{ +} + +void BiffPivotTableContext::importRecord() +{ + switch( mrStrm.getRecId() ) + { + case BIFF_ID_PTDEFINITION: mrPivotTable.importPTDefinition( mrStrm, getSheetIndex() ); break; + case BIFF_ID_PTDEFINITION2: mrPivotTable.importPTDefinition2( mrStrm ); break; + case BIFF_ID_PTFIELD: mrPivotTable.createTableField().importPTField( mrStrm ); break; + case BIFF_ID_PTROWCOLFIELDS: mrPivotTable.importPTRowColFields( mrStrm ); break; + case BIFF_ID_PTPAGEFIELDS: mrPivotTable.importPTPageFields( mrStrm ); break; + case BIFF_ID_PTDATAFIELD: mrPivotTable.importPTDataField( mrStrm ); break; + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/querytablefragment.cxx b/oox/source/xls/querytablefragment.cxx new file mode 100644 index 000000000000..303852b8492b --- /dev/null +++ b/oox/source/xls/querytablefragment.cxx @@ -0,0 +1,56 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/querytablefragment.hxx" +#include "oox/xls/webquerybuffer.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +OoxQueryTableFragment::OoxQueryTableFragment( + const WorkbookHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ) +{ +} + +ContextHandlerRef OoxQueryTableFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( queryTable ) ) getWebQueries().importQueryTable( rAttribs ); + break; + } + return 0; +} + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/richstring.cxx b/oox/source/xls/richstring.cxx new file mode 100644 index 000000000000..4e82b1e696f0 --- /dev/null +++ b/oox/source/xls/richstring.cxx @@ -0,0 +1,613 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/richstring.hxx" +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/text/XText.hpp> +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OString; +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::text::XText; +using ::com::sun::star::text::XTextRange; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt8 OOBIN_STRINGFLAG_FONTS = 0x01; +const sal_uInt8 OOBIN_STRINGFLAG_PHONETICS = 0x02; + +} // namespace + +// ============================================================================ + +RichStringPortion::RichStringPortion( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mnFontId( -1 ) +{ +} + +void RichStringPortion::setText( const OUString& rText ) +{ + maText = rText; +} + +FontRef RichStringPortion::createFont() +{ + mxFont.reset( new Font( *this, false ) ); + return mxFont; +} + +void RichStringPortion::setFontId( sal_Int32 nFontId ) +{ + mnFontId = nFontId; +} + +void RichStringPortion::finalizeImport() +{ + if( mxFont.get() ) + mxFont->finalizeImport(); + else if( mnFontId >= 0 ) + mxFont = getStyles().getFont( mnFontId ); +} + +void RichStringPortion::convert( const Reference< XText >& rxText, sal_Int32 nXfId ) +{ + Reference< XTextRange > xRange = rxText->getEnd(); + xRange->setString( maText ); + if( mxFont.get() ) + { + PropertySet aPropSet( xRange ); + mxFont->writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT ); + } + if( const Font* pFont = getStyles().getFontFromCellXf( nXfId ).get() ) + { + if( pFont->needsRichTextFormat() ) + { + PropertySet aPropSet( xRange ); + pFont->writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT ); + } + } +} + +// ---------------------------------------------------------------------------- + +void FontPortionModel::read( RecordInputStream& rStrm ) +{ + mnPos = rStrm.readuInt16(); + mnFontId = rStrm.readuInt16(); +} + +void FontPortionModel::read( BiffInputStream& rStrm, BiffFontPortionMode eMode ) +{ + switch( eMode ) + { + case BIFF_FONTPORTION_8BIT: + mnPos = rStrm.readuInt8(); + mnFontId = rStrm.readuInt8(); + break; + case BIFF_FONTPORTION_16BIT: + mnPos = rStrm.readuInt16(); + mnFontId = rStrm.readuInt16(); + break; + case BIFF_FONTPORTION_OBJ: + mnPos = rStrm.readuInt16(); + mnFontId = rStrm.readuInt16(); + rStrm.skip( 4 ); + break; + } +} + +// ---------------------------------------------------------------------------- + +void FontPortionModelList::appendPortion( const FontPortionModel& rPortion ) +{ + // #i33341# real life -- same character index may occur several times + OSL_ENSURE( empty() || (back().mnPos <= rPortion.mnPos), "FontPortionModelList::appendPortion - wrong char order" ); + if( empty() || (back().mnPos < rPortion.mnPos) ) + push_back( rPortion ); + else + back().mnFontId = rPortion.mnFontId; +} + +void FontPortionModelList::importPortions( RecordInputStream& rStrm ) +{ + sal_Int32 nCount = rStrm.readInt32(); + clear(); + if( nCount > 0 ) + { + reserve( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 4 ) ); + /* #i33341# real life -- same character index may occur several times + -> use appendPortion() to validate string position. */ + FontPortionModel aPortion; + for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex ) + { + aPortion.read( rStrm ); + appendPortion( aPortion ); + } + } +} + +void FontPortionModelList::importPortions( BiffInputStream& rStrm, sal_uInt16 nCount, BiffFontPortionMode eMode ) +{ + clear(); + reserve( nCount ); + /* #i33341# real life -- same character index may occur several times + -> use appendPortion() to validate string position. */ + FontPortionModel aPortion; + for( sal_uInt16 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex ) + { + aPortion.read( rStrm, eMode ); + appendPortion( aPortion ); + } +} + +void FontPortionModelList::importPortions( BiffInputStream& rStrm, bool b16Bit ) +{ + sal_uInt16 nCount = b16Bit ? rStrm.readuInt16() : rStrm.readuInt8(); + importPortions( rStrm, nCount, b16Bit ? BIFF_FONTPORTION_16BIT : BIFF_FONTPORTION_8BIT ); +} + +// ============================================================================ + +PhoneticDataModel::PhoneticDataModel() : + mnFontId( -1 ), + mnType( XML_fullwidthKatakana ), + mnAlignment( XML_left ) +{ +} + +void PhoneticDataModel::setBinData( sal_Int32 nType, sal_Int32 nAlignment ) +{ + static const sal_Int32 spnTypeIds[] = { XML_halfwidthKatakana, XML_fullwidthKatakana, XML_hiragana, XML_noConversion }; + mnType = STATIC_ARRAY_SELECT( spnTypeIds, nType, XML_fullwidthKatakana ); + + static const sal_Int32 spnAlignments[] = { XML_noControl, XML_left, XML_center, XML_distributed }; + mnAlignment = STATIC_ARRAY_SELECT( spnAlignments, nAlignment, XML_left ); +} + +// ---------------------------------------------------------------------------- + +PhoneticSettings::PhoneticSettings( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void PhoneticSettings::importPhoneticPr( const AttributeList& rAttribs ) +{ + maModel.mnFontId = rAttribs.getInteger( XML_fontId, -1 ); + maModel.mnType = rAttribs.getToken( XML_type, XML_fullwidthKatakana ); + maModel.mnAlignment = rAttribs.getToken( XML_alignment, XML_left ); +} + +void PhoneticSettings::importPhoneticPr( RecordInputStream& rStrm ) +{ + sal_uInt16 nFontId; + sal_Int32 nType, nAlignment; + rStrm >> nFontId >> nType >> nAlignment; + maModel.mnFontId = nFontId; + maModel.setBinData( nType, nAlignment ); +} + +void PhoneticSettings::importPhoneticPr( BiffInputStream& rStrm ) +{ + sal_uInt16 nFontId, nFlags; + rStrm >> nFontId >> nFlags; + maModel.mnFontId = nFontId; + maModel.setBinData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) ); + // following: range list with cells showing phonetic text +} + +void PhoneticSettings::importStringData( RecordInputStream& rStrm ) +{ + sal_uInt16 nFontId, nFlags; + rStrm >> nFontId >> nFlags; + maModel.mnFontId = nFontId; + maModel.setBinData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) ); +} + +void PhoneticSettings::importStringData( BiffInputStream& rStrm ) +{ + sal_uInt16 nFontId, nFlags; + rStrm >> nFontId >> nFlags; + maModel.mnFontId = nFontId; + maModel.setBinData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) ); +} + +// ============================================================================ + +RichStringPhonetic::RichStringPhonetic( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mnBasePos( -1 ), + mnBaseEnd( -1 ) +{ +} + +void RichStringPhonetic::setText( const OUString& rText ) +{ + maText = rText; +} + +void RichStringPhonetic::importPhoneticRun( const AttributeList& rAttribs ) +{ + mnBasePos = rAttribs.getInteger( XML_sb, -1 ); + mnBaseEnd = rAttribs.getInteger( XML_eb, -1 ); +} + +void RichStringPhonetic::setBaseRange( sal_Int32 nBasePos, sal_Int32 nBaseEnd ) +{ + mnBasePos = nBasePos; + mnBaseEnd = nBaseEnd; +} + +// ---------------------------------------------------------------------------- + +void PhoneticPortionModel::read( RecordInputStream& rStrm ) +{ + mnPos = rStrm.readuInt16(); + mnBasePos = rStrm.readuInt16(); + mnBaseLen = rStrm.readuInt16(); +} + +void PhoneticPortionModel::read( BiffInputStream& rStrm ) +{ + mnPos = rStrm.readuInt16(); + mnBasePos = rStrm.readuInt16(); + mnBaseLen = rStrm.readuInt16(); +} + +// ---------------------------------------------------------------------------- + +void PhoneticPortionModelList::appendPortion( const PhoneticPortionModel& rPortion ) +{ + // same character index may occur several times + OSL_ENSURE( empty() || ((back().mnPos <= rPortion.mnPos) && + (back().mnBasePos + back().mnBaseLen <= rPortion.mnBasePos)), + "PhoneticPortionModelList::appendPortion - wrong char order" ); + if( empty() || (back().mnPos < rPortion.mnPos) ) + { + push_back( rPortion ); + } + else if( back().mnPos == rPortion.mnPos ) + { + back().mnBasePos = rPortion.mnBasePos; + back().mnBaseLen = rPortion.mnBaseLen; + } +} + +void PhoneticPortionModelList::importPortions( RecordInputStream& rStrm ) +{ + sal_Int32 nCount = rStrm.readInt32(); + clear(); + if( nCount > 0 ) + { + reserve( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 6 ) ); + PhoneticPortionModel aPortion; + for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex ) + { + aPortion.read( rStrm ); + appendPortion( aPortion ); + } + } +} + +OUString PhoneticPortionModelList::importPortions( BiffInputStream& rStrm, sal_Int32 nPhoneticSize ) +{ + OUString aPhoneticText; + sal_uInt16 nPortionCount, nTextLen1, nTextLen2; + rStrm >> nPortionCount >> nTextLen1 >> nTextLen2; + OSL_ENSURE( nTextLen1 == nTextLen2, "PhoneticPortionModelList::importPortions - wrong phonetic text length" ); + if( (nTextLen1 == nTextLen2) && (nTextLen1 > 0) ) + { + sal_Int32 nMinSize = 2 * nTextLen1 + 6 * nPortionCount + 14; + OSL_ENSURE( nMinSize <= nPhoneticSize, "PhoneticPortionModelList::importPortions - wrong size of phonetic data" ); + if( nMinSize <= nPhoneticSize ) + { + aPhoneticText = rStrm.readUnicodeArray( nTextLen1 ); + clear(); + reserve( nPortionCount ); + PhoneticPortionModel aPortion; + for( sal_uInt16 nPortion = 0; nPortion < nPortionCount; ++nPortion ) + { + aPortion.read( rStrm ); + appendPortion( aPortion ); + } + } + } + return aPhoneticText; +} + +// ============================================================================ + +RichString::RichString( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + maPhonSettings( rHelper ) +{ +} + +RichStringPortionRef RichString::importText( const AttributeList& ) +{ + return createPortion(); +} + +RichStringPortionRef RichString::importRun( const AttributeList& ) +{ + return createPortion(); +} + +RichStringPhoneticRef RichString::importPhoneticRun( const AttributeList& rAttribs ) +{ + RichStringPhoneticRef xPhonetic = createPhonetic(); + xPhonetic->importPhoneticRun( rAttribs ); + return xPhonetic; +} + +void RichString::importPhoneticPr( const AttributeList& rAttribs ) +{ + maPhonSettings.importPhoneticPr( rAttribs ); +} + +void RichString::importString( RecordInputStream& rStrm, bool bRich ) +{ + sal_uInt8 nFlags = bRich ? rStrm.readuInt8() : 0; + OUString aBaseText = rStrm.readString(); + + if( !rStrm.isEof() && getFlag( nFlags, OOBIN_STRINGFLAG_FONTS ) ) + { + FontPortionModelList aPortions; + aPortions.importPortions( rStrm ); + createFontPortions( aBaseText, aPortions ); + } + else + { + createPortion()->setText( aBaseText ); + } + + if( !rStrm.isEof() && getFlag( nFlags, OOBIN_STRINGFLAG_PHONETICS ) ) + { + OUString aPhoneticText = rStrm.readString(); + PhoneticPortionModelList aPortions; + aPortions.importPortions( rStrm ); + maPhonSettings.importStringData( rStrm ); + createPhoneticPortions( aPhoneticText, aPortions, aBaseText.getLength() ); + } +} + +void RichString::importByteString( BiffInputStream& rStrm, rtl_TextEncoding eDefaultTextEnc, BiffStringFlags nFlags ) +{ + OSL_ENSURE( !getFlag( nFlags, BIFF_STR_KEEPFONTS ), "RichString::importString - keep fonts not implemented" ); + OSL_ENSURE( !getFlag( nFlags, static_cast< BiffStringFlags >( ~(BIFF_STR_8BITLENGTH | BIFF_STR_EXTRAFONTS) ) ), "RichString::importByteString - unknown flag" ); + bool b8BitLength = getFlag( nFlags, BIFF_STR_8BITLENGTH ); + + OString aBaseText = rStrm.readByteString( !b8BitLength ); + + if( !rStrm.isEof() && getFlag( nFlags, BIFF_STR_EXTRAFONTS ) ) + { + FontPortionModelList aPortions; + aPortions.importPortions( rStrm, false ); + createFontPortions( aBaseText, eDefaultTextEnc, aPortions ); + } + else + { + createPortion()->setText( OStringToOUString( aBaseText, eDefaultTextEnc ) ); + } +} + +void RichString::importUniString( BiffInputStream& rStrm, BiffStringFlags nFlags ) +{ + OSL_ENSURE( !getFlag( nFlags, BIFF_STR_KEEPFONTS ), "RichString::importUniString - keep fonts not implemented" ); + OSL_ENSURE( !getFlag( nFlags, static_cast< BiffStringFlags >( ~(BIFF_STR_8BITLENGTH | BIFF_STR_SMARTFLAGS) ) ), "RichString::importUniString - unknown flag" ); + bool b8BitLength = getFlag( nFlags, BIFF_STR_8BITLENGTH ); + + // --- string header --- + sal_uInt16 nChars = b8BitLength ? rStrm.readuInt8() : rStrm.readuInt16(); + sal_uInt8 nFlagField = 0; + if( (nChars > 0) || !getFlag( nFlags, BIFF_STR_SMARTFLAGS ) ) + rStrm >> nFlagField; + bool b16Bit = getFlag( nFlagField, BIFF_STRF_16BIT ); + bool bFonts = getFlag( nFlagField, BIFF_STRF_RICH ); + bool bPhonetic = getFlag( nFlagField, BIFF_STRF_PHONETIC ); + sal_uInt16 nFontCount = bFonts ? rStrm.readuInt16() : 0; + sal_Int32 nPhoneticSize = bPhonetic ? rStrm.readInt32() : 0; + + // --- character array --- + OUString aBaseText = rStrm.readUniStringChars( nChars, b16Bit ); + + // --- formatting --- + // #122185# bRich flag may be set, but format runs may be missing + if( !rStrm.isEof() && (nFontCount > 0) ) + { + FontPortionModelList aPortions; + aPortions.importPortions( rStrm, nFontCount, BIFF_FONTPORTION_16BIT ); + createFontPortions( aBaseText, aPortions ); + } + else + { + createPortion()->setText( aBaseText ); + } + + // --- Asian phonetic information --- + // #122185# bPhonetic flag may be set, but phonetic info may be missing + if( !rStrm.isEof() && (nPhoneticSize > 0) ) + { + sal_Int64 nPhoneticEnd = rStrm.tell() + nPhoneticSize; + OSL_ENSURE( nPhoneticSize > 14, "RichString::importUniString - wrong size of phonetic data" ); + if( nPhoneticSize > 14 ) + { + sal_uInt16 nId, nSize; + rStrm >> nId >> nSize; + OSL_ENSURE( nId == 1, "RichString::importUniString - unknown phonetic data identifier" ); + sal_Int32 nMinSize = nSize + 4; + OSL_ENSURE( nMinSize <= nPhoneticSize, "RichString::importUniString - wrong size of phonetic data" ); + if( (nId == 1) && (nMinSize <= nPhoneticSize) ) + { + maPhonSettings.importStringData( rStrm ); + PhoneticPortionModelList aPortions; + OUString aPhoneticText = aPortions.importPortions( rStrm, nPhoneticSize ); + createPhoneticPortions( aPhoneticText, aPortions, aBaseText.getLength() ); + } + } + rStrm.seek( nPhoneticEnd ); + } +} + +void RichString::finalizeImport() +{ + maFontPortions.forEachMem( &RichStringPortion::finalizeImport ); +} + +OUString RichString::getPlainText() const +{ + OUStringBuffer aBuffer; + for( PortionVec::const_iterator aIt = maFontPortions.begin(), aEnd = maFontPortions.end(); aIt != aEnd; ++aIt ) + aBuffer.append( (*aIt)->getText() ); + return aBuffer.makeStringAndClear(); +} + +void RichString::convert( const Reference< XText >& rxText, sal_Int32 nXfId ) const +{ + for( PortionVec::const_iterator aIt = maFontPortions.begin(), aEnd = maFontPortions.end(); aIt != aEnd; ++aIt ) + { + (*aIt)->convert( rxText, nXfId ); + nXfId = -1; // use passed XF identifier for first portion only + } +} + +// private -------------------------------------------------------------------- + +RichStringPortionRef RichString::createPortion() +{ + RichStringPortionRef xPortion( new RichStringPortion( *this ) ); + maFontPortions.push_back( xPortion ); + return xPortion; +} + +RichStringPhoneticRef RichString::createPhonetic() +{ + RichStringPhoneticRef xPhonetic( new RichStringPhonetic( *this ) ); + maPhonPortions.push_back( xPhonetic ); + return xPhonetic; +} + +void RichString::createFontPortions( const OString& rText, rtl_TextEncoding eDefaultTextEnc, FontPortionModelList& rPortions ) +{ + maFontPortions.clear(); + sal_Int32 nStrLen = rText.getLength(); + if( nStrLen > 0 ) + { + // add leading and trailing string position to ease the following loop + if( rPortions.empty() || (rPortions.front().mnPos > 0) ) + rPortions.insert( rPortions.begin(), FontPortionModel( 0, -1 ) ); + if( rPortions.back().mnPos < nStrLen ) + rPortions.push_back( FontPortionModel( nStrLen, -1 ) ); + + // create all string portions according to the font id vector + for( FontPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt ) + { + sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos; + if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) ) + { + // convert byte string to unicode string, using current font encoding + FontRef xFont = getStyles().getFont( aIt->mnFontId ); + rtl_TextEncoding eTextEnc = xFont.get() ? xFont->getFontEncoding() : eDefaultTextEnc; + OUString aUniStr = OStringToOUString( rText.copy( aIt->mnPos, nPortionLen ), eTextEnc ); + // create string portion + RichStringPortionRef xPortion = createPortion(); + xPortion->setText( aUniStr ); + xPortion->setFontId( aIt->mnFontId ); + } + } + } +} + +void RichString::createFontPortions( const OUString& rText, FontPortionModelList& rPortions ) +{ + maFontPortions.clear(); + sal_Int32 nStrLen = rText.getLength(); + if( nStrLen > 0 ) + { + // add leading and trailing string position to ease the following loop + if( rPortions.empty() || (rPortions.front().mnPos > 0) ) + rPortions.insert( rPortions.begin(), FontPortionModel( 0, -1 ) ); + if( rPortions.back().mnPos < nStrLen ) + rPortions.push_back( FontPortionModel( nStrLen, -1 ) ); + + // create all string portions according to the font id vector + for( FontPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt ) + { + sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos; + if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) ) + { + RichStringPortionRef xPortion = createPortion(); + xPortion->setText( rText.copy( aIt->mnPos, nPortionLen ) ); + xPortion->setFontId( aIt->mnFontId ); + } + } + } +} + +void RichString::createPhoneticPortions( const ::rtl::OUString& rText, PhoneticPortionModelList& rPortions, sal_Int32 nBaseLen ) +{ + maPhonPortions.clear(); + sal_Int32 nStrLen = rText.getLength(); + if( nStrLen > 0 ) + { + // no portions - assign phonetic text to entire base text + if( rPortions.empty() ) + rPortions.push_back( PhoneticPortionModel( 0, 0, nBaseLen ) ); + // add trailing string position to ease the following loop + if( rPortions.back().mnPos < nStrLen ) + rPortions.push_back( PhoneticPortionModel( nStrLen, nBaseLen, 0 ) ); + + // create all phonetic portions according to the portions vector + for( PhoneticPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt ) + { + sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos; + if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) ) + { + RichStringPhoneticRef xPhonetic = createPhonetic(); + xPhonetic->setText( rText.copy( aIt->mnPos, nPortionLen ) ); + xPhonetic->setBaseRange( aIt->mnBasePos, aIt->mnBasePos + aIt->mnBaseLen ); + } + } + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/richstringcontext.cxx b/oox/source/xls/richstringcontext.cxx new file mode 100644 index 000000000000..4eb3f5e91a4f --- /dev/null +++ b/oox/source/xls/richstringcontext.cxx @@ -0,0 +1,95 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/richstringcontext.hxx" +#include "oox/xls/stylesfragment.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +// ============================================================================ + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxRichStringContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( isRootElement() ) + { + switch( nElement ) + { + case XLS_TOKEN( t ): mxPortion = mxString->importText( rAttribs ); return this; // collect text in onEndElement() + case XLS_TOKEN( r ): mxPortion = mxString->importRun( rAttribs ); return this; + case XLS_TOKEN( rPh ): mxPhonetic = mxString->importPhoneticRun( rAttribs ); return this; + case XLS_TOKEN( phoneticPr ): mxString->importPhoneticPr( rAttribs ); break; + } + } + else switch( getCurrentElement() ) + { + case XLS_TOKEN( r ): + switch( nElement ) + { + case XLS_TOKEN( rPr ): + if( mxPortion.get() ) + return new OoxFontContext( *this, mxPortion->createFont() ); + break; + + case XLS_TOKEN( t ): + return this; // collect portion text in onEndElement() + } + break; + + case XLS_TOKEN( rPh ): + switch( nElement ) + { + case XLS_TOKEN( t ): + return this; // collect phonetic text in onEndElement() + } + break; + } + return 0; +} + +void OoxRichStringContext::onEndElement( const OUString& rChars ) +{ + if( getCurrentElement() == XLS_TOKEN( t ) ) + { + switch( getPreviousElement() ) + { + case XLS_TOKEN( rPh ): if( mxPhonetic.get() ) mxPhonetic->setText( rChars ); break; + default: if( mxPortion.get() ) mxPortion->setText( rChars ); break; + } + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/scenariobuffer.cxx b/oox/source/xls/scenariobuffer.cxx new file mode 100644 index 000000000000..a4a2627f3efb --- /dev/null +++ b/oox/source/xls/scenariobuffer.cxx @@ -0,0 +1,306 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/scenariobuffer.hxx" +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/sheet/XScenario.hpp> +#include <com/sun/star/sheet/XScenarios.hpp> +#include <com/sun/star/sheet/XScenariosSupplier.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::rtl::OUString; +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::container::XIndexAccess; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::table::XCell; +using ::com::sun::star::sheet::XScenario; +using ::com::sun::star::sheet::XScenarios; +using ::com::sun::star::sheet::XScenariosSupplier; +using ::com::sun::star::sheet::XSpreadsheet; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_Int32 BIFF_SCENARIO_DELETED = 0x4000; + +} // namespace + +// ============================================================================ + +ScenarioCellModel::ScenarioCellModel() : + mnNumFmtId( 0 ), + mbDeleted( false ) +{ +} + +// ---------------------------------------------------------------------------- + +ScenarioModel::ScenarioModel() : + mbLocked( false ), + mbHidden( false ) +{ +} + +// ---------------------------------------------------------------------------- + +Scenario::Scenario( const WorkbookHelper& rHelper, sal_Int16 nSheet ) : + WorkbookHelper( rHelper ), + mnSheet( nSheet ) +{ +} + +void Scenario::importScenario( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + maModel.maComment = rAttribs.getXString( XML_comment, OUString() ); + maModel.maUser = rAttribs.getXString( XML_user, OUString() ); + maModel.mbLocked = rAttribs.getBool( XML_locked, false ); + maModel.mbHidden = rAttribs.getBool( XML_hidden, false ); +} + +void Scenario::importInputCells( const AttributeList& rAttribs ) +{ + ScenarioCellModel aModel; + getAddressConverter().convertToCellAddressUnchecked( aModel.maPos, rAttribs.getString( XML_r, OUString() ), mnSheet ); + aModel.maValue = rAttribs.getXString( XML_val, OUString() ); + aModel.mnNumFmtId = rAttribs.getInteger( XML_numFmtId, 0 ); + aModel.mbDeleted = rAttribs.getBool( XML_deleted, false ); + maCells.push_back( aModel ); +} + +void Scenario::importScenario( RecordInputStream& rStrm ) +{ + rStrm.skip( 2 ); // cell count + // two longs instead of flag field + maModel.mbLocked = rStrm.readInt32() != 0; + maModel.mbHidden = rStrm.readInt32() != 0; + rStrm >> maModel.maName >> maModel.maComment >> maModel.maUser; +} + +void Scenario::importInputCells( RecordInputStream& rStrm ) +{ + // TODO: where is the deleted flag? + ScenarioCellModel aModel; + BinAddress aPos; + rStrm >> aPos; + rStrm.skip( 8 ); + aModel.mnNumFmtId = rStrm.readuInt16(); + rStrm >> aModel.maValue; + getAddressConverter().convertToCellAddressUnchecked( aModel.maPos, aPos, mnSheet ); + maCells.push_back( aModel ); +} + +void Scenario::importScenario( BiffInputStream& rStrm ) +{ + sal_uInt16 nCellCount; + sal_uInt8 nNameLen, nCommentLen, nUserLen; + rStrm >> nCellCount; + // two bytes instead of flag field + maModel.mbLocked = rStrm.readuInt8() != 0; + maModel.mbHidden = rStrm.readuInt8() != 0; + rStrm >> nNameLen >> nCommentLen >> nUserLen; + maModel.maName = rStrm.readUniStringBody( nNameLen ); + // user name: before comment (in difference to leading length field), repeated length + if( nUserLen > 0 ) + maModel.maUser = rStrm.readUniString(); + // comment: repeated length + if( nCommentLen > 0 ) + maModel.maComment = rStrm.readUniString(); + + // list of cell addresses + for( sal_uInt16 nCell = 0; !rStrm.isEof() && (nCell < nCellCount); ++nCell ) + { + ScenarioCellModel aModel; + BinAddress aPos; + rStrm >> aPos; + // deleted flag is encoded in column index + aModel.mbDeleted = getFlag( aPos.mnCol, BIFF_SCENARIO_DELETED ); + setFlag( aPos.mnCol, BIFF_SCENARIO_DELETED, false ); + getAddressConverter().convertToCellAddressUnchecked( aModel.maPos, aPos, mnSheet ); + maCells.push_back( aModel ); + } + + // list of cell values + for( ScenarioCellVector::iterator aIt = maCells.begin(), aEnd = maCells.end(); !rStrm.isEof() && (aIt != aEnd); ++aIt ) + aIt->maValue = rStrm.readUniString(); +} + +void Scenario::finalizeImport() +{ + AddressConverter& rAddrConv = getAddressConverter(); + ::std::vector< CellRangeAddress > aRanges; + for( ScenarioCellVector::iterator aIt = maCells.begin(), aEnd = maCells.end(); aIt != aEnd; ++aIt ) + if( !aIt->mbDeleted && rAddrConv.checkCellAddress( aIt->maPos, true ) ) + aRanges.push_back( CellRangeAddress( aIt->maPos.Sheet, aIt->maPos.Column, aIt->maPos.Row, aIt->maPos.Column, aIt->maPos.Row ) ); + + if( !aRanges.empty() && (maModel.maName.getLength() > 0) ) try + { + /* Find an unused name for the scenario (Calc stores scenario data in + hidden sheets named after the scenario following the base sheet). */ + Reference< XNameAccess > xSheetsNA( getDocument()->getSheets(), UNO_QUERY_THROW ); + OUString aScenName = ContainerHelper::getUnusedName( xSheetsNA, maModel.maName, '_' ); + + // create the new scenario sheet + Reference< XScenariosSupplier > xScenariosSupp( getSheetFromDoc( mnSheet ), UNO_QUERY_THROW ); + Reference< XScenarios > xScenarios( xScenariosSupp->getScenarios(), UNO_SET_THROW ); + xScenarios->addNewByName( aScenName, ContainerHelper::vectorToSequence( aRanges ), maModel.maComment ); + + // write scenario cell values + Reference< XSpreadsheet > xSheet( getSheetFromDoc( aScenName ), UNO_SET_THROW ); + for( ScenarioCellVector::iterator aIt = maCells.begin(), aEnd = maCells.end(); aIt != aEnd; ++aIt ) + { + if( !aIt->mbDeleted ) try + { + // use XCell::setFormula to auto-detect values and strings + Reference< XCell > xCell( xSheet->getCellByPosition( aIt->maPos.Column, aIt->maPos.Row ), UNO_SET_THROW ); + xCell->setFormula( aIt->maValue ); + } + catch( Exception& ) + { + } + } + + // scenario properties + PropertySet aPropSet( xScenarios->getByName( aScenName ) ); + aPropSet.setProperty( PROP_IsActive, false ); + aPropSet.setProperty( PROP_CopyBack, false ); + aPropSet.setProperty( PROP_CopyStyles, false ); + aPropSet.setProperty( PROP_CopyFormulas, false ); + aPropSet.setProperty( PROP_Protected, maModel.mbLocked ); + // #112621# do not show/print scenario border + aPropSet.setProperty( PROP_ShowBorder, false ); + aPropSet.setProperty( PROP_PrintBorder, false ); + } + catch( Exception& ) + { + } +} + +// ============================================================================ + +SheetScenariosModel::SheetScenariosModel() : + mnCurrent( 0 ), + mnShown( 0 ) +{ +} + +// ---------------------------------------------------------------------------- + +SheetScenarios::SheetScenarios( const WorkbookHelper& rHelper, sal_Int16 nSheet ) : + WorkbookHelper( rHelper ), + mnSheet( nSheet ) +{ +} + +void SheetScenarios::importScenarios( const AttributeList& rAttribs ) +{ + maModel.mnCurrent = rAttribs.getInteger( XML_current, 0 ); + maModel.mnShown = rAttribs.getInteger( XML_show, 0 ); +} + +void SheetScenarios::importScenarios( RecordInputStream& rStrm ) +{ + maModel.mnCurrent = rStrm.readuInt16(); + maModel.mnShown = rStrm.readuInt16(); +} + +void SheetScenarios::importScenarios( BiffInputStream& rStrm ) +{ + rStrm.skip( 2 ); // scenario count + maModel.mnCurrent = rStrm.readuInt16(); + maModel.mnShown = rStrm.readuInt16(); + + // read following SCENARIO records + while( (rStrm.getNextRecId() == BIFF_ID_SCENARIO) && rStrm.startNextRecord() ) + createScenario().importScenario( rStrm ); +} + +Scenario& SheetScenarios::createScenario() +{ + ScenarioVector::value_type xScenario( new Scenario( *this, mnSheet ) ); + maScenarios.push_back( xScenario ); + return *xScenario; +} + +void SheetScenarios::finalizeImport() +{ + maScenarios.forEachMem( &Scenario::finalizeImport ); + + // activate a scenario + try + { + Reference< XScenariosSupplier > xScenariosSupp( getSheetFromDoc( mnSheet ), UNO_QUERY_THROW ); + Reference< XIndexAccess > xScenariosIA( xScenariosSupp->getScenarios(), UNO_QUERY_THROW ); + Reference< XScenario > xScenario( xScenariosIA->getByIndex( maModel.mnShown ), UNO_QUERY_THROW ); + xScenario->apply(); + } + catch( Exception& ) + { + } +} + +// ============================================================================ + +ScenarioBuffer::ScenarioBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +SheetScenarios& ScenarioBuffer::createSheetScenarios( sal_Int16 nSheet ) +{ + SheetScenariosMap::mapped_type& rxSheetScens = maSheetScenarios[ nSheet ]; + if( !rxSheetScens ) + rxSheetScens.reset( new SheetScenarios( *this, nSheet ) ); + return *rxSheetScens; +} + +void ScenarioBuffer::finalizeImport() +{ + maSheetScenarios.forEachMem( &SheetScenarios::finalizeImport ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/scenariocontext.cxx b/oox/source/xls/scenariocontext.cxx new file mode 100644 index 000000000000..8d1341688a74 --- /dev/null +++ b/oox/source/xls/scenariocontext.cxx @@ -0,0 +1,124 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/scenariocontext.hxx" +#include "oox/xls/scenariobuffer.hxx" + +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxScenarioContext::OoxScenarioContext( OoxWorksheetContextBase& rParent, SheetScenarios& rSheetScenarios ) : + OoxWorksheetContextBase( rParent ), + mrScenario( rSheetScenarios.createScenario() ) +{ +} + +ContextHandlerRef OoxScenarioContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( scenario ): + if( nElement == XLS_TOKEN( inputCells ) ) mrScenario.importInputCells( rAttribs ); + break; + } + return 0; +} + +void OoxScenarioContext::onStartElement( const AttributeList& rAttribs ) +{ + if( isRootElement() ) + mrScenario.importScenario( rAttribs ); +} + +ContextHandlerRef OoxScenarioContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_SCENARIO: + if( nRecId == OOBIN_ID_INPUTCELLS ) mrScenario.importInputCells( rStrm ); + break; + } + return 0; +} + +void OoxScenarioContext::onStartRecord( RecordInputStream& rStrm ) +{ + if( isRootElement() ) + mrScenario.importScenario( rStrm ); +} + +// ============================================================================ + +OoxScenariosContext::OoxScenariosContext( OoxWorksheetFragmentBase& rFragment ) : + OoxWorksheetContextBase( rFragment ), + mrSheetScenarios( getScenarios().createSheetScenarios( getSheetIndex() ) ) +{ +} + +ContextHandlerRef OoxScenariosContext::onCreateContext( sal_Int32 nElement, const AttributeList& ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( scenarios ): + if( nElement == XLS_TOKEN( scenario ) ) return new OoxScenarioContext( *this, mrSheetScenarios ); + break; + } + return 0; +} + +void OoxScenariosContext::onStartElement( const AttributeList& rAttribs ) +{ + if( isRootElement() ) + mrSheetScenarios.importScenarios( rAttribs ); +} + +ContextHandlerRef OoxScenariosContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_SCENARIOS: + if( nRecId == OOBIN_ID_SCENARIO ) return new OoxScenarioContext( *this, mrSheetScenarios ); + break; + } + return 0; +} + +void OoxScenariosContext::onStartRecord( RecordInputStream& rStrm ) +{ + if( isRootElement() ) + mrSheetScenarios.importScenarios( rStrm ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/sharedformulabuffer.cxx b/oox/source/xls/sharedformulabuffer.cxx new file mode 100644 index 000000000000..c95d52910bec --- /dev/null +++ b/oox/source/xls/sharedformulabuffer.cxx @@ -0,0 +1,215 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/sharedformulabuffer.hxx" +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/sheet/XFormulaTokens.hpp> +#include "properties.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/formulaparser.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::XFormulaTokens; +using ::com::sun::star::sheet::XNamedRange; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +bool operator==( const CellAddress& rAddr1, const CellAddress& rAddr2 ) +{ + return + (rAddr1.Sheet == rAddr2.Sheet) && + (rAddr1.Column == rAddr2.Column) && + (rAddr1.Row == rAddr2.Row); +} + +bool lclContains( const CellRangeAddress& rRange, const CellAddress& rAddr ) +{ + return + (rRange.Sheet == rAddr.Sheet) && + (rRange.StartColumn <= rAddr.Column) && (rAddr.Column <= rRange.EndColumn) && + (rRange.StartRow <= rAddr.Row) && (rAddr.Row <= rRange.EndRow); +} + +} // namespace + +// ============================================================================ + +ExtCellFormulaContext::ExtCellFormulaContext( const WorksheetHelper& rHelper, + const Reference< XFormulaTokens >& rxTokens, const CellAddress& rCellPos ) : + SimpleFormulaContext( rxTokens, false, false ), + WorksheetHelper( rHelper ) +{ + setBaseAddress( rCellPos ); +} + +void ExtCellFormulaContext::setSharedFormula( const CellAddress& rBaseAddr ) +{ + getSharedFormulas().setSharedFormulaCell( *this, rBaseAddr ); +} + +// ============================================================================ + +SharedFormulaBuffer::SharedFormulaBuffer( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +void SharedFormulaBuffer::importSharedFmla( const OUString& rFormula, const OUString& rSharedRange, sal_Int32 nSharedId, const CellAddress& rBaseAddr ) +{ + CellRangeAddress aFmlaRange; + if( getAddressConverter().convertToCellRange( aFmlaRange, rSharedRange, getSheetIndex(), true, true ) ) + { + // create the defined name representing the shared formula + OSL_ENSURE( lclContains( aFmlaRange, rBaseAddr ), "SharedFormulaBuffer::importSharedFmla - invalid range for shared formula" ); + BinAddress aMapKey( nSharedId, 0 ); + Reference< XNamedRange > xNamedRange = createDefinedName( aMapKey ); + // convert the formula definition + Reference< XFormulaTokens > xTokens( xNamedRange, UNO_QUERY ); + if( xTokens.is() ) + { + SimpleFormulaContext aContext( xTokens, true, false ); + aContext.setBaseAddress( rBaseAddr ); + getFormulaParser().importFormula( aContext, rFormula ); + updateCachedCell( rBaseAddr, aMapKey ); + } + } +} + +void SharedFormulaBuffer::importSharedFmla( RecordInputStream& rStrm, const CellAddress& rBaseAddr ) +{ + BinRange aRange; + rStrm >> aRange; + CellRangeAddress aFmlaRange; + if( getAddressConverter().convertToCellRange( aFmlaRange, aRange, getSheetIndex(), true, true ) ) + { + // create the defined name representing the shared formula + OSL_ENSURE( lclContains( aFmlaRange, rBaseAddr ), "SharedFormulaBuffer::importSharedFmla - invalid range for shared formula" ); + BinAddress aMapKey( rBaseAddr ); + Reference< XNamedRange > xNamedRange = createDefinedName( aMapKey ); + // load the formula definition + Reference< XFormulaTokens > xTokens( xNamedRange, UNO_QUERY ); + if( xTokens.is() ) + { + SimpleFormulaContext aContext( xTokens, true, false ); + aContext.setBaseAddress( rBaseAddr ); + getFormulaParser().importFormula( aContext, rStrm ); + updateCachedCell( rBaseAddr, aMapKey ); + } + } +} + +void SharedFormulaBuffer::importSharedFmla( BiffInputStream& rStrm, const CellAddress& rBaseAddr ) +{ + BinRange aRange; + aRange.read( rStrm, false ); // always 8bit column indexes + CellRangeAddress aFmlaRange; + if( getAddressConverter().convertToCellRange( aFmlaRange, aRange, getSheetIndex(), true, true ) ) + { + // create the defined name representing the shared formula + OSL_ENSURE( lclContains( aFmlaRange, rBaseAddr ), "SharedFormulaBuffer::importSharedFmla - invalid range for shared formula" ); + BinAddress aMapKey( rBaseAddr ); + Reference< XNamedRange > xNamedRange = createDefinedName( aMapKey ); + // load the formula definition + Reference< XFormulaTokens > xTokens( xNamedRange, UNO_QUERY ); + if( xTokens.is() ) + { + rStrm.skip( 2 ); // flags + SimpleFormulaContext aContext( xTokens, true, false ); + aContext.setBaseAddress( rBaseAddr ); + getFormulaParser().importFormula( aContext, rStrm ); + updateCachedCell( rBaseAddr, aMapKey ); + } + } +} + +void SharedFormulaBuffer::setSharedFormulaCell( ExtCellFormulaContext& rContext, const CellAddress& rBaseAddr ) +{ + if( !implSetSharedFormulaCell( rContext, BinAddress( rBaseAddr ) ) ) + if( rContext.getBaseAddress() == rBaseAddr ) + mxLastContext.reset( new ExtCellFormulaContext( rContext ) ); +} + +void SharedFormulaBuffer::setSharedFormulaCell( ExtCellFormulaContext& rContext, sal_Int32 nSharedId ) +{ + implSetSharedFormulaCell( rContext, BinAddress( nSharedId, 0 ) ); +} + +Reference< XNamedRange > SharedFormulaBuffer::createDefinedName( const BinAddress& rMapKey ) +{ + OSL_ENSURE( maIndexMap.count( rMapKey ) == 0, "SharedFormulaBuffer::createDefinedName - shared formula exists already" ); + // create the defined name representing the shared formula + OUString aName = OUStringBuffer().appendAscii( "__shared_" ). + append( static_cast< sal_Int32 >( getSheetIndex() + 1 ) ). + append( sal_Unicode( '_' ) ).append( rMapKey.mnRow ). + append( sal_Unicode( '_' ) ).append( rMapKey.mnCol ).makeStringAndClear(); + Reference< XNamedRange > xNamedRange = createNamedRangeObject( aName ); + PropertySet aNameProps( xNamedRange ); + aNameProps.setProperty( PROP_IsSharedFormula, true ); + sal_Int32 nTokenIndex = -1; + if( aNameProps.getProperty( nTokenIndex, PROP_TokenIndex ) && (nTokenIndex >= 0) ) + maIndexMap[ rMapKey ] = nTokenIndex; + return xNamedRange; +} + +bool SharedFormulaBuffer::implSetSharedFormulaCell( ExtCellFormulaContext& rContext, const BinAddress& rMapKey ) +{ + TokenIndexMap::const_iterator aIt = maIndexMap.find( rMapKey ); + sal_Int32 nTokenIndex = (aIt == maIndexMap.end()) ? -1 : aIt->second; + if( nTokenIndex >= 0 ) + { + getFormulaParser().convertNameToFormula( rContext, nTokenIndex ); + return true; + } + return false; +} + +void SharedFormulaBuffer::updateCachedCell( const CellAddress& rBaseAddr, const BinAddress& rMapKey ) +{ + if( mxLastContext.get() && (mxLastContext->getBaseAddress() == rBaseAddr) ) + implSetSharedFormulaCell( *mxLastContext, rMapKey ); + mxLastContext.reset(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/sharedstringsbuffer.cxx b/oox/source/xls/sharedstringsbuffer.cxx new file mode 100644 index 000000000000..f2a92ef06c0a --- /dev/null +++ b/oox/source/xls/sharedstringsbuffer.cxx @@ -0,0 +1,84 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/sharedstringsbuffer.hxx" +#include "oox/xls/biffinputstream.hxx" + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::text::XText; + +namespace oox { +namespace xls { + +// ============================================================================ + +SharedStringsBuffer::SharedStringsBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +RichStringRef SharedStringsBuffer::createRichString() +{ + RichStringRef xString( new RichString( *this ) ); + maStrings.push_back( xString ); + return xString; +} + +void SharedStringsBuffer::importSst( BiffInputStream& rStrm ) +{ + rStrm.skip( 4 ); + sal_Int32 nStringCount = rStrm.readInt32(); + if( nStringCount > 0 ) + { + maStrings.clear(); + maStrings.reserve( static_cast< size_t >( nStringCount ) ); + for( ; !rStrm.isEof() && (nStringCount > 0); --nStringCount ) + { + RichStringRef xString( new RichString( *this ) ); + maStrings.push_back( xString ); + xString->importUniString( rStrm ); + } + } +} + +void SharedStringsBuffer::finalizeImport() +{ + maStrings.forEachMem( &RichString::finalizeImport ); +} + +void SharedStringsBuffer::convertString( const Reference< XText >& rxText, sal_Int32 nStringId, sal_Int32 nXfId ) const +{ + if( rxText.is() ) + if( const RichString* pString = maStrings.get( nStringId ).get() ) + pString->convert( rxText, nXfId ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/sharedstringsfragment.cxx b/oox/source/xls/sharedstringsfragment.cxx new file mode 100644 index 000000000000..b868ca08523d --- /dev/null +++ b/oox/source/xls/sharedstringsfragment.cxx @@ -0,0 +1,104 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/sharedstringsfragment.hxx" +#include "oox/xls/sharedstringsbuffer.hxx" +#include "oox/xls/richstringcontext.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxSharedStringsFragment::OoxSharedStringsFragment( + const WorkbookHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxSharedStringsFragment::onCreateContext( sal_Int32 nElement, const AttributeList& ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( sst ) ) + return this; + break; + + case XLS_TOKEN( sst ): + if( nElement == XLS_TOKEN( si ) ) + return new OoxRichStringContext( *this, getSharedStrings().createRichString() ); + break; + } + return 0; +} + +ContextHandlerRef OoxSharedStringsFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_SST ) + return this; + break; + + case OOBIN_ID_SST: + if( nRecId == OOBIN_ID_SI ) + getSharedStrings().createRichString()->importString( rStrm, true ); + break; + } + return 0; +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxSharedStringsFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_SST, OOBIN_ID_SST + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +void OoxSharedStringsFragment::finalizeImport() +{ + getSharedStrings().finalizeImport(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/sheetdatacontext.cxx b/oox/source/xls/sheetdatacontext.cxx new file mode 100644 index 000000000000..05e4c5bfe767 --- /dev/null +++ b/oox/source/xls/sheetdatacontext.cxx @@ -0,0 +1,992 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/sheetdatacontext.hxx" +#include <com/sun/star/table/CellContentType.hpp> +#include <com/sun/star/table/XCell.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/sheet/XFormulaTokens.hpp> +#include <com/sun/star/sheet/XArrayFormulaTokens.hpp> +#include <com/sun/star/text/XText.hpp> +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/formulaparser.hxx" +#include "oox/xls/richstringcontext.hxx" +#include "oox/xls/sharedformulabuffer.hxx" +#include "oox/xls/unitconverter.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::table::CellContentType_EMPTY; +using ::com::sun::star::table::XCell; +using ::com::sun::star::table::XCellRange; +using ::com::sun::star::sheet::XFormulaTokens; +using ::com::sun::star::sheet::XArrayFormulaTokens; +using ::com::sun::star::text::XText; +using ::oox::core::ContextHandlerRef; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +// record constants ----------------------------------------------------------- + +const sal_uInt32 OOBIN_CELL_SHOWPHONETIC = 0x01000000; + +const sal_uInt8 OOBIN_DATATABLE_ROW = 0x01; +const sal_uInt8 OOBIN_DATATABLE_2D = 0x02; +const sal_uInt8 OOBIN_DATATABLE_REF1DEL = 0x04; +const sal_uInt8 OOBIN_DATATABLE_REF2DEL = 0x08; + +const sal_uInt16 OOBIN_ROW_THICKTOP = 0x0001; +const sal_uInt16 OOBIN_ROW_THICKBOTTOM = 0x0002; +const sal_uInt16 OOBIN_ROW_COLLAPSED = 0x0800; +const sal_uInt16 OOBIN_ROW_HIDDEN = 0x1000; +const sal_uInt16 OOBIN_ROW_CUSTOMHEIGHT = 0x2000; +const sal_uInt16 OOBIN_ROW_CUSTOMFORMAT = 0x4000; +const sal_uInt8 OOBIN_ROW_SHOWPHONETIC = 0x01; + +const sal_uInt8 BIFF_BOOLERR_BOOL = 0; +const sal_uInt8 BIFF_BOOLERR_ERROR = 1; + +const sal_uInt16 BIFF_DATATABLE_ROW = 0x0004; +const sal_uInt16 BIFF_DATATABLE_2D = 0x0008; +const sal_uInt16 BIFF_DATATABLE_REF1DEL = 0x0010; +const sal_uInt16 BIFF_DATATABLE_REF2DEL = 0x0020; + +const sal_uInt8 BIFF_FORMULA_RES_STRING = 0; /// Result is a string. +const sal_uInt8 BIFF_FORMULA_RES_BOOL = 1; /// Result is Boolean value. +const sal_uInt8 BIFF_FORMULA_RES_ERROR = 2; /// Result is error code. +const sal_uInt8 BIFF_FORMULA_RES_EMPTY = 3; /// Result is empty cell (BIFF8 only). +const sal_uInt16 BIFF_FORMULA_SHARED = 0x0008; /// Shared formula cell. + +const sal_uInt8 BIFF2_ROW_CUSTOMFORMAT = 0x01; +const sal_uInt16 BIFF_ROW_DEFAULTHEIGHT = 0x8000; +const sal_uInt16 BIFF_ROW_HEIGHTMASK = 0x7FFF; +const sal_uInt32 BIFF_ROW_COLLAPSED = 0x00000010; +const sal_uInt32 BIFF_ROW_HIDDEN = 0x00000020; +const sal_uInt32 BIFF_ROW_CUSTOMHEIGHT = 0x00000040; +const sal_uInt32 BIFF_ROW_CUSTOMFORMAT = 0x00000080; +const sal_uInt32 BIFF_ROW_THICKTOP = 0x10000000; +const sal_uInt32 BIFF_ROW_THICKBOTTOM = 0x20000000; +const sal_uInt32 BIFF_ROW_SHOWPHONETIC = 0x40000000; + +const sal_Int32 BIFF2_XF_EXTENDED_IDS = 63; +const sal_uInt8 BIFF2_XF_MASK = 0x3F; + +// ---------------------------------------------------------------------------- + +/** Formula context for cell formulas. */ +class CellFormulaContext : public SimpleFormulaContext +{ +public: + explicit CellFormulaContext( + const Reference< XFormulaTokens >& rxTokens, + const CellAddress& rCellPos ); +}; + +CellFormulaContext::CellFormulaContext( const Reference< XFormulaTokens >& rxTokens, const CellAddress& rCellPos ) : + SimpleFormulaContext( rxTokens, false, false ) +{ + setBaseAddress( rCellPos ); +} + +// ---------------------------------------------------------------------------- + +/** Uses the XArrayFormulaTokens interface to set a token sequence. */ +class ArrayFormulaContext : public FormulaContext +{ +public: + explicit ArrayFormulaContext( + const Reference< XArrayFormulaTokens >& rxTokens, + const CellRangeAddress& rArrayRange ); + + virtual void setTokens( const ApiTokenSequence& rTokens ); + +private: + Reference< XArrayFormulaTokens > mxTokens; +}; + +ArrayFormulaContext::ArrayFormulaContext( + const Reference< XArrayFormulaTokens >& rxTokens, const CellRangeAddress& rArrayRange ) : + FormulaContext( false, false ), + mxTokens( rxTokens ) +{ + OSL_ENSURE( mxTokens.is(), "ArrayFormulaContext::ArrayFormulaContext - missing XArrayFormulaTokens interface" ); + setBaseAddress( CellAddress( rArrayRange.Sheet, rArrayRange.StartColumn, rArrayRange.StartRow ) ); +} + +void ArrayFormulaContext::setTokens( const ApiTokenSequence& rTokens ) +{ + mxTokens->setArrayTokens( rTokens ); +} + +// ---------------------------------------------------------------------------- + +} // namespace + +// ============================================================================ + +OoxSheetDataContext::OoxSheetDataContext( OoxWorksheetFragmentBase& rFragment ) : + OoxWorksheetContextBase( rFragment ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxSheetDataContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( sheetData ): + if( nElement == XLS_TOKEN( row ) ) { importRow( rAttribs ); return this; } + break; + + case XLS_TOKEN( row ): + if( nElement == XLS_TOKEN( c ) ) { importCell( rAttribs ); return this; } + break; + + case XLS_TOKEN( c ): + if( maCurrCell.mxCell.is() ) switch( nElement ) + { + case XLS_TOKEN( is ): + mxInlineStr.reset( new RichString( *this ) ); + return new OoxRichStringContext( *this, mxInlineStr ); + case XLS_TOKEN( v ): + return this; + case XLS_TOKEN( f ): + importFormula( rAttribs ); + return this; + } + break; + } + return 0; +} + +void OoxSheetDataContext::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( v ): + maCurrCell.maValueStr = rChars; + maCurrCell.mbHasValueStr = true; + break; + + case XLS_TOKEN( f ): + if( maCurrCell.mxCell.is() ) try + { + switch( maCurrCell.mnFormulaType ) + { + case XML_normal: + if( rChars.getLength() > 0 ) + { + Reference< XFormulaTokens > xTokens( maCurrCell.mxCell, UNO_QUERY_THROW ); + CellFormulaContext aContext( xTokens, maCurrCell.maAddress ); + getFormulaParser().importFormula( aContext, rChars ); + } + break; + + case XML_array: + if( (maCurrCell.maFormulaRef.getLength() > 0) && (rChars.getLength() > 0) ) + { + CellRangeAddress aArrayRange; + Reference< XArrayFormulaTokens > xTokens( getCellRange( maCurrCell.maFormulaRef, &aArrayRange ), UNO_QUERY_THROW ); + ArrayFormulaContext aContext( xTokens, aArrayRange ); + getFormulaParser().importFormula( aContext, rChars ); + } + break; + + case XML_shared: + if( maCurrCell.mnSharedId >= 0 ) + { + if( rChars.getLength() > 0 ) + getSharedFormulas().importSharedFmla( rChars, maCurrCell.maFormulaRef, maCurrCell.mnSharedId, maCurrCell.maAddress ); + Reference< XFormulaTokens > xTokens( maCurrCell.mxCell, UNO_QUERY_THROW ); + ExtCellFormulaContext aContext( *this, xTokens, maCurrCell.maAddress ); + getSharedFormulas().setSharedFormulaCell( aContext, maCurrCell.mnSharedId ); + } + break; + + case XML_dataTable: + if( maCurrCell.maFormulaRef.getLength() > 0 ) + { + CellRangeAddress aTableRange; + if( getAddressConverter().convertToCellRange( aTableRange, maCurrCell.maFormulaRef, getSheetIndex(), true, true ) ) + setTableOperation( aTableRange, maTableData ); + } + break; + + default: + OSL_ENSURE( false, "OoxSheetDataContext::onEndElement - unknown formula type" ); + } + } + catch( Exception& ) + { + } + break; + + case XLS_TOKEN( c ): + if( maCurrCell.mxCell.is() ) + { + if( maCurrCell.mxCell->getType() == CellContentType_EMPTY ) + { + if( maCurrCell.mbHasValueStr ) + { + // implemented in WorksheetHelper class + setCell( maCurrCell ); + } + else if( (maCurrCell.mnCellType == XML_inlineStr) && mxInlineStr.get() ) + { + // convert font settings + mxInlineStr->finalizeImport(); + // write string to cell + Reference< XText > xText( maCurrCell.mxCell, UNO_QUERY ); + if( xText.is() ) + mxInlineStr->convert( xText, maCurrCell.mnXfId ); + } + else + { + // empty cell, update cell type + maCurrCell.mnCellType = XML_TOKEN_INVALID; + } + } + + // store the cell formatting data + setCellFormat( maCurrCell ); + } + break; + } +} + +ContextHandlerRef OoxSheetDataContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_SHEETDATA: + switch( nRecId ) + { + case OOBIN_ID_ROW: importRow( rStrm ); return this; + } + break; + + case OOBIN_ID_ROW: + switch( nRecId ) + { + case OOBIN_ID_ARRAY: importArray( rStrm ); break; + case OOBIN_ID_CELL_BOOL: importCellBool( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_BLANK: importCellBlank( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_DOUBLE: importCellDouble( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_ERROR: importCellError( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_RK: importCellRk( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_RSTRING: importCellRString( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_SI: importCellSi( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_CELL_STRING: importCellString( rStrm, CELLTYPE_VALUE ); break; + case OOBIN_ID_DATATABLE: importDataTable( rStrm ); break; + case OOBIN_ID_FORMULA_BOOL: importCellBool( rStrm, CELLTYPE_FORMULA ); break; + case OOBIN_ID_FORMULA_DOUBLE: importCellDouble( rStrm, CELLTYPE_FORMULA ); break; + case OOBIN_ID_FORMULA_ERROR: importCellError( rStrm, CELLTYPE_FORMULA ); break; + case OOBIN_ID_FORMULA_STRING: importCellString( rStrm, CELLTYPE_FORMULA ); break; + case OOBIN_ID_MULTCELL_BOOL: importCellBool( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_BLANK: importCellBlank( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_DOUBLE: importCellDouble( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_ERROR: importCellError( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_RK: importCellRk( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_RSTRING: importCellRString( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_SI: importCellSi( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_MULTCELL_STRING: importCellString( rStrm, CELLTYPE_MULTI ); break; + case OOBIN_ID_SHAREDFMLA: importSharedFmla( rStrm ); break; + } + break; + } + return 0; +} + +// private -------------------------------------------------------------------- + +void OoxSheetDataContext::importRow( const AttributeList& rAttribs ) +{ + RowModel aModel; + aModel.mnFirstRow = aModel.mnLastRow = rAttribs.getInteger( XML_r, -1 ); + aModel.mfHeight = rAttribs.getDouble( XML_ht, -1.0 ); + aModel.mnXfId = rAttribs.getInteger( XML_s, -1 ); + aModel.mnLevel = rAttribs.getInteger( XML_outlineLevel, 0 ); + aModel.mbCustomHeight = rAttribs.getBool( XML_customHeight, false ); + aModel.mbCustomFormat = rAttribs.getBool( XML_customFormat, false ); + aModel.mbShowPhonetic = rAttribs.getBool( XML_ph, false ); + aModel.mbHidden = rAttribs.getBool( XML_hidden, false ); + aModel.mbCollapsed = rAttribs.getBool( XML_collapsed, false ); + aModel.mbThickTop = rAttribs.getBool( XML_thickTop, false ); + aModel.mbThickBottom = rAttribs.getBool( XML_thickBot, false ); + // set row properties in the current sheet + setRowModel( aModel ); +} + +void OoxSheetDataContext::importCell( const AttributeList& rAttribs ) +{ + maCurrCell.reset(); + maCurrCell.mxCell = getCell( rAttribs.getString( XML_r, OUString() ), &maCurrCell.maAddress ); + maCurrCell.mnCellType = rAttribs.getToken( XML_t, XML_n ); + maCurrCell.mnXfId = rAttribs.getInteger( XML_s, -1 ); + maCurrCell.mbShowPhonetic = rAttribs.getBool( XML_ph, false ); + mxInlineStr.reset(); + + // update used area of the sheet + if( maCurrCell.mxCell.is() ) + extendUsedArea( maCurrCell.maAddress ); +} + +void OoxSheetDataContext::importFormula( const AttributeList& rAttribs ) +{ + maCurrCell.maFormulaRef = rAttribs.getString( XML_ref, OUString() ); + maCurrCell.mnFormulaType = rAttribs.getToken( XML_t, XML_normal ); + maCurrCell.mnSharedId = rAttribs.getInteger( XML_si, -1 ); + maTableData.maRef1 = rAttribs.getString( XML_r1, OUString() ); + maTableData.maRef2 = rAttribs.getString( XML_r2, OUString() ); + maTableData.mb2dTable = rAttribs.getBool( XML_dt2D, false ); + maTableData.mbRowTable = rAttribs.getBool( XML_dtr, false ); + maTableData.mbRef1Deleted = rAttribs.getBool( XML_del1, false ); + maTableData.mbRef2Deleted = rAttribs.getBool( XML_del2, false ); +} + +void OoxSheetDataContext::importCellHeader( RecordInputStream& rStrm, CellType eCellType ) +{ + maCurrCell.reset(); + + switch( eCellType ) + { + case CELLTYPE_VALUE: + case CELLTYPE_FORMULA: rStrm >> maCurrPos.mnCol; break; + case CELLTYPE_MULTI: ++maCurrPos.mnCol; break; + } + + sal_uInt32 nXfId; + rStrm >> nXfId; + + maCurrCell.mxCell = getCell( maCurrPos, &maCurrCell.maAddress ); + maCurrCell.mnXfId = extractValue< sal_Int32 >( nXfId, 0, 24 ); + maCurrCell.mbShowPhonetic = getFlag( nXfId, OOBIN_CELL_SHOWPHONETIC ); + + // update used area of the sheet + if( maCurrCell.mxCell.is() ) + extendUsedArea( maCurrCell.maAddress ); +} + +void OoxSheetDataContext::importCellBool( RecordInputStream& rStrm, CellType eCellType ) +{ + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_b; + if( maCurrCell.mxCell.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + { + bool bValue = rStrm.readuInt8() != 0; + if( eCellType == CELLTYPE_FORMULA ) + { + importCellFormula( rStrm ); + } + else + { + setBooleanCell( maCurrCell.mxCell, bValue ); + // #108770# set 'Standard' number format for all Boolean cells + maCurrCell.mnNumFmtId = 0; + } + } + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellBlank( RecordInputStream& rStrm, CellType eCellType ) +{ + OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "OoxSheetDataContext::importCellBlank - no formula cells supported" ); + importCellHeader( rStrm, eCellType ); + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellDouble( RecordInputStream& rStrm, CellType eCellType ) +{ + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_n; + if( maCurrCell.mxCell.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + { + double fValue = rStrm.readDouble(); + if( eCellType == CELLTYPE_FORMULA ) + importCellFormula( rStrm ); + else + maCurrCell.mxCell->setValue( fValue ); + } + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellError( RecordInputStream& rStrm, CellType eCellType ) +{ + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_e; + if( maCurrCell.mxCell.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + { + sal_uInt8 nErrorCode = rStrm.readuInt8(); + if( eCellType == CELLTYPE_FORMULA ) + importCellFormula( rStrm ); + else + setErrorCell( maCurrCell.mxCell, nErrorCode ); + } + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellRk( RecordInputStream& rStrm, CellType eCellType ) +{ + OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "OoxSheetDataContext::importCellRk - no formula cells supported" ); + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_n; + if( maCurrCell.mxCell.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + maCurrCell.mxCell->setValue( BiffHelper::calcDoubleFromRk( rStrm.readInt32() ) ); + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellRString( RecordInputStream& rStrm, CellType eCellType ) +{ + OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "OoxSheetDataContext::importCellRString - no formula cells supported" ); + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_inlineStr; + Reference< XText > xText( maCurrCell.mxCell, UNO_QUERY ); + if( xText.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + { + RichString aString( *this ); + aString.importString( rStrm, true ); + aString.finalizeImport(); + aString.convert( xText, maCurrCell.mnXfId ); + } + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellSi( RecordInputStream& rStrm, CellType eCellType ) +{ + OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "OoxSheetDataContext::importCellSi - no formula cells supported" ); + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_s; + if( maCurrCell.mxCell.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + setSharedStringCell( maCurrCell.mxCell, rStrm.readInt32(), maCurrCell.mnXfId ); + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellString( RecordInputStream& rStrm, CellType eCellType ) +{ + importCellHeader( rStrm, eCellType ); + maCurrCell.mnCellType = XML_inlineStr; + Reference< XText > xText( maCurrCell.mxCell, UNO_QUERY ); + if( xText.is() && (maCurrCell.mxCell->getType() == CellContentType_EMPTY) ) + { + RichString aString( *this ); + aString.importString( rStrm, false ); + aString.finalizeImport(); + if( eCellType == CELLTYPE_FORMULA ) + importCellFormula( rStrm ); + else + aString.convert( xText, maCurrCell.mnXfId ); + } + setCellFormat( maCurrCell ); +} + +void OoxSheetDataContext::importCellFormula( RecordInputStream& rStrm ) +{ + rStrm.skip( 2 ); + Reference< XFormulaTokens > xTokens( maCurrCell.mxCell, UNO_QUERY ); + if( xTokens.is() ) + { + ExtCellFormulaContext aContext( *this, xTokens, maCurrCell.maAddress ); + getFormulaParser().importFormula( aContext, rStrm ); + } +} + +void OoxSheetDataContext::importRow( RecordInputStream& rStrm ) +{ + RowModel aModel; + + sal_uInt16 nHeight, nFlags1; + sal_uInt8 nFlags2; + rStrm >> maCurrPos.mnRow >> aModel.mnXfId >> nHeight >> nFlags1 >> nFlags2; + + // row index is 0-based in OOBIN, but RowModel expects 1-based + aModel.mnFirstRow = aModel.mnLastRow = maCurrPos.mnRow + 1; + // row height is in twips in OOBIN, convert to points + aModel.mfHeight = nHeight / 20.0; + aModel.mnLevel = extractValue< sal_Int32 >( nFlags1, 8, 3 ); + aModel.mbCustomHeight = getFlag( nFlags1, OOBIN_ROW_CUSTOMHEIGHT ); + aModel.mbCustomFormat = getFlag( nFlags1, OOBIN_ROW_CUSTOMFORMAT ); + aModel.mbShowPhonetic = getFlag( nFlags2, OOBIN_ROW_SHOWPHONETIC ); + aModel.mbHidden = getFlag( nFlags1, OOBIN_ROW_HIDDEN ); + aModel.mbCollapsed = getFlag( nFlags1, OOBIN_ROW_COLLAPSED ); + aModel.mbThickTop = getFlag( nFlags1, OOBIN_ROW_THICKTOP ); + aModel.mbThickBottom = getFlag( nFlags1, OOBIN_ROW_THICKBOTTOM ); + // set row properties in the current sheet + setRowModel( aModel ); +} + +void OoxSheetDataContext::importArray( RecordInputStream& rStrm ) +{ + BinRange aRange; + rStrm >> aRange; + CellRangeAddress aArrayRange; + Reference< XCellRange > xRange = getCellRange( aRange, &aArrayRange ); + Reference< XArrayFormulaTokens > xTokens( xRange, UNO_QUERY ); + if( xRange.is() && xTokens.is() ) + { + rStrm.skip( 1 ); + ArrayFormulaContext aContext( xTokens, aArrayRange ); + getFormulaParser().importFormula( aContext, rStrm ); + } +} + +void OoxSheetDataContext::importSharedFmla( RecordInputStream& rStrm ) +{ + getSharedFormulas().importSharedFmla( rStrm, maCurrCell.maAddress ); +} + +void OoxSheetDataContext::importDataTable( RecordInputStream& rStrm ) +{ + BinRange aRange; + rStrm >> aRange; + CellRangeAddress aTableRange; + if( getAddressConverter().convertToCellRange( aTableRange, aRange, getSheetIndex(), true, true ) ) + { + DataTableModel aModel; + BinAddress aRef1, aRef2; + sal_uInt8 nFlags; + rStrm >> aRef1 >> aRef2 >> nFlags; + aModel.maRef1 = FormulaProcessorBase::generateAddress2dString( aRef1, false ); + aModel.maRef2 = FormulaProcessorBase::generateAddress2dString( aRef2, false ); + aModel.mbRowTable = getFlag( nFlags, OOBIN_DATATABLE_ROW ); + aModel.mb2dTable = getFlag( nFlags, OOBIN_DATATABLE_2D ); + aModel.mbRef1Deleted = getFlag( nFlags, OOBIN_DATATABLE_REF1DEL ); + aModel.mbRef2Deleted = getFlag( nFlags, OOBIN_DATATABLE_REF2DEL ); + setTableOperation( aTableRange, aModel ); + } +} + +// ============================================================================ + +BiffSheetDataContext::BiffSheetDataContext( const BiffWorksheetFragmentBase& rParent ) : + BiffWorksheetContextBase( rParent ), + mnBiff2XfId( 0 ) +{ + mnArrayIgnoreSize = (getBiff() == BIFF2) ? 1 : ((getBiff() <= BIFF4) ? 2 : 6); + switch( getBiff() ) + { + case BIFF2: + mnFormulaIgnoreSize = 9; // double formula result, 1 byte flags + mnArrayIgnoreSize = 1; // recalc-always flag + break; + case BIFF3: + case BIFF4: + mnFormulaIgnoreSize = 10; // double formula result, 2 byte flags + mnArrayIgnoreSize = 2; // 2 byte flags + break; + case BIFF5: + case BIFF8: + mnFormulaIgnoreSize = 14; // double formula result, 2 byte flags, 4 bytes nothing + mnArrayIgnoreSize = 6; // 2 byte flags, 4 bytes nothing + break; + case BIFF_UNKNOWN: break; + } +} + +void BiffSheetDataContext::importRecord() +{ + sal_uInt16 nRecId = mrStrm.getRecId(); + switch( nRecId ) + { + // records in all BIFF versions + case BIFF2_ID_ARRAY: // #i72713# + case BIFF3_ID_ARRAY: importArray(); break; + case BIFF2_ID_BLANK: + case BIFF3_ID_BLANK: importBlank(); break; + case BIFF2_ID_BOOLERR: + case BIFF3_ID_BOOLERR: importBoolErr(); break; + case BIFF2_ID_INTEGER: importInteger(); break; + case BIFF_ID_IXFE: mrStrm >> mnBiff2XfId; break; + case BIFF2_ID_LABEL: + case BIFF3_ID_LABEL: importLabel(); break; + case BIFF2_ID_NUMBER: + case BIFF3_ID_NUMBER: importNumber(); break; + case BIFF_ID_RK: importRk(); break; + + // BIFF specific records + default: switch( getBiff() ) + { + case BIFF2: switch( nRecId ) + { + case BIFF2_ID_DATATABLE: importDataTable(); break; + case BIFF2_ID_DATATABLE2: importDataTable(); break; + case BIFF2_ID_FORMULA: importFormula(); break; + case BIFF2_ID_ROW: importRow(); break; + } + break; + + case BIFF3: switch( nRecId ) + { + case BIFF3_ID_DATATABLE: importDataTable(); break; + case BIFF3_ID_FORMULA: importFormula(); break; + case BIFF3_ID_ROW: importRow(); break; + } + break; + + case BIFF4: switch( nRecId ) + { + case BIFF3_ID_DATATABLE: importDataTable(); break; + case BIFF4_ID_FORMULA: importFormula(); break; + case BIFF3_ID_ROW: importRow(); break; + } + break; + + case BIFF5: switch( nRecId ) + { + case BIFF3_ID_DATATABLE: importDataTable(); break; + case BIFF3_ID_FORMULA: + case BIFF4_ID_FORMULA: + case BIFF5_ID_FORMULA: importFormula(); break; + case BIFF_ID_MULTBLANK: importMultBlank(); break; + case BIFF_ID_MULTRK: importMultRk(); break; + case BIFF3_ID_ROW: importRow(); break; + case BIFF_ID_RSTRING: importLabel(); break; + case BIFF_ID_SHAREDFMLA: importSharedFmla(); break; + } + break; + + case BIFF8: switch( nRecId ) + { + case BIFF3_ID_DATATABLE: importDataTable(); break; + case BIFF3_ID_FORMULA: + case BIFF4_ID_FORMULA: + case BIFF5_ID_FORMULA: importFormula(); break; + case BIFF_ID_LABELSST: importLabelSst(); break; + case BIFF_ID_MULTBLANK: importMultBlank(); break; + case BIFF_ID_MULTRK: importMultRk(); break; + case BIFF3_ID_ROW: importRow(); break; + case BIFF_ID_RSTRING: importLabel(); break; + case BIFF_ID_SHAREDFMLA: importSharedFmla(); break; + } + break; + + case BIFF_UNKNOWN: break; + } + } +} + +// private -------------------------------------------------------------------- + +void BiffSheetDataContext::setCurrCell( const BinAddress& rAddr ) +{ + maCurrCell.reset(); + maCurrCell.mxCell = getCell( rAddr, &maCurrCell.maAddress ); + // update used area of the sheet + if( maCurrCell.mxCell.is() ) + extendUsedArea( maCurrCell.maAddress ); +} + +void BiffSheetDataContext::importXfId( bool bBiff2 ) +{ + if( bBiff2 ) + { + sal_uInt8 nBiff2XfId; + mrStrm >> nBiff2XfId; + mrStrm.skip( 2 ); + maCurrCell.mnXfId = nBiff2XfId & BIFF2_XF_MASK; + if( maCurrCell.mnXfId == BIFF2_XF_EXTENDED_IDS ) + maCurrCell.mnXfId = mnBiff2XfId; + } + else + { + maCurrCell.mnXfId = mrStrm.readuInt16(); + } +} + +void BiffSheetDataContext::importCellHeader( bool bBiff2 ) +{ + BinAddress aAddr; + mrStrm >> aAddr; + setCurrCell( aAddr ); + importXfId( bBiff2 ); +} + +void BiffSheetDataContext::importBlank() +{ + importCellHeader( mrStrm.getRecId() == BIFF2_ID_BLANK ); + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importBoolErr() +{ + importCellHeader( mrStrm.getRecId() == BIFF2_ID_BOOLERR ); + if( maCurrCell.mxCell.is() ) + { + sal_uInt8 nValue, nType; + mrStrm >> nValue >> nType; + switch( nType ) + { + case BIFF_BOOLERR_BOOL: + maCurrCell.mnCellType = XML_b; + setBooleanCell( maCurrCell.mxCell, nValue != 0 ); + // #108770# set 'Standard' number format for all Boolean cells + maCurrCell.mnNumFmtId = 0; + break; + case BIFF_BOOLERR_ERROR: + maCurrCell.mnCellType = XML_e; + setErrorCell( maCurrCell.mxCell, nValue ); + break; + default: + OSL_ENSURE( false, "BiffSheetDataContext::importBoolErr - unknown cell type" ); + } + } + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importFormula() +{ + importCellHeader( getBiff() == BIFF2 ); + maCurrCell.mnCellType = XML_n; + Reference< XFormulaTokens > xTokens( maCurrCell.mxCell, UNO_QUERY ); + if( xTokens.is() ) + { + mrStrm.skip( mnFormulaIgnoreSize ); + ExtCellFormulaContext aContext( *this, xTokens, maCurrCell.maAddress ); + getFormulaParser().importFormula( aContext, mrStrm ); + } + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importInteger() +{ + importCellHeader( true ); + maCurrCell.mnCellType = XML_n; + if( maCurrCell.mxCell.is() ) + maCurrCell.mxCell->setValue( mrStrm.readuInt16() ); + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importLabel() +{ + bool bBiff2Xf = mrStrm.getRecId() == BIFF2_ID_LABEL; + importCellHeader( bBiff2Xf ); + maCurrCell.mnCellType = XML_inlineStr; + Reference< XText > xText( maCurrCell.mxCell, UNO_QUERY ); + if( xText.is() ) + { + /* the deep secrets of BIFF type and record identifier... + 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 */ + + RichString aString( *this ); + if( getBiff() == BIFF8 ) + { + aString.importUniString( mrStrm ); + } + else + { + // #i63105# use text encoding from FONT record + rtl_TextEncoding eTextEnc = getTextEncoding(); + if( const Font* pFont = getStyles().getFontFromCellXf( maCurrCell.mnXfId ).get() ) + eTextEnc = pFont->getFontEncoding(); + BiffStringFlags nFlags = bBiff2Xf ? BIFF_STR_8BITLENGTH : BIFF_STR_DEFAULT; + setFlag( nFlags, BIFF_STR_EXTRAFONTS, mrStrm.getRecId() == BIFF_ID_RSTRING ); + aString.importByteString( mrStrm, eTextEnc, nFlags ); + } + aString.finalizeImport(); + aString.convert( xText, maCurrCell.mnXfId ); + } + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importLabelSst() +{ + importCellHeader( false ); + maCurrCell.mnCellType = XML_s; + if( maCurrCell.mxCell.is() ) + setSharedStringCell( maCurrCell.mxCell, mrStrm.readInt32(), maCurrCell.mnXfId ); + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importMultBlank() +{ + BinAddress aAddr; + for( mrStrm >> aAddr; mrStrm.getRemaining() > 2; ++aAddr.mnCol ) + { + setCurrCell( aAddr ); + importXfId( false ); + setCellFormat( maCurrCell ); + } +} + +void BiffSheetDataContext::importMultRk() +{ + BinAddress aAddr; + for( mrStrm >> aAddr; mrStrm.getRemaining() > 2; ++aAddr.mnCol ) + { + setCurrCell( aAddr ); + maCurrCell.mnCellType = XML_n; + importXfId( false ); + sal_Int32 nRkValue = mrStrm.readInt32(); + if( maCurrCell.mxCell.is() ) + maCurrCell.mxCell->setValue( BiffHelper::calcDoubleFromRk( nRkValue ) ); + setCellFormat( maCurrCell ); + } +} + +void BiffSheetDataContext::importNumber() +{ + importCellHeader( mrStrm.getRecId() == BIFF2_ID_NUMBER ); + maCurrCell.mnCellType = XML_n; + if( maCurrCell.mxCell.is() ) + maCurrCell.mxCell->setValue( mrStrm.readDouble() ); + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importRk() +{ + importCellHeader( false ); + maCurrCell.mnCellType = XML_n; + if( maCurrCell.mxCell.is() ) + maCurrCell.mxCell->setValue( BiffHelper::calcDoubleFromRk( mrStrm.readInt32() ) ); + setCellFormat( maCurrCell ); +} + +void BiffSheetDataContext::importRow() +{ + RowModel aModel; + + sal_uInt16 nRow, nHeight; + mrStrm >> nRow; + mrStrm.skip( 4 ); + mrStrm >> nHeight; + if( getBiff() == BIFF2 ) + { + mrStrm.skip( 2 ); + aModel.mbCustomFormat = mrStrm.readuInt8() == BIFF2_ROW_CUSTOMFORMAT; + if( aModel.mbCustomFormat ) + { + mrStrm.skip( 5 ); + aModel.mnXfId = mrStrm.readuInt16(); + } + } + else + { + mrStrm.skip( 4 ); + sal_uInt32 nFlags = mrStrm.readuInt32(); + aModel.mnXfId = extractValue< sal_Int32 >( nFlags, 16, 12 ); + aModel.mnLevel = extractValue< sal_Int32 >( nFlags, 0, 3 ); + aModel.mbCustomFormat = getFlag( nFlags, BIFF_ROW_CUSTOMFORMAT ); + aModel.mbCustomHeight = getFlag( nFlags, BIFF_ROW_CUSTOMHEIGHT ); + aModel.mbShowPhonetic = getFlag( nFlags, BIFF_ROW_SHOWPHONETIC ); + aModel.mbHidden = getFlag( nFlags, BIFF_ROW_HIDDEN ); + aModel.mbCollapsed = getFlag( nFlags, BIFF_ROW_COLLAPSED ); + aModel.mbThickTop = getFlag( nFlags, BIFF_ROW_THICKTOP ); + aModel.mbThickBottom = getFlag( nFlags, BIFF_ROW_THICKBOTTOM ); + } + + // row index is 0-based in BIFF, but RowModel expects 1-based + aModel.mnFirstRow = aModel.mnLastRow = nRow + 1; + // row height is in twips in BIFF, convert to points + aModel.mfHeight = (nHeight & BIFF_ROW_HEIGHTMASK) / 20.0; + // set row properties in the current sheet + setRowModel( aModel ); +} + +void BiffSheetDataContext::importArray() +{ + BinRange aRange; + aRange.read( mrStrm, false ); // columns always 8-bit + CellRangeAddress aArrayRange; + Reference< XCellRange > xRange = getCellRange( aRange, &aArrayRange ); + Reference< XArrayFormulaTokens > xTokens( xRange, UNO_QUERY ); + if( xRange.is() && xTokens.is() ) + { + mrStrm.skip( mnArrayIgnoreSize ); + ArrayFormulaContext aContext( xTokens, aArrayRange ); + getFormulaParser().importFormula( aContext, mrStrm ); + } +} + +void BiffSheetDataContext::importSharedFmla() +{ + getSharedFormulas().importSharedFmla( mrStrm, maCurrCell.maAddress ); +} + +void BiffSheetDataContext::importDataTable() +{ + BinRange aRange; + aRange.read( mrStrm, false ); // columns always 8-bit + CellRangeAddress aTableRange; + if( getAddressConverter().convertToCellRange( aTableRange, aRange, getSheetIndex(), true, true ) ) + { + DataTableModel aModel; + BinAddress aRef1, aRef2; + switch( mrStrm.getRecId() ) + { + case BIFF2_ID_DATATABLE: + mrStrm.skip( 1 ); + aModel.mbRowTable = mrStrm.readuInt8() != 0; + aModel.mb2dTable = false; + mrStrm >> aRef1; + break; + case BIFF2_ID_DATATABLE2: + mrStrm.skip( 2 ); + aModel.mb2dTable = true; + mrStrm >> aRef1 >> aRef2; + break; + case BIFF3_ID_DATATABLE: + { + sal_uInt16 nFlags; + mrStrm >> nFlags >> aRef1 >> aRef2; + aModel.mbRowTable = getFlag( nFlags, BIFF_DATATABLE_ROW ); + aModel.mb2dTable = getFlag( nFlags, BIFF_DATATABLE_2D ); + aModel.mbRef1Deleted = getFlag( nFlags, BIFF_DATATABLE_REF1DEL ); + aModel.mbRef2Deleted = getFlag( nFlags, BIFF_DATATABLE_REF2DEL ); + } + break; + default: + OSL_ENSURE( false, "BiffSheetDataContext::importDataTable - unknown record id" ); + } + aModel.maRef1 = FormulaProcessorBase::generateAddress2dString( aRef1, false ); + aModel.maRef2 = FormulaProcessorBase::generateAddress2dString( aRef2, false ); + setTableOperation( aTableRange, aModel ); + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/stylesbuffer.cxx b/oox/source/xls/stylesbuffer.cxx new file mode 100644 index 000000000000..5a149961c0e2 --- /dev/null +++ b/oox/source/xls/stylesbuffer.cxx @@ -0,0 +1,3447 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/stylesbuffer.hxx" +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/FontFamily.hpp> +#include <com/sun/star/awt/FontPitch.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontType.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/awt/XFont2.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XText.hpp> +#include <rtl/tencinfo.h> +#include <rtl/ustrbuf.hxx> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertymap.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/condformatbuffer.hxx" +#include "oox/xls/excelhandlers.hxx" +#include "oox/xls/themebuffer.hxx" +#include "oox/xls/unitconverter.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +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::container::XIndexAccess; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::container::XNamed; +using ::com::sun::star::awt::FontDescriptor; +using ::com::sun::star::awt::XDevice; +using ::com::sun::star::awt::XFont2; +using ::com::sun::star::table::BorderLine; +using ::com::sun::star::table::TableBorder; +using ::com::sun::star::text::XText; +using ::com::sun::star::style::XStyle; +using ::oox::core::FilterBase; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +// OOXML constants ------------------------------------------------------------ + +// OOX predefined color indexes (also used in BIFF3-BIFF8) +const sal_Int32 OOX_COLOR_USEROFFSET = 0; /// First user defined color in palette (OOX). +const sal_Int32 BIFF_COLOR_USEROFFSET = 8; /// First user defined color in palette (BIFF). + +// OOX font family (also used in BIFF) +const sal_Int32 OOX_FONTFAMILY_NONE = 0; +const sal_Int32 OOX_FONTFAMILY_ROMAN = 1; +const sal_Int32 OOX_FONTFAMILY_SWISS = 2; +const sal_Int32 OOX_FONTFAMILY_MODERN = 3; +const sal_Int32 OOX_FONTFAMILY_SCRIPT = 4; +const sal_Int32 OOX_FONTFAMILY_DECORATIVE = 5; + +// OOX cell text direction (also used in BIFF) +const sal_Int32 OOX_XF_TEXTDIR_CONTEXT = 0; +const sal_Int32 OOX_XF_TEXTDIR_LTR = 1; +const sal_Int32 OOX_XF_TEXTDIR_RTL = 2; + +// OOX cell rotation (also used in BIFF) +const sal_Int32 OOX_XF_ROTATION_NONE = 0; +const sal_Int32 OOX_XF_ROTATION_90CCW = 90; +const sal_Int32 OOX_XF_ROTATION_90CW = 180; +const sal_Int32 OOX_XF_ROTATION_STACKED = 255; + +// OOX cell indentation +const sal_Int32 OOX_XF_INDENT_NONE = 0; + +// OOX built-in cell styles (also used in BIFF) +const sal_Int32 OOX_STYLE_NORMAL = 0; /// Default cell style. +const sal_Int32 OOX_STYLE_ROWLEVEL = 1; /// RowLevel_x cell style. +const sal_Int32 OOX_STYLE_COLLEVEL = 2; /// ColLevel_x cell style. + +const sal_Int32 OOX_STYLE_LEVELCOUNT = 7; /// Number of outline level styles. + +// OOBIN constants ------------------------------------------------------------ + +// OOBIN color types +const sal_uInt8 OOBIN_COLOR_AUTO = 0; +const sal_uInt8 OOBIN_COLOR_INDEXED = 1; +const sal_uInt8 OOBIN_COLOR_RGB = 2; +const sal_uInt8 OOBIN_COLOR_THEME = 3; + +// OOBIN diagonal borders +const sal_uInt8 OOBIN_BORDER_DIAG_TLBR = 0x01; /// Top-left to bottom-right. +const sal_uInt8 OOBIN_BORDER_DIAG_BLTR = 0x02; /// Bottom-left to top-right. + +// OOBIN gradient fill +const sal_Int32 OOBIN_FILL_GRADIENT = 40; + +// OOBIN XF flags +const sal_uInt32 OOBIN_XF_WRAPTEXT = 0x00400000; +const sal_uInt32 OOBIN_XF_JUSTLASTLINE = 0x00800000; +const sal_uInt32 OOBIN_XF_SHRINK = 0x01000000; +const sal_uInt32 OOBIN_XF_LOCKED = 0x10000000; +const sal_uInt32 OOBIN_XF_HIDDEN = 0x20000000; + +// OOBIN XF attribute used flags +const sal_uInt16 OOBIN_XF_NUMFMT_USED = 0x0001; +const sal_uInt16 OOBIN_XF_FONT_USED = 0x0002; +const sal_uInt16 OOBIN_XF_ALIGN_USED = 0x0004; +const sal_uInt16 OOBIN_XF_BORDER_USED = 0x0008; +const sal_uInt16 OOBIN_XF_AREA_USED = 0x0010; +const sal_uInt16 OOBIN_XF_PROT_USED = 0x0020; + +// OOBIN DXF constants +const sal_uInt16 OOBIN_DXF_FILL_PATTERN = 0; +const sal_uInt16 OOBIN_DXF_FILL_FGCOLOR = 1; +const sal_uInt16 OOBIN_DXF_FILL_BGCOLOR = 2; +const sal_uInt16 OOBIN_DXF_FILL_GRADIENT = 3; +const sal_uInt16 OOBIN_DXF_FILL_STOP = 4; +const sal_uInt16 OOBIN_DXF_FONT_COLOR = 5; +const sal_uInt16 OOBIN_DXF_BORDER_TOP = 6; +const sal_uInt16 OOBIN_DXF_BORDER_BOTTOM = 7; +const sal_uInt16 OOBIN_DXF_BORDER_LEFT = 8; +const sal_uInt16 OOBIN_DXF_BORDER_RIGHT = 9; +const sal_uInt16 OOBIN_DXF_BORDER_DIAG = 10; +const sal_uInt16 OOBIN_DXF_BORDER_VERT = 11; +const sal_uInt16 OOBIN_DXF_BORDER_HOR = 12; +const sal_uInt16 OOBIN_DXF_BORDER_DIAGUP = 13; +const sal_uInt16 OOBIN_DXF_BORDER_DIAGDOWN = 14; +const sal_uInt16 OOBIN_DXF_FONT_NAME = 24; +const sal_uInt16 OOBIN_DXF_FONT_WEIGHT = 25; +const sal_uInt16 OOBIN_DXF_FONT_UNDERLINE = 26; +const sal_uInt16 OOBIN_DXF_FONT_ESCAPEMENT = 27; +const sal_uInt16 OOBIN_DXF_FONT_ITALIC = 28; +const sal_uInt16 OOBIN_DXF_FONT_STRIKE = 29; +const sal_uInt16 OOBIN_DXF_FONT_OUTLINE = 30; +const sal_uInt16 OOBIN_DXF_FONT_SHADOW = 31; +const sal_uInt16 OOBIN_DXF_FONT_CONDENSE = 32; +const sal_uInt16 OOBIN_DXF_FONT_EXTEND = 33; +const sal_uInt16 OOBIN_DXF_FONT_CHARSET = 34; +const sal_uInt16 OOBIN_DXF_FONT_FAMILY = 35; +const sal_uInt16 OOBIN_DXF_FONT_HEIGHT = 36; +const sal_uInt16 OOBIN_DXF_FONT_SCHEME = 37; +const sal_uInt16 OOBIN_DXF_NUMFMT_CODE = 38; +const sal_uInt16 OOBIN_DXF_NUMFMT_ID = 41; + +// OOBIN CELLSTYLE flags +const sal_uInt16 OOBIN_CELLSTYLE_BUILTIN = 0x0001; +const sal_uInt16 OOBIN_CELLSTYLE_HIDDEN = 0x0002; +const sal_uInt16 OOBIN_CELLSTYLE_CUSTOM = 0x0004; + +// OOBIN and BIFF constants --------------------------------------------------- + +// BIFF predefined color indexes +const sal_uInt16 BIFF2_COLOR_BLACK = 0; /// Black (text) in BIFF2. +const sal_uInt16 BIFF2_COLOR_WHITE = 1; /// White (background) in BIFF2. + +// BIFF font flags, also used in OOBIN +const sal_uInt16 BIFF_FONTFLAG_BOLD = 0x0001; +const sal_uInt16 BIFF_FONTFLAG_ITALIC = 0x0002; +const sal_uInt16 BIFF_FONTFLAG_UNDERLINE = 0x0004; +const sal_uInt16 BIFF_FONTFLAG_STRIKEOUT = 0x0008; +const sal_uInt16 BIFF_FONTFLAG_OUTLINE = 0x0010; +const sal_uInt16 BIFF_FONTFLAG_SHADOW = 0x0020; +const sal_uInt16 BIFF_FONTFLAG_CONDENSE = 0x0040; + +// BIFF font weight +const sal_uInt16 BIFF_FONTWEIGHT_BOLD = 450; + +// BIFF font underline, also used in OOBIN +const sal_uInt8 BIFF_FONTUNDERL_NONE = 0; +const sal_uInt8 BIFF_FONTUNDERL_SINGLE = 1; +const sal_uInt8 BIFF_FONTUNDERL_DOUBLE = 2; +const sal_uInt8 BIFF_FONTUNDERL_SINGLE_ACC = 33; +const sal_uInt8 BIFF_FONTUNDERL_DOUBLE_ACC = 34; + +// BIFF XF flags +const sal_uInt16 BIFF_XF_LOCKED = 0x0001; +const sal_uInt16 BIFF_XF_HIDDEN = 0x0002; +const sal_uInt16 BIFF_XF_STYLE = 0x0004; +const sal_uInt16 BIFF_XF_STYLEPARENT = 0x0FFF; /// Syles don't have a parent. +const sal_uInt16 BIFF_XF_WRAPTEXT = 0x0008; /// Automatic line break. +const sal_uInt16 BIFF_XF_JUSTLASTLINE = 0x0080; +const sal_uInt16 BIFF_XF_SHRINK = 0x0010; /// Shrink to fit into cell. +const sal_uInt16 BIFF_XF_MERGE = 0x0020; + +// BIFF XF attribute used flags +const sal_uInt8 BIFF_XF_NUMFMT_USED = 0x01; +const sal_uInt8 BIFF_XF_FONT_USED = 0x02; +const sal_uInt8 BIFF_XF_ALIGN_USED = 0x04; +const sal_uInt8 BIFF_XF_BORDER_USED = 0x08; +const sal_uInt8 BIFF_XF_AREA_USED = 0x10; +const sal_uInt8 BIFF_XF_PROT_USED = 0x20; + +// BIFF XF text orientation +const sal_uInt8 BIFF_XF_ORIENT_NONE = 0; +const sal_uInt8 BIFF_XF_ORIENT_STACKED = 1; /// Stacked top to bottom. +const sal_uInt8 BIFF_XF_ORIENT_90CCW = 2; /// 90 degr. counterclockwise. +const sal_uInt8 BIFF_XF_ORIENT_90CW = 3; /// 90 degr. clockwise. + +// BIFF XF line styles +const sal_uInt8 BIFF_LINE_NONE = 0; +const sal_uInt8 BIFF_LINE_THIN = 1; + +// BIFF XF patterns +const sal_uInt8 BIFF_PATT_NONE = 0; +const sal_uInt8 BIFF_PATT_125 = 17; + +// BIFF2 XF flags +const sal_uInt8 BIFF2_XF_VALFMT_MASK = 0x3F; +const sal_uInt8 BIFF2_XF_LOCKED = 0x40; +const sal_uInt8 BIFF2_XF_HIDDEN = 0x80; +const sal_uInt8 BIFF2_XF_LEFTLINE = 0x08; +const sal_uInt8 BIFF2_XF_RIGHTLINE = 0x10; +const sal_uInt8 BIFF2_XF_TOPLINE = 0x20; +const sal_uInt8 BIFF2_XF_BOTTOMLINE = 0x40; +const sal_uInt8 BIFF2_XF_BACKGROUND = 0x80; + +// BIFF8 diagonal borders +const sal_uInt32 BIFF_XF_DIAG_TLBR = 0x40000000; /// Top-left to bottom-right. +const sal_uInt32 BIFF_XF_DIAG_BLTR = 0x80000000; /// Bottom-left to top-right. + +// BIFF STYLE flags +const sal_uInt16 BIFF_STYLE_BUILTIN = 0x8000; +const sal_uInt16 BIFF_STYLE_XFMASK = 0x0FFF; + +// BIFF STYLEEXT flags +const sal_uInt8 BIFF_STYLEEXT_BUILTIN = 0x01; +const sal_uInt8 BIFF_STYLEEXT_HIDDEN = 0x02; +const sal_uInt8 BIFF_STYLEEXT_CUSTOM = 0x04; + +// BIFF conditional formatting +const sal_uInt32 BIFF_CFRULE_BORDER_LEFT = 0x00000400; +const sal_uInt32 BIFF_CFRULE_BORDER_RIGHT = 0x00000800; +const sal_uInt32 BIFF_CFRULE_BORDER_TOP = 0x00001000; +const sal_uInt32 BIFF_CFRULE_BORDER_BOTTOM = 0x00002000; +const sal_uInt32 BIFF_CFRULE_FILL_PATTERN = 0x00010000; +const sal_uInt32 BIFF_CFRULE_FILL_PATTCOLOR = 0x00020000; +const sal_uInt32 BIFF_CFRULE_FILL_FILLCOLOR = 0x00040000; +const sal_uInt32 BIFF_CFRULE_FONTBLOCK = 0x04000000; +const sal_uInt32 BIFF_CFRULE_ALIGNBLOCK = 0x08000000; +const sal_uInt32 BIFF_CFRULE_BORDERBLOCK = 0x10000000; +const sal_uInt32 BIFF_CFRULE_FILLBLOCK = 0x20000000; +const sal_uInt32 BIFF_CFRULE_PROTBLOCK = 0x40000000; + +const sal_uInt32 BIFF_CFRULE_FONT_STYLE = 0x00000002; /// Font posture or weight modified? +const sal_uInt32 BIFF_CFRULE_FONT_OUTLINE = 0x00000008; /// Font outline modified? +const sal_uInt32 BIFF_CFRULE_FONT_SHADOW = 0x00000010; /// Font shadow modified? +const sal_uInt32 BIFF_CFRULE_FONT_STRIKEOUT = 0x00000080; /// Font cancellation modified? +const sal_uInt32 BIFF_CFRULE_FONT_UNDERL = 0x00000001; /// Font underline type modified? +const sal_uInt32 BIFF_CFRULE_FONT_ESCAPEM = 0x00000001; /// Font escapement type modified? + +// ---------------------------------------------------------------------------- + +sal_Int32 lclReadRgbColor( BinaryInputStream& rStrm ) +{ + sal_uInt8 nR, nG, nB, nA; + rStrm >> nR >> nG >> nB >> nA; + sal_Int32 nValue = nA; + nValue <<= 8; + nValue |= nR; + nValue <<= 8; + nValue |= nG; + nValue <<= 8; + nValue |= nB; + return nValue; +} + +} // namespace + +// ============================================================================ + +ExcelGraphicHelper::ExcelGraphicHelper( const WorkbookHelper& rHelper ) : + GraphicHelper( rHelper.getGlobalFactory(), rHelper.getBaseFilter().getTargetFrame(), rHelper.getBaseFilter().getStorage() ), + WorkbookHelper( rHelper ) +{ +} + +sal_Int32 ExcelGraphicHelper::getSchemeColor( sal_Int32 nToken ) const +{ + if( getFilterType() == FILTER_OOX ) + return getTheme().getColorByToken( nToken ); + return GraphicHelper::getSchemeColor( nToken ); +} + +sal_Int32 ExcelGraphicHelper::getPaletteColor( sal_Int32 nPaletteIdx ) const +{ + return getStyles().getPaletteColor( nPaletteIdx ); +} + +// ============================================================================ + +void Color::setAuto() +{ + clearTransformations(); + setSchemeClr( XML_phClr ); +} + +void Color::setRgb( sal_Int32 nRgbValue, double fTint ) +{ + clearTransformations(); + setSrgbClr( nRgbValue & 0xFFFFFF ); + if( fTint != 0.0 ) addExcelTintTransformation( fTint ); +} + +void Color::setTheme( sal_Int32 nThemeIdx, double fTint ) +{ + clearTransformations(); + static const sal_Int32 spnColorTokens[] = { + XML_lt1, XML_dk1, XML_lt2, XML_dk2, XML_accent1, XML_accent2, + XML_accent3, XML_accent4, XML_accent5, XML_accent6, XML_hlink, XML_folHlink }; + setSchemeClr( STATIC_ARRAY_SELECT( spnColorTokens, nThemeIdx, XML_TOKEN_INVALID ) ); + if( fTint != 0.0 ) addExcelTintTransformation( fTint ); +} + +void Color::setIndexed( sal_Int32 nPaletteIdx, double fTint ) +{ + clearTransformations(); + setPaletteClr( nPaletteIdx ); + if( fTint != 0.0 ) addExcelTintTransformation( fTint ); +} + +void Color::importColor( const AttributeList& rAttribs ) +{ + if( rAttribs.getBool( XML_auto, false ) ) + setAuto(); + else if( rAttribs.hasAttribute( XML_rgb ) ) + setRgb( rAttribs.getIntegerHex( XML_rgb, API_RGB_TRANSPARENT ), rAttribs.getDouble( XML_tint, 0.0 ) ); + else if( rAttribs.hasAttribute( XML_theme ) ) + setTheme( rAttribs.getInteger( XML_theme, -1 ), rAttribs.getDouble( XML_tint, 0.0 ) ); + else if( rAttribs.hasAttribute( XML_indexed ) ) + setIndexed( rAttribs.getInteger( XML_indexed, -1 ), rAttribs.getDouble( XML_tint, 0.0 ) ); + else + { + OSL_ENSURE( false, "Color::importColor - unknown color type" ); + setAuto(); + } +} + +void Color::importColor( RecordInputStream& rStrm ) +{ + sal_uInt8 nFlags, nIndex; + sal_Int16 nTint; + rStrm >> nFlags >> nIndex >> nTint; + + // scale tint from signed 16-bit to double range -1.0 ... 1.0 + double fTint = nTint; + if( nTint < 0 ) + fTint /= -SAL_MIN_INT16; + else if( nTint > 0 ) + fTint /= SAL_MAX_INT16; + + switch( extractValue< sal_uInt8 >( nFlags, 1, 7 ) ) + { + case OOBIN_COLOR_AUTO: + setAuto(); + rStrm.skip( 4 ); + break; + case OOBIN_COLOR_INDEXED: + setIndexed( nIndex, fTint ); + rStrm.skip( 4 ); + break; + case OOBIN_COLOR_RGB: + setRgb( lclReadRgbColor( rStrm ), fTint ); + break; + case OOBIN_COLOR_THEME: + setTheme( nIndex, fTint ); + rStrm.skip( 4 ); + break; + default: + OSL_ENSURE( false, "Color::importColor - unknown color type" ); + setAuto(); + rStrm.skip( 4 ); + } +} + +void Color::importColorId( RecordInputStream& rStrm ) +{ + setIndexed( rStrm.readInt32() ); +} + +void Color::importColorRgb( RecordInputStream& rStrm ) +{ + setRgb( lclReadRgbColor( rStrm ) ); +} + +void Color::importColorId( BiffInputStream& rStrm, bool b16Bit ) +{ + setIndexed( b16Bit ? rStrm.readuInt16() : rStrm.readuInt8() ); +} + +void Color::importColorRgb( BiffInputStream& rStrm ) +{ + setRgb( lclReadRgbColor( rStrm ) ); +} + +RecordInputStream& operator>>( RecordInputStream& rStrm, Color& orColor ) +{ + orColor.importColor( rStrm ); + return rStrm; +} + +// ============================================================================ + +namespace { + +/** Standard EGA colors, bright. */ +#define PALETTE_EGA_COLORS_LIGHT \ + 0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF +/** Standard EGA colors, dark. */ +#define PALETTE_EGA_COLORS_DARK \ + 0x800000, 0x008000, 0x000080, 0x808000, 0x800080, 0x008080, 0xC0C0C0, 0x808080 + +/** Default color table for BIFF2. */ +static const sal_Int32 spnDefColors2[] = +{ +/* 0 */ PALETTE_EGA_COLORS_LIGHT +}; + +/** Default color table for BIFF3/BIFF4. */ +static const sal_Int32 spnDefColors3[] = +{ +/* 0 */ PALETTE_EGA_COLORS_LIGHT, +/* 8 */ PALETTE_EGA_COLORS_LIGHT, +/* 16 */ PALETTE_EGA_COLORS_DARK +}; + +/** Default color table for BIFF5. */ +static const sal_Int32 spnDefColors5[] = +{ +/* 0 */ PALETTE_EGA_COLORS_LIGHT, +/* 8 */ PALETTE_EGA_COLORS_LIGHT, +/* 16 */ PALETTE_EGA_COLORS_DARK, +/* 24 */ 0x8080FF, 0x802060, 0xFFFFC0, 0xA0E0E0, 0x600080, 0xFF8080, 0x0080C0, 0xC0C0FF, +/* 32 */ 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, 0x800080, 0x800000, 0x008080, 0x0000FF, +/* 40 */ 0x00CFFF, 0x69FFFF, 0xE0FFE0, 0xFFFF80, 0xA6CAF0, 0xDD9CB3, 0xB38FEE, 0xE3E3E3, +/* 48 */ 0x2A6FF9, 0x3FB8CD, 0x488436, 0x958C41, 0x8E5E42, 0xA0627A, 0x624FAC, 0x969696, +/* 56 */ 0x1D2FBE, 0x286676, 0x004500, 0x453E01, 0x6A2813, 0x85396A, 0x4A3285, 0x424242 +}; + +/** Default color table for BIFF8/OOX. */ +static const sal_Int32 spnDefColors8[] = +{ +/* 0 */ PALETTE_EGA_COLORS_LIGHT, +/* 8 */ PALETTE_EGA_COLORS_LIGHT, +/* 16 */ PALETTE_EGA_COLORS_DARK, +/* 24 */ 0x9999FF, 0x993366, 0xFFFFCC, 0xCCFFFF, 0x660066, 0xFF8080, 0x0066CC, 0xCCCCFF, +/* 32 */ 0x000080, 0xFF00FF, 0xFFFF00, 0x00FFFF, 0x800080, 0x800000, 0x008080, 0x0000FF, +/* 40 */ 0x00CCFF, 0xCCFFFF, 0xCCFFCC, 0xFFFF99, 0x99CCFF, 0xFF99CC, 0xCC99FF, 0xFFCC99, +/* 48 */ 0x3366FF, 0x33CCCC, 0x99CC00, 0xFFCC00, 0xFF9900, 0xFF6600, 0x666699, 0x969696, +/* 56 */ 0x003366, 0x339966, 0x003300, 0x333300, 0x993300, 0x993366, 0x333399, 0x333333 +}; + +#undef PALETTE_EGA_COLORS_LIGHT +#undef PALETTE_EGA_COLORS_DARK + +} // namespace + +// ---------------------------------------------------------------------------- + +ColorPalette::ColorPalette( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ + // default colors + switch( getFilterType() ) + { + case FILTER_OOX: + maColors.insert( maColors.begin(), spnDefColors8, STATIC_ARRAY_END( spnDefColors8 ) ); + mnAppendIndex = OOX_COLOR_USEROFFSET; + break; + case FILTER_BIFF: + switch( getBiff() ) + { + case BIFF2: maColors.insert( maColors.begin(), spnDefColors2, STATIC_ARRAY_END( spnDefColors2 ) ); break; + case BIFF3: + case BIFF4: maColors.insert( maColors.begin(), spnDefColors3, STATIC_ARRAY_END( spnDefColors3 ) ); break; + case BIFF5: maColors.insert( maColors.begin(), spnDefColors5, STATIC_ARRAY_END( spnDefColors5 ) ); break; + case BIFF8: maColors.insert( maColors.begin(), spnDefColors8, STATIC_ARRAY_END( spnDefColors8 ) ); break; + case BIFF_UNKNOWN: break; + } + mnAppendIndex = BIFF_COLOR_USEROFFSET; + break; + case FILTER_UNKNOWN: break; + } +} + +void ColorPalette::importPaletteColor( const AttributeList& rAttribs ) +{ + appendColor( rAttribs.getIntegerHex( XML_rgb, API_RGB_WHITE ) ); +} + +void ColorPalette::importPaletteColor( RecordInputStream& rStrm ) +{ + sal_Int32 nRgb = lclReadRgbColor( rStrm ); + appendColor( nRgb & 0xFFFFFF ); +} + +void ColorPalette::importPalette( BiffInputStream& rStrm ) +{ + sal_uInt16 nCount; + rStrm >> nCount; + OSL_ENSURE( rStrm.getRemaining() == 4 * nCount, "ColorPalette::importPalette - wrong palette size" ); + + // fill palette from BIFF_COLOR_USEROFFSET + mnAppendIndex = BIFF_COLOR_USEROFFSET; + for( sal_uInt16 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex ) + { + sal_Int32 nRgb = lclReadRgbColor( rStrm ); + appendColor( nRgb & 0xFFFFFF ); + } +} + +sal_Int32 ColorPalette::getColor( sal_Int32 nPaletteIdx ) const +{ + sal_Int32 nColor = API_RGB_TRANSPARENT; + if( const sal_Int32* pnPaletteColor = ContainerHelper::getVectorElement( maColors, nPaletteIdx ) ) + { + nColor = *pnPaletteColor; + } + else switch( nPaletteIdx ) + { + case OOX_COLOR_WINDOWTEXT3: + case OOX_COLOR_WINDOWTEXT: + case OOX_COLOR_CHWINDOWTEXT: nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_windowText ); break; + case OOX_COLOR_WINDOWBACK3: + case OOX_COLOR_WINDOWBACK: + case OOX_COLOR_CHWINDOWBACK: nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_window ); break; + case OOX_COLOR_BUTTONBACK: nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_btnFace ); break; + case OOX_COLOR_CHBORDERAUTO: nColor = API_RGB_BLACK; /* really always black? */ break; + case OOX_COLOR_NOTEBACK: nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_infoBk ); break; + case OOX_COLOR_NOTETEXT: nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_infoText ); break; + case OOX_COLOR_FONTAUTO: nColor = API_RGB_TRANSPARENT; break; + default: OSL_ENSURE( false, "ColorPalette::getColor - unknown color index" ); + } + return nColor; +} + +void ColorPalette::appendColor( sal_Int32 nRGBValue ) +{ + if( mnAppendIndex < maColors.size() ) + maColors[ mnAppendIndex ] = nRGBValue; + else + maColors.push_back( nRGBValue ); + ++mnAppendIndex; +} + +// ============================================================================ + +namespace { + +void lclSetFontName( ApiScriptFontName& rFontName, const FontDescriptor& rFontDesc, bool bHasGlyphs ) +{ + if( bHasGlyphs ) + { + rFontName.maName = rFontDesc.Name; + rFontName.mnFamily = rFontDesc.Family; + // API font descriptor contains rtl_TextEncoding constants + rFontName.mnTextEnc = rFontDesc.CharSet; + } + else + { + rFontName = ApiScriptFontName(); + } +} + +} // namespace + +// ---------------------------------------------------------------------------- + +FontModel::FontModel() : + mnScheme( XML_none ), + mnFamily( OOX_FONTFAMILY_NONE ), + mnCharSet( WINDOWS_CHARSET_DEFAULT ), + mfHeight( 0.0 ), + mnUnderline( XML_none ), + mnEscapement( XML_baseline ), + mbBold( false ), + mbItalic( false ), + mbStrikeout( false ), + mbOutline( false ), + mbShadow( false ) +{ +} + +void FontModel::setBinScheme( sal_uInt8 nScheme ) +{ + static const sal_Int32 spnSchemes[] = { XML_none, XML_major, XML_minor }; + mnScheme = STATIC_ARRAY_SELECT( spnSchemes, nScheme, XML_none ); +} + +void FontModel::setBiffHeight( sal_uInt16 nHeight ) +{ + mfHeight = nHeight / 20.0; // convert twips to points +} + +void FontModel::setBiffWeight( sal_uInt16 nWeight ) +{ + mbBold = nWeight >= BIFF_FONTWEIGHT_BOLD; +} + +void FontModel::setBiffUnderline( sal_uInt16 nUnderline ) +{ + switch( nUnderline ) + { + case BIFF_FONTUNDERL_NONE: mnUnderline = XML_none; break; + case BIFF_FONTUNDERL_SINGLE: mnUnderline = XML_single; break; + case BIFF_FONTUNDERL_DOUBLE: mnUnderline = XML_double; break; + case BIFF_FONTUNDERL_SINGLE_ACC: mnUnderline = XML_singleAccounting; break; + case BIFF_FONTUNDERL_DOUBLE_ACC: mnUnderline = XML_doubleAccounting; break; + default: mnUnderline = XML_none; + } +} + +void FontModel::setBiffEscapement( sal_uInt16 nEscapement ) +{ + static const sal_Int32 spnEscapes[] = { XML_baseline, XML_superscript, XML_subscript }; + mnEscapement = STATIC_ARRAY_SELECT( spnEscapes, nEscapement, XML_baseline ); +} + +// ---------------------------------------------------------------------------- + +ApiFontUsedFlags::ApiFontUsedFlags( bool bAllUsed ) : + mbNameUsed( bAllUsed ), + mbColorUsed( bAllUsed ), + mbSchemeUsed( bAllUsed ), + mbHeightUsed( bAllUsed ), + mbUnderlineUsed( bAllUsed ), + mbEscapementUsed( bAllUsed ), + mbWeightUsed( bAllUsed ), + mbPostureUsed( bAllUsed ), + mbStrikeoutUsed( bAllUsed ), + mbOutlineUsed( bAllUsed ), + mbShadowUsed( bAllUsed ) +{ +} + +// ---------------------------------------------------------------------------- + +ApiScriptFontName::ApiScriptFontName() : + mnFamily( ::com::sun::star::awt::FontFamily::DONTKNOW ), + mnTextEnc( RTL_TEXTENCODING_DONTKNOW ) +{ +} + +// ---------------------------------------------------------------------------- + +ApiFontData::ApiFontData() : + maDesc( + CREATE_OUSTRING( "Calibri" ), + 220, // height 11 points + 0, + OUString(), + ::com::sun::star::awt::FontFamily::DONTKNOW, + RTL_TEXTENCODING_DONTKNOW, + ::com::sun::star::awt::FontPitch::DONTKNOW, + 100.0, + ::com::sun::star::awt::FontWeight::NORMAL, + ::com::sun::star::awt::FontSlant_NONE, + ::com::sun::star::awt::FontUnderline::NONE, + ::com::sun::star::awt::FontStrikeout::NONE, + 0.0, + sal_False, + sal_False, + ::com::sun::star::awt::FontType::DONTKNOW ), + mnColor( API_RGB_TRANSPARENT ), + mnEscapement( API_ESCAPE_NONE ), + mnEscapeHeight( API_ESCAPEHEIGHT_NONE ), + mbOutline( false ), + mbShadow( false ) +{ + maLatinFont.maName = maDesc.Name; +} + +// ============================================================================ + +Font::Font( const WorkbookHelper& rHelper, bool bDxf ) : + WorkbookHelper( rHelper ), + maModel( rHelper.getTheme().getDefaultFontModel() ), + maUsedFlags( !bDxf ), + mbDxf( bDxf ) +{ +} + +Font::Font( const WorkbookHelper& rHelper, const FontModel& rModel ) : + WorkbookHelper( rHelper ), + maModel( rModel ), + maUsedFlags( true ), + mbDxf( false ) +{ +} + +void Font::importAttribs( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + const FontModel& rDefModel = getTheme().getDefaultFontModel(); + switch( nElement ) + { + case XLS_TOKEN( name ): // when in <font> element + case XLS_TOKEN( rFont ): // when in <rPr> element + if( rAttribs.hasAttribute( XML_val ) ) + { + maModel.maName = rAttribs.getXString( XML_val, OUString() ); + maUsedFlags.mbNameUsed = true; + } + break; + case XLS_TOKEN( scheme ): + maModel.mnScheme = rAttribs.getToken( XML_val, rDefModel.mnScheme ); + break; + case XLS_TOKEN( family ): + maModel.mnFamily = rAttribs.getInteger( XML_val, rDefModel.mnFamily ); + break; + case XLS_TOKEN( charset ): + maModel.mnCharSet = rAttribs.getInteger( XML_val, rDefModel.mnCharSet ); + break; + case XLS_TOKEN( sz ): + maModel.mfHeight = rAttribs.getDouble( XML_val, rDefModel.mfHeight ); + maUsedFlags.mbHeightUsed = true; + break; + case XLS_TOKEN( color ): + maModel.maColor.importColor( rAttribs ); + maUsedFlags.mbColorUsed = true; + break; + case XLS_TOKEN( u ): + maModel.mnUnderline = rAttribs.getToken( XML_val, XML_single ); + maUsedFlags.mbUnderlineUsed = true; + break; + case XLS_TOKEN( vertAlign ): + maModel.mnEscapement = rAttribs.getToken( XML_val, XML_baseline ); + maUsedFlags.mbEscapementUsed = true; + break; + case XLS_TOKEN( b ): + maModel.mbBold = rAttribs.getBool( XML_val, true ); + maUsedFlags.mbWeightUsed = true; + break; + case XLS_TOKEN( i ): + maModel.mbItalic = rAttribs.getBool( XML_val, true ); + maUsedFlags.mbPostureUsed = true; + break; + case XLS_TOKEN( strike ): + maModel.mbStrikeout = rAttribs.getBool( XML_val, true ); + maUsedFlags.mbStrikeoutUsed = true; + break; + case XLS_TOKEN( outline ): + maModel.mbOutline = rAttribs.getBool( XML_val, true ); + maUsedFlags.mbOutlineUsed = true; + break; + case XLS_TOKEN( shadow ): + maModel.mbShadow = rAttribs.getBool( XML_val, true ); + maUsedFlags.mbShadowUsed = true; + break; + } +} + +void Font::importFont( RecordInputStream& rStrm ) +{ + OSL_ENSURE( !mbDxf, "Font::importFont - unexpected conditional formatting flag" ); + + sal_uInt16 nHeight, nFlags, nWeight, nEscapement; + sal_uInt8 nUnderline, nFamily, nCharSet, nScheme; + rStrm >> nHeight >> nFlags >> nWeight >> nEscapement >> nUnderline >> nFamily >> nCharSet; + rStrm.skip( 1 ); + rStrm >> maModel.maColor >> nScheme >> maModel.maName; + + // equal constants in BIFF and OOBIN for weight, underline, and escapement + maModel.setBinScheme( nScheme ); + maModel.setBiffHeight( nHeight ); + maModel.setBiffWeight( nWeight ); + maModel.setBiffUnderline( nUnderline ); + maModel.setBiffEscapement( nEscapement ); + maModel.mnFamily = nFamily; + maModel.mnCharSet = nCharSet; + // equal flags in BIFF and OOBIN + maModel.mbItalic = getFlag( nFlags, BIFF_FONTFLAG_ITALIC ); + maModel.mbStrikeout = getFlag( nFlags, BIFF_FONTFLAG_STRIKEOUT ); + maModel.mbOutline = getFlag( nFlags, BIFF_FONTFLAG_OUTLINE ); + maModel.mbShadow = getFlag( nFlags, BIFF_FONTFLAG_SHADOW ); +} + +void Font::importDxfName( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfName - missing conditional formatting flag" ); + maModel.maName = rStrm.readString( false ); + maUsedFlags.mbColorUsed = true; +} + +void Font::importDxfColor( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfColor - missing conditional formatting flag" ); + rStrm >> maModel.maColor; + maUsedFlags.mbColorUsed = true; +} + +void Font::importDxfScheme( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfScheme - missing conditional formatting flag" ); + maModel.setBinScheme( rStrm.readuInt8() ); + maUsedFlags.mbSchemeUsed = true; +} + +void Font::importDxfHeight( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfHeight - missing conditional formatting flag" ); + maModel.setBiffHeight( rStrm.readuInt16() ); + maUsedFlags.mbHeightUsed = true; +} + +void Font::importDxfWeight( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfWeight - missing conditional formatting flag" ); + maModel.setBiffWeight( rStrm.readuInt16() ); + maUsedFlags.mbWeightUsed = true; +} + +void Font::importDxfUnderline( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfUnderline - missing conditional formatting flag" ); + maModel.setBiffUnderline( rStrm.readuInt16() ); + maUsedFlags.mbUnderlineUsed = true; +} + +void Font::importDxfEscapement( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfEscapement - missing conditional formatting flag" ); + maModel.setBiffEscapement( rStrm.readuInt16() ); + maUsedFlags.mbEscapementUsed = true; +} + +void Font::importDxfFlag( sal_Int32 nElement, RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importDxfFlag - missing conditional formatting flag" ); + bool bFlag = rStrm.readuInt8() != 0; + switch( nElement ) + { + case XML_i: + maModel.mbItalic = bFlag; + maUsedFlags.mbPostureUsed = true; + break; + case XML_strike: + maModel.mbStrikeout = bFlag; + maUsedFlags.mbStrikeoutUsed = true; + break; + case XML_outline: + maModel.mbOutline = bFlag; + maUsedFlags.mbOutlineUsed = true; + break; + case XML_shadow: + maModel.mbShadow = bFlag; + maUsedFlags.mbShadowUsed = true; + break; + default: + OSL_ENSURE( false, "Font::importDxfFlag - unexpected element identifier" ); + } +} + +void Font::importFont( BiffInputStream& rStrm ) +{ + OSL_ENSURE( !mbDxf, "Font::importFont - unexpected conditional formatting flag" ); + switch( getBiff() ) + { + case BIFF2: + importFontData2( rStrm ); + importFontName2( rStrm ); + break; + case BIFF3: + case BIFF4: + importFontData2( rStrm ); + importFontColor( rStrm ); + importFontName2( rStrm ); + break; + case BIFF5: + importFontData2( rStrm ); + importFontColor( rStrm ); + importFontData5( rStrm ); + importFontName2( rStrm ); + break; + case BIFF8: + importFontData2( rStrm ); + importFontColor( rStrm ); + importFontData5( rStrm ); + importFontName8( rStrm ); + break; + case BIFF_UNKNOWN: break; + } +} + +void Font::importFontColor( BiffInputStream& rStrm ) +{ + OSL_ENSURE( !mbDxf, "Font::importFontColor - unexpected conditional formatting flag" ); + maModel.maColor.importColorId( rStrm ); +} + +void Font::importCfRule( BiffInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Font::importCfRule - missing conditional formatting flag" ); + + sal_Int32 nHeight, nColor; + sal_uInt32 nStyle, nFontFlags1, nFontFlags2, nFontFlags3; + sal_uInt16 nWeight, nEscapement; + sal_uInt8 nUnderline; + + OSL_ENSURE( rStrm.getRemaining() >= 118, "Font::importCfRule - missing record data" ); + sal_Int64 nRecPos = rStrm.tell(); + maModel.maName = rStrm.readUniStringBody( rStrm.readuInt8() ); + maUsedFlags.mbNameUsed = maModel.maName.getLength() > 0; + OSL_ENSURE( !rStrm.isEof() && (rStrm.tell() <= nRecPos + 64), "Font::importCfRule - font name too long" ); + rStrm.seek( nRecPos + 64 ); + rStrm >> nHeight >> nStyle >> nWeight >> nEscapement >> nUnderline; + rStrm.skip( 3 ); + rStrm >> nColor; + rStrm.skip( 4 ); + rStrm >> nFontFlags1 >> nFontFlags2 >> nFontFlags3; + rStrm.skip( 18 ); + + if( (maUsedFlags.mbColorUsed = (0 <= nColor) && (nColor <= 0x7FFF)) == true ) + maModel.maColor.setIndexed( nColor ); + if( (maUsedFlags.mbHeightUsed = (0 < nHeight) && (nHeight <= 0x7FFF)) == true ) + maModel.setBiffHeight( static_cast< sal_uInt16 >( nHeight ) ); + if( (maUsedFlags.mbUnderlineUsed = !getFlag( nFontFlags3, BIFF_CFRULE_FONT_UNDERL )) == true ) + maModel.setBiffUnderline( nUnderline ); + if( (maUsedFlags.mbEscapementUsed = !getFlag( nFontFlags2, BIFF_CFRULE_FONT_ESCAPEM )) == true ) + maModel.setBiffEscapement( nEscapement ); + if( (maUsedFlags.mbWeightUsed = maUsedFlags.mbPostureUsed = !getFlag( nFontFlags1, BIFF_CFRULE_FONT_STYLE )) == true ) + { + maModel.setBiffWeight( nWeight ); + maModel.mbItalic = getFlag( nStyle, BIFF_CFRULE_FONT_STYLE ); + } + if( (maUsedFlags.mbStrikeoutUsed = !getFlag( nFontFlags1, BIFF_CFRULE_FONT_STRIKEOUT )) == true ) + maModel.mbStrikeout = getFlag( nStyle, BIFF_CFRULE_FONT_STRIKEOUT ); + if( (maUsedFlags.mbOutlineUsed = !getFlag( nFontFlags1, BIFF_CFRULE_FONT_OUTLINE )) == true ) + maModel.mbOutline = getFlag( nStyle, BIFF_CFRULE_FONT_OUTLINE ); + if( (maUsedFlags.mbShadowUsed = !getFlag( nFontFlags1, BIFF_CFRULE_FONT_SHADOW )) == true ) + maModel.mbShadow = getFlag( nStyle, BIFF_CFRULE_FONT_SHADOW ); +} + +rtl_TextEncoding Font::getFontEncoding() const +{ + // #i63105# cells use text encoding from FONT record character set + // #i67768# BIFF2-BIFF4 FONT records do not contain character set + // #i71033# do not use maApiData, this function is used before finalizeImport() + rtl_TextEncoding eFontEnc = RTL_TEXTENCODING_DONTKNOW; + if( (0 <= maModel.mnCharSet) && (maModel.mnCharSet <= SAL_MAX_UINT8) ) + eFontEnc = rtl_getTextEncodingFromWindowsCharset( static_cast< sal_uInt8 >( maModel.mnCharSet ) ); + return (eFontEnc == RTL_TEXTENCODING_DONTKNOW) ? getTextEncoding() : eFontEnc; +} + +void Font::finalizeImport() +{ + namespace cssawt = ::com::sun::star::awt; + + // font name + maApiData.maDesc.Name = maModel.maName; + + // font family + switch( maModel.mnFamily ) + { + case OOX_FONTFAMILY_NONE: maApiData.maDesc.Family = cssawt::FontFamily::DONTKNOW; break; + case OOX_FONTFAMILY_ROMAN: maApiData.maDesc.Family = cssawt::FontFamily::ROMAN; break; + case OOX_FONTFAMILY_SWISS: maApiData.maDesc.Family = cssawt::FontFamily::SWISS; break; + case OOX_FONTFAMILY_MODERN: maApiData.maDesc.Family = cssawt::FontFamily::MODERN; break; + case OOX_FONTFAMILY_SCRIPT: maApiData.maDesc.Family = cssawt::FontFamily::SCRIPT; break; + case OOX_FONTFAMILY_DECORATIVE: maApiData.maDesc.Family = cssawt::FontFamily::DECORATIVE; break; + } + + // character set (API font descriptor uses rtl_TextEncoding in member CharSet!) + if( (0 <= maModel.mnCharSet) && (maModel.mnCharSet <= SAL_MAX_UINT8) ) + maApiData.maDesc.CharSet = static_cast< sal_Int16 >( + rtl_getTextEncodingFromWindowsCharset( static_cast< sal_uInt8 >( maModel.mnCharSet ) ) ); + + // color, height, weight, slant, strikeout, outline, shadow + maApiData.mnColor = maModel.maColor.getColor( getBaseFilter().getGraphicHelper() ); + maApiData.maDesc.Height = static_cast< sal_Int16 >( maModel.mfHeight * 20.0 ); + maApiData.maDesc.Weight = maModel.mbBold ? cssawt::FontWeight::BOLD : cssawt::FontWeight::NORMAL; + maApiData.maDesc.Slant = maModel.mbItalic ? cssawt::FontSlant_ITALIC : cssawt::FontSlant_NONE; + maApiData.maDesc.Strikeout = maModel.mbStrikeout ? cssawt::FontStrikeout::SINGLE : cssawt::FontStrikeout::NONE; + maApiData.mbOutline = maModel.mbOutline; + maApiData.mbShadow = maModel.mbShadow; + + // underline + switch( maModel.mnUnderline ) + { + case XML_double: maApiData.maDesc.Underline = cssawt::FontUnderline::DOUBLE; break; + case XML_doubleAccounting: maApiData.maDesc.Underline = cssawt::FontUnderline::DOUBLE; break; + case XML_none: maApiData.maDesc.Underline = cssawt::FontUnderline::NONE; break; + case XML_single: maApiData.maDesc.Underline = cssawt::FontUnderline::SINGLE; break; + case XML_singleAccounting: maApiData.maDesc.Underline = cssawt::FontUnderline::SINGLE; break; + } + + // escapement + switch( maModel.mnEscapement ) + { + case XML_baseline: + maApiData.mnEscapement = API_ESCAPE_NONE; + maApiData.mnEscapeHeight = API_ESCAPEHEIGHT_NONE; + break; + case XML_superscript: + maApiData.mnEscapement = API_ESCAPE_SUPERSCRIPT; + maApiData.mnEscapeHeight = API_ESCAPEHEIGHT_DEFAULT; + break; + case XML_subscript: + maApiData.mnEscapement = API_ESCAPE_SUBSCRIPT; + maApiData.mnEscapeHeight = API_ESCAPEHEIGHT_DEFAULT; + break; + } + + // supported script types + if( maUsedFlags.mbNameUsed ) + { + Reference< XDevice > xDevice = getReferenceDevice(); + if( xDevice.is() ) + { + Reference< XFont2 > xFont( xDevice->getFont( maApiData.maDesc ), UNO_QUERY ); + if( xFont.is() ) + { + // #91658# CJK fonts + bool bHasAsian = + xFont->hasGlyphs( OUString( sal_Unicode( 0x3041 ) ) ) || // 3040-309F: Hiragana + xFont->hasGlyphs( OUString( sal_Unicode( 0x30A1 ) ) ) || // 30A0-30FF: Katakana + xFont->hasGlyphs( OUString( sal_Unicode( 0x3111 ) ) ) || // 3100-312F: Bopomofo + xFont->hasGlyphs( OUString( sal_Unicode( 0x3131 ) ) ) || // 3130-318F: Hangul Compatibility Jamo + xFont->hasGlyphs( OUString( sal_Unicode( 0x3301 ) ) ) || // 3300-33FF: CJK Compatibility + xFont->hasGlyphs( OUString( sal_Unicode( 0x3401 ) ) ) || // 3400-4DBF: CJK Unified Ideographs Extension A + xFont->hasGlyphs( OUString( sal_Unicode( 0x4E01 ) ) ) || // 4E00-9FAF: CJK Unified Ideographs + xFont->hasGlyphs( OUString( sal_Unicode( 0x7E01 ) ) ) || // 4E00-9FAF: CJK unified ideographs + xFont->hasGlyphs( OUString( sal_Unicode( 0xA001 ) ) ) || // A001-A48F: Yi Syllables + xFont->hasGlyphs( OUString( sal_Unicode( 0xAC01 ) ) ) || // AC00-D7AF: Hangul Syllables + xFont->hasGlyphs( OUString( sal_Unicode( 0xCC01 ) ) ) || // AC00-D7AF: Hangul Syllables + xFont->hasGlyphs( OUString( sal_Unicode( 0xF901 ) ) ) || // F900-FAFF: CJK Compatibility Ideographs + xFont->hasGlyphs( OUString( sal_Unicode( 0xFF71 ) ) ); // FF00-FFEF: Halfwidth/Fullwidth Forms + // #113783# CTL fonts + bool bHasCmplx = + xFont->hasGlyphs( OUString( sal_Unicode( 0x05D1 ) ) ) || // 0590-05FF: Hebrew + xFont->hasGlyphs( OUString( sal_Unicode( 0x0631 ) ) ) || // 0600-06FF: Arabic + xFont->hasGlyphs( OUString( sal_Unicode( 0x0721 ) ) ) || // 0700-074F: Syriac + xFont->hasGlyphs( OUString( sal_Unicode( 0x0911 ) ) ) || // 0900-0DFF: Indic scripts + xFont->hasGlyphs( OUString( sal_Unicode( 0x0E01 ) ) ) || // 0E00-0E7F: Thai + xFont->hasGlyphs( OUString( sal_Unicode( 0xFB21 ) ) ) || // FB1D-FB4F: Hebrew Presentation Forms + xFont->hasGlyphs( OUString( sal_Unicode( 0xFB51 ) ) ) || // FB50-FDFF: Arabic Presentation Forms-A + xFont->hasGlyphs( OUString( sal_Unicode( 0xFE71 ) ) ); // FE70-FEFF: Arabic Presentation Forms-B + // Western fonts + bool bHasLatin = + (!bHasAsian && !bHasCmplx) || + xFont->hasGlyphs( OUString( sal_Unicode( 'A' ) ) ); + + lclSetFontName( maApiData.maLatinFont, maApiData.maDesc, bHasLatin ); + lclSetFontName( maApiData.maAsianFont, maApiData.maDesc, bHasAsian ); + lclSetFontName( maApiData.maCmplxFont, maApiData.maDesc, bHasCmplx ); + } + } + } +} + +const FontDescriptor& Font::getFontDescriptor() const +{ + return maApiData.maDesc; +} + +bool Font::needsRichTextFormat() const +{ + return maApiData.mnEscapement != API_ESCAPE_NONE; +} + +void Font::writeToPropertyMap( PropertyMap& rPropMap, FontPropertyType ePropType ) const +{ + // font name properties + if( maUsedFlags.mbNameUsed ) + { + if( maApiData.maLatinFont.maName.getLength() > 0 ) + { + rPropMap[ PROP_CharFontName ] <<= maApiData.maLatinFont.maName; + rPropMap[ PROP_CharFontFamily ] <<= maApiData.maLatinFont.mnFamily; + rPropMap[ PROP_CharFontCharSet ] <<= maApiData.maLatinFont.mnTextEnc; + } + if( maApiData.maAsianFont.maName.getLength() > 0 ) + { + rPropMap[ PROP_CharFontNameAsian ] <<= maApiData.maAsianFont.maName; + rPropMap[ PROP_CharFontFamilyAsian ] <<= maApiData.maAsianFont.mnFamily; + rPropMap[ PROP_CharFontCharSetAsian ] <<= maApiData.maAsianFont.mnTextEnc; + } + if( maApiData.maCmplxFont.maName.getLength() > 0 ) + { + rPropMap[ PROP_CharFontNameComplex ] <<= maApiData.maCmplxFont.maName; + rPropMap[ PROP_CharFontFamilyComplex ] <<= maApiData.maCmplxFont.mnFamily; + rPropMap[ PROP_CharFontCharSetComplex ] <<= maApiData.maCmplxFont.mnTextEnc; + } + } + // font height + if( maUsedFlags.mbHeightUsed ) + { + float fHeight = static_cast< float >( maApiData.maDesc.Height / 20.0 ); // twips to points + rPropMap[ PROP_CharHeight ] <<= fHeight; + rPropMap[ PROP_CharHeightAsian ] <<= fHeight; + rPropMap[ PROP_CharHeightComplex ] <<= fHeight; + } + // font weight + if( maUsedFlags.mbWeightUsed ) + { + float fWeight = maApiData.maDesc.Weight; + rPropMap[ PROP_CharWeight ] <<= fWeight; + rPropMap[ PROP_CharWeightAsian ] <<= fWeight; + rPropMap[ PROP_CharWeightComplex ] <<= fWeight; + } + // font posture + if( maUsedFlags.mbPostureUsed ) + { + rPropMap[ PROP_CharPosture ] <<= maApiData.maDesc.Slant; + rPropMap[ PROP_CharPostureAsian ] <<= maApiData.maDesc.Slant; + rPropMap[ PROP_CharPostureComplex ] <<= maApiData.maDesc.Slant; + } + // character color + if( maUsedFlags.mbColorUsed ) + rPropMap[ PROP_CharColor ] <<= maApiData.mnColor; + // underline style + if( maUsedFlags.mbUnderlineUsed ) + rPropMap[ PROP_CharUnderline ] <<= maApiData.maDesc.Underline; + // strike out style + if( maUsedFlags.mbStrikeoutUsed ) + rPropMap[ PROP_CharStrikeout ] <<= maApiData.maDesc.Strikeout; + // outline style + if( maUsedFlags.mbOutlineUsed ) + rPropMap[ PROP_CharContoured ] <<= maApiData.mbOutline; + // shadow style + if( maUsedFlags.mbShadowUsed ) + rPropMap[ PROP_CharShadowed ] <<= maApiData.mbShadow; + // escapement + if( maUsedFlags.mbEscapementUsed && (ePropType == FONT_PROPTYPE_TEXT) ) + { + rPropMap[ PROP_CharEscapement ] <<= maApiData.mnEscapement; + rPropMap[ PROP_CharEscapementHeight ] <<= maApiData.mnEscapeHeight; + } +} + +void Font::writeToPropertySet( PropertySet& rPropSet, FontPropertyType ePropType ) const +{ + PropertyMap aPropMap; + writeToPropertyMap( aPropMap, ePropType ); + rPropSet.setProperties( aPropMap ); +} + +void Font::importFontData2( BiffInputStream& rStrm ) +{ + sal_uInt16 nHeight, nFlags; + rStrm >> nHeight >> nFlags; + + maModel.setBiffHeight( nHeight ); + maModel.mnFamily = OOX_FONTFAMILY_NONE; + maModel.mnCharSet = -1; // ensure to not use font charset in byte string import + maModel.mnUnderline = getFlagValue( nFlags, BIFF_FONTFLAG_UNDERLINE, XML_single, XML_none ); + maModel.mnEscapement = XML_none; + maModel.mbBold = getFlag( nFlags, BIFF_FONTFLAG_BOLD ); + maModel.mbItalic = getFlag( nFlags, BIFF_FONTFLAG_ITALIC ); + maModel.mbStrikeout = getFlag( nFlags, BIFF_FONTFLAG_STRIKEOUT ); + maModel.mbOutline = getFlag( nFlags, BIFF_FONTFLAG_OUTLINE ); + maModel.mbShadow = getFlag( nFlags, BIFF_FONTFLAG_SHADOW ); +} + +void Font::importFontData5( BiffInputStream& rStrm ) +{ + sal_uInt16 nWeight, nEscapement; + sal_uInt8 nUnderline, nFamily, nCharSet; + rStrm >> nWeight >> nEscapement >> nUnderline >> nFamily >> nCharSet; + rStrm.skip( 1 ); + + maModel.setBiffWeight( nWeight ); + maModel.setBiffUnderline( nUnderline ); + maModel.setBiffEscapement( nEscapement ); + // equal constants in XML and BIFF for family and charset + maModel.mnFamily = nFamily; + maModel.mnCharSet = nCharSet; +} + +void Font::importFontName2( BiffInputStream& rStrm ) +{ + maModel.maName = rStrm.readByteStringUC( false, getTextEncoding() ); +} + +void Font::importFontName8( BiffInputStream& rStrm ) +{ + maModel.maName = rStrm.readUniStringBody( rStrm.readuInt8() ); +} + +// ============================================================================ + +AlignmentModel::AlignmentModel() : + mnHorAlign( XML_general ), + mnVerAlign( XML_bottom ), + mnTextDir( OOX_XF_TEXTDIR_CONTEXT ), + mnRotation( OOX_XF_ROTATION_NONE ), + mnIndent( OOX_XF_INDENT_NONE ), + mbWrapText( false ), + mbShrink( false ), + mbJustLastLine( false ) +{ +} + +void AlignmentModel::setBinHorAlign( sal_uInt8 nHorAlign ) +{ + static const sal_Int32 spnHorAligns[] = { + XML_general, XML_left, XML_center, XML_right, + XML_fill, XML_justify, XML_centerContinuous, XML_distributed }; + mnHorAlign = STATIC_ARRAY_SELECT( spnHorAligns, nHorAlign, XML_general ); +} + +void AlignmentModel::setBinVerAlign( sal_uInt8 nVerAlign ) +{ + static const sal_Int32 spnVerAligns[] = { + XML_top, XML_center, XML_bottom, XML_justify, XML_distributed }; + mnVerAlign = STATIC_ARRAY_SELECT( spnVerAligns, nVerAlign, XML_bottom ); +} + +void AlignmentModel::setBinTextOrient( sal_uInt8 nTextOrient ) +{ + static const sal_Int32 spnRotations[] = { + OOX_XF_ROTATION_NONE, OOX_XF_ROTATION_STACKED, + OOX_XF_ROTATION_90CCW, OOX_XF_ROTATION_90CW }; + mnRotation = STATIC_ARRAY_SELECT( spnRotations, nTextOrient, OOX_XF_ROTATION_NONE ); +} + +// ---------------------------------------------------------------------------- + +ApiAlignmentData::ApiAlignmentData() : + meHorJustify( ::com::sun::star::table::CellHoriJustify_STANDARD ), + meVerJustify( ::com::sun::star::table::CellVertJustify_STANDARD ), + meOrientation( ::com::sun::star::table::CellOrientation_STANDARD ), + mnRotation( 0 ), + mnWritingMode( ::com::sun::star::text::WritingMode2::PAGE ), + mnIndent( 0 ), + mbWrapText( false ), + mbShrink( false ) +{ +} + +bool operator==( const ApiAlignmentData& rLeft, const ApiAlignmentData& rRight ) +{ + return + (rLeft.meHorJustify == rRight.meHorJustify) && + (rLeft.meVerJustify == rRight.meVerJustify) && + (rLeft.meOrientation == rRight.meOrientation) && + (rLeft.mnRotation == rRight.mnRotation) && + (rLeft.mnWritingMode == rRight.mnWritingMode) && + (rLeft.mnIndent == rRight.mnIndent) && + (rLeft.mbWrapText == rRight.mbWrapText) && + (rLeft.mbShrink == rRight.mbShrink); +} + +// ============================================================================ + +Alignment::Alignment( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void Alignment::importAlignment( const AttributeList& rAttribs ) +{ + maModel.mnHorAlign = rAttribs.getToken( XML_horizontal, XML_general ); + maModel.mnVerAlign = rAttribs.getToken( XML_vertical, XML_bottom ); + maModel.mnTextDir = rAttribs.getInteger( XML_readingOrder, OOX_XF_TEXTDIR_CONTEXT ); + maModel.mnRotation = rAttribs.getInteger( XML_textRotation, OOX_XF_ROTATION_NONE ); + maModel.mnIndent = rAttribs.getInteger( XML_indent, OOX_XF_INDENT_NONE ); + maModel.mbWrapText = rAttribs.getBool( XML_wrapText, false ); + maModel.mbShrink = rAttribs.getBool( XML_shrinkToFit, false ); + maModel.mbJustLastLine = rAttribs.getBool( XML_justifyLastLine, false ); +} + +void Alignment::setBinData( sal_uInt32 nFlags ) +{ + maModel.setBinHorAlign( extractValue< sal_uInt8 >( nFlags, 16, 3 ) ); + maModel.setBinVerAlign( extractValue< sal_uInt8 >( nFlags, 19, 3 ) ); + maModel.mnTextDir = extractValue< sal_Int32 >( nFlags, 26, 2 ); + maModel.mnRotation = extractValue< sal_Int32 >( nFlags, 0, 8 ); + maModel.mnIndent = extractValue< sal_uInt8 >( nFlags, 8, 8 ); + maModel.mbWrapText = getFlag( nFlags, OOBIN_XF_WRAPTEXT ); + maModel.mbShrink = getFlag( nFlags, OOBIN_XF_SHRINK ); + maModel.mbJustLastLine = getFlag( nFlags, OOBIN_XF_JUSTLASTLINE ); +} + +void Alignment::setBiff2Data( sal_uInt8 nFlags ) +{ + maModel.setBinHorAlign( extractValue< sal_uInt8 >( nFlags, 0, 3 ) ); +} + +void Alignment::setBiff3Data( sal_uInt16 nAlign ) +{ + maModel.setBinHorAlign( extractValue< sal_uInt8 >( nAlign, 0, 3 ) ); + maModel.mbWrapText = getFlag( nAlign, BIFF_XF_WRAPTEXT ); // new in BIFF3 +} + +void Alignment::setBiff4Data( sal_uInt16 nAlign ) +{ + maModel.setBinHorAlign( extractValue< sal_uInt8 >( nAlign, 0, 3 ) ); + maModel.setBinVerAlign( extractValue< sal_uInt8 >( nAlign, 4, 2 ) ); // new in BIFF4 + maModel.setBinTextOrient( extractValue< sal_uInt8 >( nAlign, 6, 2 ) ); // new in BIFF4 + maModel.mbWrapText = getFlag( nAlign, BIFF_XF_WRAPTEXT ); +} + +void Alignment::setBiff5Data( sal_uInt16 nAlign ) +{ + maModel.setBinHorAlign( extractValue< sal_uInt8 >( nAlign, 0, 3 ) ); + maModel.setBinVerAlign( extractValue< sal_uInt8 >( nAlign, 4, 3 ) ); + maModel.setBinTextOrient( extractValue< sal_uInt8 >( nAlign, 8, 2 ) ); + maModel.mbWrapText = getFlag( nAlign, BIFF_XF_WRAPTEXT ); +} + +void Alignment::setBiff8Data( sal_uInt16 nAlign, sal_uInt16 nMiscAttrib ) +{ + maModel.setBinHorAlign( extractValue< sal_uInt8 >( nAlign, 0, 3 ) ); + maModel.setBinVerAlign( extractValue< sal_uInt8 >( nAlign, 4, 3 ) ); + maModel.mnTextDir = extractValue< sal_Int32 >( nMiscAttrib, 6, 2 ); // new in BIFF8 + maModel.mnRotation = extractValue< sal_Int32 >( nAlign, 8, 8 ); // new in BIFF8 + maModel.mnIndent = extractValue< sal_uInt8 >( nMiscAttrib, 0, 4 ); // new in BIFF8 + maModel.mbWrapText = getFlag( nAlign, BIFF_XF_WRAPTEXT ); + maModel.mbShrink = getFlag( nMiscAttrib, BIFF_XF_SHRINK ); // new in BIFF8 + maModel.mbJustLastLine = getFlag( nAlign, BIFF_XF_JUSTLASTLINE ); // new in BIFF8(?) +} + +void Alignment::finalizeImport() +{ + namespace csstab = ::com::sun::star::table; + namespace csstxt = ::com::sun::star::text; + + // horizontal alignment + switch( maModel.mnHorAlign ) + { + case XML_center: maApiData.meHorJustify = csstab::CellHoriJustify_CENTER; break; + case XML_centerContinuous: maApiData.meHorJustify = csstab::CellHoriJustify_CENTER; break; + case XML_distributed: maApiData.meHorJustify = csstab::CellHoriJustify_BLOCK; break; + case XML_fill: maApiData.meHorJustify = csstab::CellHoriJustify_REPEAT; break; + case XML_general: maApiData.meHorJustify = csstab::CellHoriJustify_STANDARD; break; + case XML_justify: maApiData.meHorJustify = csstab::CellHoriJustify_BLOCK; break; + case XML_left: maApiData.meHorJustify = csstab::CellHoriJustify_LEFT; break; + case XML_right: maApiData.meHorJustify = csstab::CellHoriJustify_RIGHT; break; + } + + // vertical alignment + switch( maModel.mnVerAlign ) + { + case XML_bottom: maApiData.meVerJustify = csstab::CellVertJustify_BOTTOM; break; + case XML_center: maApiData.meVerJustify = csstab::CellVertJustify_CENTER; break; + case XML_distributed: maApiData.meVerJustify = csstab::CellVertJustify_TOP; break; + case XML_justify: maApiData.meVerJustify = csstab::CellVertJustify_TOP; break; + case XML_top: maApiData.meVerJustify = csstab::CellVertJustify_TOP; break; + } + + /* indentation: expressed as number of blocks of 3 space characters in + OOX, and as multiple of 10 points in BIFF. */ + sal_Int32 nIndent = 0; + switch( getFilterType() ) + { + case FILTER_OOX: nIndent = getUnitConverter().scaleToMm100( 3.0 * maModel.mnIndent, UNIT_SPACE ); break; + case FILTER_BIFF: nIndent = getUnitConverter().scaleToMm100( 10.0 * maModel.mnIndent, UNIT_POINT ); break; + case FILTER_UNKNOWN: break; + } + if( (0 <= nIndent) && (nIndent <= SAL_MAX_INT16) ) + maApiData.mnIndent = static_cast< sal_Int16 >( nIndent ); + + // complex text direction + switch( maModel.mnTextDir ) + { + case OOX_XF_TEXTDIR_CONTEXT: maApiData.mnWritingMode = csstxt::WritingMode2::PAGE; break; + case OOX_XF_TEXTDIR_LTR: maApiData.mnWritingMode = csstxt::WritingMode2::LR_TB; break; + case OOX_XF_TEXTDIR_RTL: maApiData.mnWritingMode = csstxt::WritingMode2::RL_TB; break; + } + + // rotation: 0-90 means 0 to 90 degrees ccw, 91-180 means 1 to 90 degrees cw, 255 means stacked + sal_Int32 nOoxRot = maModel.mnRotation; + maApiData.mnRotation = ((0 <= nOoxRot) && (nOoxRot <= 90)) ? + (100 * nOoxRot) : + (((91 <= nOoxRot) && (nOoxRot <= 180)) ? (100 * (450 - nOoxRot)) : 0); + + // "Orientation" property used for character stacking + maApiData.meOrientation = (nOoxRot == OOX_XF_ROTATION_STACKED) ? + csstab::CellOrientation_STACKED : csstab::CellOrientation_STANDARD; + + // alignment flags (#i84960 automatic line break, if vertically justified/distributed) + maApiData.mbWrapText = maModel.mbWrapText || (maModel.mnVerAlign == XML_distributed) || (maModel.mnVerAlign == XML_justify); + maApiData.mbShrink = maModel.mbShrink; + +} + +void Alignment::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + rPropMap[ PROP_HoriJustify ] <<= maApiData.meHorJustify; + rPropMap[ PROP_VertJustify ] <<= maApiData.meVerJustify; + rPropMap[ PROP_WritingMode ] <<= maApiData.mnWritingMode; + rPropMap[ PROP_RotateAngle ] <<= maApiData.mnRotation; + rPropMap[ PROP_Orientation ] <<= maApiData.meOrientation; + rPropMap[ PROP_ParaIndent ] <<= maApiData.mnIndent; + rPropMap[ PROP_IsTextWrapped ] <<= maApiData.mbWrapText; + rPropMap[ PROP_ShrinkToFit ] <<= maApiData.mbShrink; +} + +// ============================================================================ + +ProtectionModel::ProtectionModel() : + mbLocked( true ), // default in Excel and Calc + mbHidden( false ) +{ +} + +// ---------------------------------------------------------------------------- + +ApiProtectionData::ApiProtectionData() : + maCellProt( sal_True, sal_False, sal_False, sal_False ) +{ +} + +bool operator==( const ApiProtectionData& rLeft, const ApiProtectionData& rRight ) +{ + return + (rLeft.maCellProt.IsLocked == rRight.maCellProt.IsLocked) && + (rLeft.maCellProt.IsFormulaHidden == rRight.maCellProt.IsFormulaHidden) && + (rLeft.maCellProt.IsHidden == rRight.maCellProt.IsHidden) && + (rLeft.maCellProt.IsPrintHidden == rRight.maCellProt.IsPrintHidden); +} + +// ============================================================================ + +Protection::Protection( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void Protection::importProtection( const AttributeList& rAttribs ) +{ + maModel.mbLocked = rAttribs.getBool( XML_locked, true ); + maModel.mbHidden = rAttribs.getBool( XML_hidden, false ); +} + +void Protection::setBinData( sal_uInt32 nFlags ) +{ + maModel.mbLocked = getFlag( nFlags, OOBIN_XF_LOCKED ); + maModel.mbHidden = getFlag( nFlags, OOBIN_XF_HIDDEN ); +} + +void Protection::setBiff2Data( sal_uInt8 nNumFmt ) +{ + maModel.mbLocked = getFlag( nNumFmt, BIFF2_XF_LOCKED ); + maModel.mbHidden = getFlag( nNumFmt, BIFF2_XF_HIDDEN ); +} + +void Protection::setBiff3Data( sal_uInt16 nProt ) +{ + maModel.mbLocked = getFlag( nProt, BIFF_XF_LOCKED ); + maModel.mbHidden = getFlag( nProt, BIFF_XF_HIDDEN ); +} + +void Protection::finalizeImport() +{ + maApiData.maCellProt.IsLocked = maModel.mbLocked; + maApiData.maCellProt.IsFormulaHidden = maModel.mbHidden; +} + +void Protection::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + rPropMap[ PROP_CellProtection ] <<= maApiData.maCellProt; +} + +// ============================================================================ + +BorderLineModel::BorderLineModel( bool bDxf ) : + mnStyle( XML_none ), + mbUsed( !bDxf ) +{ + maColor.setIndexed( OOX_COLOR_WINDOWTEXT ); +} + +void BorderLineModel::setBiffStyle( sal_Int32 nLineStyle ) +{ + static const sal_Int32 spnStyleIds[] = { + XML_none, XML_thin, XML_medium, XML_dashed, + XML_dotted, XML_thick, XML_double, XML_hair, + XML_mediumDashed, XML_dashDot, XML_mediumDashDot, XML_dashDotDot, + XML_mediumDashDotDot, XML_slantDashDot }; + mnStyle = STATIC_ARRAY_SELECT( spnStyleIds, nLineStyle, XML_none ); +} + +void BorderLineModel::setBiffData( sal_uInt8 nLineStyle, sal_uInt16 nLineColor ) +{ + maColor.setIndexed( nLineColor ); + setBiffStyle( nLineStyle ); +} + +// ---------------------------------------------------------------------------- + +BorderModel::BorderModel( bool bDxf ) : + maLeft( bDxf ), + maRight( bDxf ), + maTop( bDxf ), + maBottom( bDxf ), + maDiagonal( bDxf ), + mbDiagTLtoBR( false ), + mbDiagBLtoTR( false ) +{ +} + +// ---------------------------------------------------------------------------- + +ApiBorderData::ApiBorderData() : + mbBorderUsed( false ), + mbDiagUsed( false ) +{ +} + +bool ApiBorderData::hasAnyOuterBorder() const +{ + return + (maBorder.IsTopLineValid && (maBorder.TopLine.OuterLineWidth > 0)) || + (maBorder.IsBottomLineValid && (maBorder.BottomLine.OuterLineWidth > 0)) || + (maBorder.IsLeftLineValid && (maBorder.LeftLine.OuterLineWidth > 0)) || + (maBorder.IsRightLineValid && (maBorder.RightLine.OuterLineWidth > 0)); +} + +namespace { + +bool operator==( const BorderLine& rLeft, const BorderLine& rRight ) +{ + return + (rLeft.Color == rRight.Color) && + (rLeft.InnerLineWidth == rRight.InnerLineWidth) && + (rLeft.OuterLineWidth == rRight.OuterLineWidth) && + (rLeft.LineDistance == rRight.LineDistance); +} + +bool operator==( const TableBorder& rLeft, const TableBorder& rRight ) +{ + return + (rLeft.TopLine == rRight.TopLine) && + (rLeft.IsTopLineValid == rRight.IsTopLineValid) && + (rLeft.BottomLine == rRight.BottomLine) && + (rLeft.IsBottomLineValid == rRight.IsBottomLineValid) && + (rLeft.LeftLine == rRight.LeftLine) && + (rLeft.IsLeftLineValid == rRight.IsLeftLineValid) && + (rLeft.RightLine == rRight.RightLine) && + (rLeft.IsRightLineValid == rRight.IsRightLineValid) && + (rLeft.HorizontalLine == rRight.HorizontalLine) && + (rLeft.IsHorizontalLineValid == rRight.IsHorizontalLineValid) && + (rLeft.VerticalLine == rRight.VerticalLine) && + (rLeft.IsVerticalLineValid == rRight.IsVerticalLineValid) && + (rLeft.Distance == rRight.Distance) && + (rLeft.IsDistanceValid == rRight.IsDistanceValid); +} + +} // namespace + +bool operator==( const ApiBorderData& rLeft, const ApiBorderData& rRight ) +{ + return + (rLeft.maBorder == rRight.maBorder) && + (rLeft.maTLtoBR == rRight.maTLtoBR) && + (rLeft.maBLtoTR == rRight.maBLtoTR) && + (rLeft.mbBorderUsed == rRight.mbBorderUsed) && + (rLeft.mbDiagUsed == rRight.mbDiagUsed); +} + +// ============================================================================ + +namespace { + +inline void lclSetBorderLineWidth( BorderLine& rBorderLine, + sal_Int16 nOuter, sal_Int16 nDist = API_LINE_NONE, sal_Int16 nInner = API_LINE_NONE ) +{ + rBorderLine.OuterLineWidth = nOuter; + rBorderLine.LineDistance = nDist; + rBorderLine.InnerLineWidth = nInner; +} + +inline sal_Int32 lclGetBorderLineWidth( const BorderLine& rBorderLine ) +{ + return rBorderLine.OuterLineWidth + rBorderLine.LineDistance + rBorderLine.InnerLineWidth; +} + +const BorderLine* lclGetThickerLine( const BorderLine& rBorderLine1, sal_Bool bValid1, const BorderLine& rBorderLine2, sal_Bool bValid2 ) +{ + if( bValid1 && bValid2 ) + return (lclGetBorderLineWidth( rBorderLine1 ) < lclGetBorderLineWidth( rBorderLine2 )) ? &rBorderLine2 : &rBorderLine1; + if( bValid1 ) + return &rBorderLine1; + if( bValid2 ) + return &rBorderLine2; + return 0; +} + +} // namespace + +// ---------------------------------------------------------------------------- + +Border::Border( const WorkbookHelper& rHelper, bool bDxf ) : + WorkbookHelper( rHelper ), + maModel( bDxf ), + mbDxf( bDxf ) +{ +} + +void Border::importBorder( const AttributeList& rAttribs ) +{ + maModel.mbDiagTLtoBR = rAttribs.getBool( XML_diagonalDown, false ); + maModel.mbDiagBLtoTR = rAttribs.getBool( XML_diagonalUp, false ); +} + +void Border::importStyle( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( BorderLineModel* pBorderLine = getBorderLine( nElement ) ) + { + pBorderLine->mnStyle = rAttribs.getToken( XML_style, XML_none ); + pBorderLine->mbUsed = true; + } +} + +void Border::importColor( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( BorderLineModel* pBorderLine = getBorderLine( nElement ) ) + pBorderLine->maColor.importColor( rAttribs ); +} + +void Border::importBorder( RecordInputStream& rStrm ) +{ + sal_uInt8 nFlags = rStrm.readuInt8(); + maModel.mbDiagTLtoBR = getFlag( nFlags, OOBIN_BORDER_DIAG_TLBR ); + maModel.mbDiagBLtoTR = getFlag( nFlags, OOBIN_BORDER_DIAG_BLTR ); + maModel.maTop.setBiffStyle( rStrm.readuInt16() ); + rStrm >> maModel.maTop.maColor; + maModel.maBottom.setBiffStyle( rStrm.readuInt16() ); + rStrm >> maModel.maBottom.maColor; + maModel.maLeft.setBiffStyle( rStrm.readuInt16() ); + rStrm >> maModel.maLeft.maColor; + maModel.maRight.setBiffStyle( rStrm.readuInt16() ); + rStrm >> maModel.maRight.maColor; + maModel.maDiagonal.setBiffStyle( rStrm.readuInt16() ); + rStrm >> maModel.maDiagonal.maColor; +} + +void Border::importDxfBorder( sal_Int32 nElement, RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Border::importDxfBorder - missing conditional formatting flag" ); + if( BorderLineModel* pBorderLine = getBorderLine( nElement ) ) + { + sal_uInt16 nStyle; + rStrm >> pBorderLine->maColor >> nStyle; + pBorderLine->setBiffStyle( nStyle ); + pBorderLine->mbUsed = true; + } +} + +void Border::setBiff2Data( sal_uInt8 nFlags ) +{ + OSL_ENSURE( !mbDxf, "Border::setBiff2Data - unexpected conditional formatting flag" ); + maModel.maLeft.setBiffData( getFlagValue( nFlags, BIFF2_XF_LEFTLINE, BIFF_LINE_THIN, BIFF_LINE_NONE ), BIFF2_COLOR_BLACK ); + maModel.maRight.setBiffData( getFlagValue( nFlags, BIFF2_XF_RIGHTLINE, BIFF_LINE_THIN, BIFF_LINE_NONE ), BIFF2_COLOR_BLACK ); + maModel.maTop.setBiffData( getFlagValue( nFlags, BIFF2_XF_TOPLINE, BIFF_LINE_THIN, BIFF_LINE_NONE ), BIFF2_COLOR_BLACK ); + maModel.maBottom.setBiffData( getFlagValue( nFlags, BIFF2_XF_BOTTOMLINE, BIFF_LINE_THIN, BIFF_LINE_NONE ), BIFF2_COLOR_BLACK ); + maModel.maDiagonal.mbUsed = false; +} + +void Border::setBiff3Data( sal_uInt32 nBorder ) +{ + OSL_ENSURE( !mbDxf, "Border::setBiff3Data - unexpected conditional formatting flag" ); + maModel.maLeft.setBiffData( extractValue< sal_uInt8 >( nBorder, 8, 3 ), extractValue< sal_uInt16 >( nBorder, 11, 5 ) ); + maModel.maRight.setBiffData( extractValue< sal_uInt8 >( nBorder, 24, 3 ), extractValue< sal_uInt16 >( nBorder, 27, 5 ) ); + maModel.maTop.setBiffData( extractValue< sal_uInt8 >( nBorder, 0, 3 ), extractValue< sal_uInt16 >( nBorder, 3, 5 ) ); + maModel.maBottom.setBiffData( extractValue< sal_uInt8 >( nBorder, 16, 3 ), extractValue< sal_uInt16 >( nBorder, 19, 5 ) ); + maModel.maDiagonal.mbUsed = false; +} + +void Border::setBiff5Data( sal_uInt32 nBorder, sal_uInt32 nArea ) +{ + OSL_ENSURE( !mbDxf, "Border::setBiff5Data - unexpected conditional formatting flag" ); + maModel.maLeft.setBiffData( extractValue< sal_uInt8 >( nBorder, 3, 3 ), extractValue< sal_uInt16 >( nBorder, 16, 7 ) ); + maModel.maRight.setBiffData( extractValue< sal_uInt8 >( nBorder, 6, 3 ), extractValue< sal_uInt16 >( nBorder, 23, 7 ) ); + maModel.maTop.setBiffData( extractValue< sal_uInt8 >( nBorder, 0, 3 ), extractValue< sal_uInt16 >( nBorder, 9, 7 ) ); + maModel.maBottom.setBiffData( extractValue< sal_uInt8 >( nArea, 22, 3 ), extractValue< sal_uInt16 >( nArea, 25, 7 ) ); + maModel.maDiagonal.mbUsed = false; +} + +void Border::setBiff8Data( sal_uInt32 nBorder1, sal_uInt32 nBorder2 ) +{ + OSL_ENSURE( !mbDxf, "Border::setBiff8Data - unexpected conditional formatting flag" ); + maModel.maLeft.setBiffData( extractValue< sal_uInt8 >( nBorder1, 0, 4 ), extractValue< sal_uInt16 >( nBorder1, 16, 7 ) ); + maModel.maRight.setBiffData( extractValue< sal_uInt8 >( nBorder1, 4, 4 ), extractValue< sal_uInt16 >( nBorder1, 23, 7 ) ); + maModel.maTop.setBiffData( extractValue< sal_uInt8 >( nBorder1, 8, 4 ), extractValue< sal_uInt16 >( nBorder2, 0, 7 ) ); + maModel.maBottom.setBiffData( extractValue< sal_uInt8 >( nBorder1, 12, 4 ), extractValue< sal_uInt16 >( nBorder2, 7, 7 ) ); + maModel.mbDiagTLtoBR = getFlag( nBorder1, BIFF_XF_DIAG_TLBR ); + maModel.mbDiagBLtoTR = getFlag( nBorder1, BIFF_XF_DIAG_BLTR ); + if( maModel.mbDiagTLtoBR || maModel.mbDiagBLtoTR ) + maModel.maDiagonal.setBiffData( extractValue< sal_uInt8 >( nBorder2, 21, 4 ), extractValue< sal_uInt16 >( nBorder2, 14, 7 ) ); +} + +void Border::importCfRule( BiffInputStream& rStrm, sal_uInt32 nFlags ) +{ + OSL_ENSURE( mbDxf, "Border::importCfRule - missing conditional formatting flag" ); + OSL_ENSURE( getFlag( nFlags, BIFF_CFRULE_BORDERBLOCK ), "Border::importCfRule - missing border block flag" ); + sal_uInt16 nStyle; + sal_uInt32 nColor; + rStrm >> nStyle >> nColor; + rStrm.skip( 2 ); + maModel.maLeft.setBiffData( extractValue< sal_uInt8 >( nStyle, 0, 4 ), extractValue< sal_uInt16 >( nColor, 0, 7 ) ); + maModel.maRight.setBiffData( extractValue< sal_uInt8 >( nStyle, 4, 4 ), extractValue< sal_uInt16 >( nColor, 7, 7 ) ); + maModel.maTop.setBiffData( extractValue< sal_uInt8 >( nStyle, 8, 4 ), extractValue< sal_uInt16 >( nColor, 16, 7 ) ); + maModel.maBottom.setBiffData( extractValue< sal_uInt8 >( nStyle, 12, 4 ), extractValue< sal_uInt16 >( nColor, 23, 7 ) ); + maModel.maLeft.mbUsed = !getFlag( nFlags, BIFF_CFRULE_BORDER_LEFT ); + maModel.maRight.mbUsed = !getFlag( nFlags, BIFF_CFRULE_BORDER_RIGHT ); + maModel.maTop.mbUsed = !getFlag( nFlags, BIFF_CFRULE_BORDER_TOP ); + maModel.maBottom.mbUsed = !getFlag( nFlags, BIFF_CFRULE_BORDER_BOTTOM ); +} + +void Border::finalizeImport() +{ + maApiData.mbBorderUsed = maModel.maLeft.mbUsed || maModel.maRight.mbUsed || maModel.maTop.mbUsed || maModel.maBottom.mbUsed; + maApiData.mbDiagUsed = maModel.maDiagonal.mbUsed; + + maApiData.maBorder.IsLeftLineValid = convertBorderLine( maApiData.maBorder.LeftLine, maModel.maLeft ); + maApiData.maBorder.IsRightLineValid = convertBorderLine( maApiData.maBorder.RightLine, maModel.maRight ); + maApiData.maBorder.IsTopLineValid = convertBorderLine( maApiData.maBorder.TopLine, maModel.maTop ); + maApiData.maBorder.IsBottomLineValid = convertBorderLine( maApiData.maBorder.BottomLine, maModel.maBottom ); + + if( !mbDxf ) + { + maApiData.maBorder.IsVerticalLineValid = maApiData.maBorder.IsLeftLineValid || maApiData.maBorder.IsRightLineValid; + if( const BorderLine* pVertLine = lclGetThickerLine( maApiData.maBorder.LeftLine, maApiData.maBorder.IsLeftLineValid, maApiData.maBorder.RightLine, maApiData.maBorder.IsRightLineValid ) ) + maApiData.maBorder.VerticalLine = *pVertLine; + + maApiData.maBorder.IsHorizontalLineValid = maApiData.maBorder.IsTopLineValid || maApiData.maBorder.IsBottomLineValid; + if( const BorderLine* pHorLine = lclGetThickerLine( maApiData.maBorder.TopLine, maApiData.maBorder.IsTopLineValid, maApiData.maBorder.BottomLine, maApiData.maBorder.IsBottomLineValid ) ) + maApiData.maBorder.HorizontalLine = *pHorLine; + } + + if( maModel.mbDiagTLtoBR ) + convertBorderLine( maApiData.maTLtoBR, maModel.maDiagonal ); + if( maModel.mbDiagBLtoTR ) + convertBorderLine( maApiData.maBLtoTR, maModel.maDiagonal ); +} + +void Border::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + if( maApiData.mbBorderUsed ) + rPropMap[ PROP_TableBorder ] <<= maApiData.maBorder; + if( maApiData.mbDiagUsed ) + { + rPropMap[ PROP_DiagonalTLBR ] <<= maApiData.maTLtoBR; + rPropMap[ PROP_DiagonalBLTR ] <<= maApiData.maBLtoTR; + } +} + +BorderLineModel* Border::getBorderLine( sal_Int32 nElement ) +{ + switch( nElement ) + { + case XLS_TOKEN( left ): return &maModel.maLeft; + case XLS_TOKEN( right ): return &maModel.maRight; + case XLS_TOKEN( top ): return &maModel.maTop; + case XLS_TOKEN( bottom ): return &maModel.maBottom; + case XLS_TOKEN( diagonal ): return &maModel.maDiagonal; + } + return 0; +} + +bool Border::convertBorderLine( BorderLine& rBorderLine, const BorderLineModel& rModel ) +{ + rBorderLine.Color = rModel.maColor.getColor( getBaseFilter().getGraphicHelper(), API_RGB_BLACK ); + switch( rModel.mnStyle ) + { + case XML_dashDot: lclSetBorderLineWidth( rBorderLine, API_LINE_THIN ); break; + case XML_dashDotDot: lclSetBorderLineWidth( rBorderLine, API_LINE_THIN ); break; + case XML_dashed: lclSetBorderLineWidth( rBorderLine, API_LINE_THIN ); break; + case XML_dotted: lclSetBorderLineWidth( rBorderLine, API_LINE_THIN ); break; + case XML_double: lclSetBorderLineWidth( rBorderLine, API_LINE_THIN, API_LINE_THIN, API_LINE_THIN ); break; + case XML_hair: lclSetBorderLineWidth( rBorderLine, API_LINE_HAIR ); break; + case XML_medium: lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM ); break; + case XML_mediumDashDot: lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM ); break; + case XML_mediumDashDotDot: lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM ); break; + case XML_mediumDashed: lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM ); break; + case XML_none: lclSetBorderLineWidth( rBorderLine, API_LINE_NONE ); break; + case XML_slantDashDot: lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM ); break; + case XML_thick: lclSetBorderLineWidth( rBorderLine, API_LINE_THICK ); break; + case XML_thin: lclSetBorderLineWidth( rBorderLine, API_LINE_THIN ); break; + default: lclSetBorderLineWidth( rBorderLine, API_LINE_NONE ); break; + } + return rModel.mbUsed; +} + + +// ============================================================================ + +PatternFillModel::PatternFillModel( bool bDxf ) : + mnPattern( XML_none ), + mbPattColorUsed( !bDxf ), + mbFillColorUsed( !bDxf ), + mbPatternUsed( !bDxf ) +{ + maPatternColor.setIndexed( OOX_COLOR_WINDOWTEXT ); + maFillColor.setIndexed( OOX_COLOR_WINDOWBACK ); +} + +void PatternFillModel::setBinPattern( sal_Int32 nPattern ) +{ + static const sal_Int32 spnPatternIds[] = { + XML_none, XML_solid, XML_mediumGray, XML_darkGray, + XML_lightGray, XML_darkHorizontal, XML_darkVertical, XML_darkDown, + XML_darkUp, XML_darkGrid, XML_darkTrellis, XML_lightHorizontal, + XML_lightVertical, XML_lightDown, XML_lightUp, XML_lightGrid, + XML_lightTrellis, XML_gray125, XML_gray0625 }; + mnPattern = STATIC_ARRAY_SELECT( spnPatternIds, nPattern, XML_none ); +} + +void PatternFillModel::setBiffData( sal_uInt16 nPatternColor, sal_uInt16 nFillColor, sal_uInt8 nPattern ) +{ + maPatternColor.setIndexed( nPatternColor ); + maFillColor.setIndexed( nFillColor ); + // patterns equal in BIFF and OOBIN + setBinPattern( nPattern ); +} + +// ---------------------------------------------------------------------------- + +GradientFillModel::GradientFillModel() : + mnType( XML_linear ), + mfAngle( 0.0 ), + mfLeft( 0.0 ), + mfRight( 0.0 ), + mfTop( 0.0 ), + mfBottom( 0.0 ) +{ +} + +void GradientFillModel::readGradient( RecordInputStream& rStrm ) +{ + sal_Int32 nType; + rStrm >> nType >> mfAngle >> mfLeft >> mfRight >> mfTop >> mfBottom; + static const sal_Int32 spnTypes[] = { XML_linear, XML_path }; + mnType = STATIC_ARRAY_SELECT( spnTypes, nType, XML_TOKEN_INVALID ); +} + +void GradientFillModel::readGradientStop( RecordInputStream& rStrm, bool bDxf ) +{ + Color aColor; + double fPosition; + if( bDxf ) + { + rStrm.skip( 2 ); + rStrm >> fPosition >> aColor; + } + else + { + rStrm >> aColor >> fPosition; + } + if( !rStrm.isEof() && (fPosition >= 0.0) ) + maColors[ fPosition ] = aColor; +} + +// ---------------------------------------------------------------------------- + +ApiSolidFillData::ApiSolidFillData() : + mnColor( API_RGB_TRANSPARENT ), + mbTransparent( true ), + mbUsed( false ) +{ +} + +bool operator==( const ApiSolidFillData& rLeft, const ApiSolidFillData& rRight ) +{ + return + (rLeft.mnColor == rRight.mnColor) && + (rLeft.mbTransparent == rRight.mbTransparent) && + (rLeft.mbUsed == rRight.mbUsed); +} + +// ============================================================================ + +namespace { + +inline sal_Int32 lclGetMixedColorComp( sal_Int32 nPatt, sal_Int32 nFill, sal_Int32 nAlpha ) +{ + return ((nPatt - nFill) * nAlpha) / 0x80 + nFill; +} + +sal_Int32 lclGetMixedColor( sal_Int32 nPattColor, sal_Int32 nFillColor, sal_Int32 nAlpha ) +{ + return + (lclGetMixedColorComp( nPattColor & 0xFF0000, nFillColor & 0xFF0000, nAlpha ) & 0xFF0000) | + (lclGetMixedColorComp( nPattColor & 0x00FF00, nFillColor & 0x00FF00, nAlpha ) & 0x00FF00) | + (lclGetMixedColorComp( nPattColor & 0x0000FF, nFillColor & 0x0000FF, nAlpha ) & 0x0000FF); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +Fill::Fill( const WorkbookHelper& rHelper, bool bDxf ) : + WorkbookHelper( rHelper ), + mbDxf( bDxf ) +{ +} + +void Fill::importPatternFill( const AttributeList& rAttribs ) +{ + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->mnPattern = rAttribs.getToken( XML_patternType, XML_none ); + if( mbDxf ) + mxPatternModel->mbPatternUsed = rAttribs.hasAttribute( XML_patternType ); +} + +void Fill::importFgColor( const AttributeList& rAttribs ) +{ + OSL_ENSURE( mxPatternModel.get(), "Fill::importFgColor - missing pattern data" ); + if( mxPatternModel.get() ) + { + mxPatternModel->maPatternColor.importColor( rAttribs ); + mxPatternModel->mbPattColorUsed = true; + } +} + +void Fill::importBgColor( const AttributeList& rAttribs ) +{ + OSL_ENSURE( mxPatternModel.get(), "Fill::importBgColor - missing pattern data" ); + if( mxPatternModel.get() ) + { + mxPatternModel->maFillColor.importColor( rAttribs ); + mxPatternModel->mbFillColorUsed = true; + } +} + +void Fill::importGradientFill( const AttributeList& rAttribs ) +{ + mxGradientModel.reset( new GradientFillModel ); + mxGradientModel->mnType = rAttribs.getToken( XML_type, XML_linear ); + mxGradientModel->mfAngle = rAttribs.getDouble( XML_degree, 0.0 ); + mxGradientModel->mfLeft = rAttribs.getDouble( XML_left, 0.0 ); + mxGradientModel->mfRight = rAttribs.getDouble( XML_right, 0.0 ); + mxGradientModel->mfTop = rAttribs.getDouble( XML_top, 0.0 ); + mxGradientModel->mfBottom = rAttribs.getDouble( XML_bottom, 0.0 ); +} + +void Fill::importColor( const AttributeList& rAttribs, double fPosition ) +{ + OSL_ENSURE( mxGradientModel.get(), "Fill::importColor - missing gradient data" ); + if( mxGradientModel.get() && (fPosition >= 0.0) ) + mxGradientModel->maColors[ fPosition ].importColor( rAttribs ); +} + +void Fill::importFill( RecordInputStream& rStrm ) +{ + OSL_ENSURE( !mbDxf, "Fill::importFill - unexpected conditional formatting flag" ); + sal_Int32 nPattern = rStrm.readInt32(); + if( nPattern == OOBIN_FILL_GRADIENT ) + { + mxGradientModel.reset( new GradientFillModel ); + sal_Int32 nStopCount; + rStrm.skip( 16 ); + mxGradientModel->readGradient( rStrm ); + rStrm >> nStopCount; + for( sal_Int32 nStop = 0; (nStop < nStopCount) && !rStrm.isEof(); ++nStop ) + mxGradientModel->readGradientStop( rStrm, false ); + } + else + { + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->setBinPattern( nPattern ); + rStrm >> mxPatternModel->maPatternColor >> mxPatternModel->maFillColor; + } +} + +void Fill::importDxfPattern( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Fill::importDxfPattern - missing conditional formatting flag" ); + if( !mxPatternModel ) + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->setBinPattern( rStrm.readuInt8() ); + mxPatternModel->mbPatternUsed = true; +} + +void Fill::importDxfFgColor( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Fill::importDxfFgColor - missing conditional formatting flag" ); + if( !mxPatternModel ) + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->maPatternColor.importColor( rStrm ); + mxPatternModel->mbPattColorUsed = true; +} + +void Fill::importDxfBgColor( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Fill::importDxfBgColor - missing conditional formatting flag" ); + if( !mxPatternModel ) + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->maFillColor.importColor( rStrm ); + mxPatternModel->mbFillColorUsed = true; +} + +void Fill::importDxfGradient( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Fill::importDxfGradient - missing conditional formatting flag" ); + if( !mxGradientModel ) + mxGradientModel.reset( new GradientFillModel ); + mxGradientModel->readGradient( rStrm ); +} + +void Fill::importDxfStop( RecordInputStream& rStrm ) +{ + OSL_ENSURE( mbDxf, "Fill::importDxfStop - missing conditional formatting flag" ); + if( !mxGradientModel ) + mxGradientModel.reset( new GradientFillModel ); + mxGradientModel->readGradientStop( rStrm, true ); +} + +void Fill::setBiff2Data( sal_uInt8 nFlags ) +{ + OSL_ENSURE( !mbDxf, "Fill::setBiff2Data - unexpected conditional formatting flag" ); + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->setBiffData( + BIFF2_COLOR_BLACK, + BIFF2_COLOR_WHITE, + getFlagValue( nFlags, BIFF2_XF_BACKGROUND, BIFF_PATT_125, BIFF_PATT_NONE ) ); +} + +void Fill::setBiff3Data( sal_uInt16 nArea ) +{ + OSL_ENSURE( !mbDxf, "Fill::setBiff3Data - unexpected conditional formatting flag" ); + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->setBiffData( + extractValue< sal_uInt16 >( nArea, 6, 5 ), + extractValue< sal_uInt16 >( nArea, 11, 5 ), + extractValue< sal_uInt8 >( nArea, 0, 6 ) ); +} + +void Fill::setBiff5Data( sal_uInt32 nArea ) +{ + OSL_ENSURE( !mbDxf, "Fill::setBiff5Data - unexpected conditional formatting flag" ); + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->setBiffData( + extractValue< sal_uInt16 >( nArea, 0, 7 ), + extractValue< sal_uInt16 >( nArea, 7, 7 ), + extractValue< sal_uInt8 >( nArea, 16, 6 ) ); +} + +void Fill::setBiff8Data( sal_uInt32 nBorder2, sal_uInt16 nArea ) +{ + OSL_ENSURE( !mbDxf, "Fill::setBiff8Data - unexpected conditional formatting flag" ); + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + mxPatternModel->setBiffData( + extractValue< sal_uInt16 >( nArea, 0, 7 ), + extractValue< sal_uInt16 >( nArea, 7, 7 ), + extractValue< sal_uInt8 >( nBorder2, 26, 6 ) ); +} + +void Fill::importCfRule( BiffInputStream& rStrm, sal_uInt32 nFlags ) +{ + OSL_ENSURE( mbDxf, "Fill::importCfRule - missing conditional formatting flag" ); + OSL_ENSURE( getFlag( nFlags, BIFF_CFRULE_FILLBLOCK ), "Fill::importCfRule - missing fill block flag" ); + mxPatternModel.reset( new PatternFillModel( mbDxf ) ); + sal_uInt32 nFillData; + rStrm >> nFillData; + mxPatternModel->setBiffData( + extractValue< sal_uInt16 >( nFillData, 16, 7 ), + extractValue< sal_uInt16 >( nFillData, 23, 7 ), + extractValue< sal_uInt8 >( nFillData, 10, 6 ) ); + mxPatternModel->mbPattColorUsed = !getFlag( nFlags, BIFF_CFRULE_FILL_PATTCOLOR ); + mxPatternModel->mbFillColorUsed = !getFlag( nFlags, BIFF_CFRULE_FILL_FILLCOLOR ); + mxPatternModel->mbPatternUsed = !getFlag( nFlags, BIFF_CFRULE_FILL_PATTERN ); +} + +void Fill::finalizeImport() +{ + const GraphicHelper& rGraphicHelper = getBaseFilter().getGraphicHelper(); + + if( mxPatternModel.get() ) + { + // finalize the OOX data struct + PatternFillModel& rModel = *mxPatternModel; + if( mbDxf ) + { + if( rModel.mbFillColorUsed && (!rModel.mbPatternUsed || (rModel.mnPattern == XML_solid)) ) + { + rModel.maPatternColor = rModel.maFillColor; + rModel.mnPattern = XML_solid; + rModel.mbPattColorUsed = rModel.mbPatternUsed = true; + } + else if( !rModel.mbFillColorUsed && rModel.mbPatternUsed && (rModel.mnPattern == XML_solid) ) + { + rModel.mbPatternUsed = false; + } + } + + // convert to API fill settings + maApiData.mbUsed = rModel.mbPatternUsed; + if( rModel.mnPattern == XML_none ) + { + maApiData.mnColor = API_RGB_TRANSPARENT; + maApiData.mbTransparent = true; + } + else + { + sal_Int32 nAlpha = 0x80; + switch( rModel.mnPattern ) + { + case XML_darkDown: nAlpha = 0x40; break; + case XML_darkGray: nAlpha = 0x60; break; + case XML_darkGrid: nAlpha = 0x40; break; + case XML_darkHorizontal: nAlpha = 0x40; break; + case XML_darkTrellis: nAlpha = 0x60; break; + case XML_darkUp: nAlpha = 0x40; break; + case XML_darkVertical: nAlpha = 0x40; break; + case XML_gray0625: nAlpha = 0x08; break; + case XML_gray125: nAlpha = 0x10; break; + case XML_lightDown: nAlpha = 0x20; break; + case XML_lightGray: nAlpha = 0x20; break; + case XML_lightGrid: nAlpha = 0x38; break; + case XML_lightHorizontal: nAlpha = 0x20; break; + case XML_lightTrellis: nAlpha = 0x30; break; + case XML_lightUp: nAlpha = 0x20; break; + case XML_lightVertical: nAlpha = 0x20; break; + case XML_mediumGray: nAlpha = 0x40; break; + case XML_solid: nAlpha = 0x80; break; + } + + sal_Int32 nWinTextColor = rGraphicHelper.getSystemColor( XML_windowText ); + sal_Int32 nWinColor = rGraphicHelper.getSystemColor( XML_window ); + + if( !rModel.mbPattColorUsed ) + rModel.maPatternColor.setAuto(); + sal_Int32 nPattColor = rModel.maPatternColor.getColor( rGraphicHelper, nWinTextColor ); + + if( !rModel.mbFillColorUsed ) + rModel.maFillColor.setAuto(); + sal_Int32 nFillColor = rModel.maFillColor.getColor( rGraphicHelper, nWinColor ); + + maApiData.mnColor = lclGetMixedColor( nPattColor, nFillColor, nAlpha ); + maApiData.mbTransparent = false; + } + } + else if( mxGradientModel.get() && !mxGradientModel->maColors.empty() ) + { + GradientFillModel& rModel = *mxGradientModel; + maApiData.mbUsed = true; // no support for differential attributes + GradientFillModel::ColorMap::const_iterator aIt = rModel.maColors.begin(); + OSL_ENSURE( !aIt->second.isAuto(), "Fill::finalizeImport - automatic gradient color" ); + maApiData.mnColor = aIt->second.getColor( rGraphicHelper, API_RGB_WHITE ); + if( ++aIt != rModel.maColors.end() ) + { + OSL_ENSURE( !aIt->second.isAuto(), "Fill::finalizeImport - automatic gradient color" ); + sal_Int32 nEndColor = aIt->second.getColor( rGraphicHelper, API_RGB_WHITE ); + maApiData.mnColor = lclGetMixedColor( maApiData.mnColor, nEndColor, 0x40 ); + maApiData.mbTransparent = false; + } + } +} + +void Fill::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + if( maApiData.mbUsed ) + { + rPropMap[ PROP_CellBackColor ] <<= maApiData.mnColor; + rPropMap[ PROP_IsCellBackgroundTransparent ] <<= maApiData.mbTransparent; + } +} + +// ============================================================================ + +XfModel::XfModel() : + mnStyleXfId( -1 ), + mnFontId( -1 ), + mnNumFmtId( -1 ), + mnBorderId( -1 ), + mnFillId( -1 ), + mbCellXf( true ), + mbFontUsed( false ), + mbNumFmtUsed( false ), + mbAlignUsed( false ), + mbProtUsed( false ), + mbBorderUsed( false ), + mbAreaUsed( false ) +{ +} + +// ============================================================================ + +Xf::Xf( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + maAlignment( rHelper ), + maProtection( rHelper ), + meRotationRef( ::com::sun::star::table::CellVertJustify_STANDARD ) +{ +} + +void Xf::setAllUsedFlags( bool bUsed ) +{ + maModel.mbAlignUsed = maModel.mbProtUsed = maModel.mbFontUsed = + maModel.mbNumFmtUsed = maModel.mbBorderUsed = maModel.mbAreaUsed = bUsed; +} + +void Xf::importXf( const AttributeList& rAttribs, bool bCellXf ) +{ + maModel.mbCellXf = bCellXf; + maModel.mnStyleXfId = rAttribs.getInteger( XML_xfId, -1 ); + maModel.mnFontId = rAttribs.getInteger( XML_fontId, -1 ); + maModel.mnNumFmtId = rAttribs.getInteger( XML_numFmtId, -1 ); + maModel.mnBorderId = rAttribs.getInteger( XML_borderId, -1 ); + maModel.mnFillId = rAttribs.getInteger( XML_fillId, -1 ); + + /* Default value of the apply*** attributes is dependent on context: + true in cellStyleXfs element, false in cellXfs element... */ + maModel.mbAlignUsed = rAttribs.getBool( XML_applyAlignment, !maModel.mbCellXf ); + maModel.mbProtUsed = rAttribs.getBool( XML_applyProtection, !maModel.mbCellXf ); + maModel.mbFontUsed = rAttribs.getBool( XML_applyFont, !maModel.mbCellXf ); + maModel.mbNumFmtUsed = rAttribs.getBool( XML_applyNumberFormat, !maModel.mbCellXf ); + maModel.mbBorderUsed = rAttribs.getBool( XML_applyBorder, !maModel.mbCellXf ); + maModel.mbAreaUsed = rAttribs.getBool( XML_applyFill, !maModel.mbCellXf ); +} + +void Xf::importAlignment( const AttributeList& rAttribs ) +{ + maAlignment.importAlignment( rAttribs ); +} + +void Xf::importProtection( const AttributeList& rAttribs ) +{ + maProtection.importProtection( rAttribs ); +} + +void Xf::importXf( RecordInputStream& rStrm, bool bCellXf ) +{ + maModel.mbCellXf = bCellXf; + maModel.mnStyleXfId = rStrm.readuInt16(); + maModel.mnNumFmtId = rStrm.readuInt16(); + maModel.mnFontId = rStrm.readuInt16(); + maModel.mnFillId = rStrm.readuInt16(); + maModel.mnBorderId = rStrm.readuInt16(); + sal_uInt32 nFlags = rStrm.readuInt32(); + maAlignment.setBinData( nFlags ); + maProtection.setBinData( nFlags ); + // used flags, see comments in Xf::setBiffUsedFlags() + sal_uInt16 nUsedFlags = rStrm.readuInt16(); + maModel.mbFontUsed = maModel.mbCellXf == getFlag( nUsedFlags, OOBIN_XF_FONT_USED ); + maModel.mbNumFmtUsed = maModel.mbCellXf == getFlag( nUsedFlags, OOBIN_XF_NUMFMT_USED ); + maModel.mbAlignUsed = maModel.mbCellXf == getFlag( nUsedFlags, OOBIN_XF_ALIGN_USED ); + maModel.mbProtUsed = maModel.mbCellXf == getFlag( nUsedFlags, OOBIN_XF_PROT_USED ); + maModel.mbBorderUsed = maModel.mbCellXf == getFlag( nUsedFlags, OOBIN_XF_BORDER_USED ); + maModel.mbAreaUsed = maModel.mbCellXf == getFlag( nUsedFlags, OOBIN_XF_AREA_USED ); +} + +void Xf::importXf( BiffInputStream& rStrm ) +{ + BorderRef xBorder = getStyles().createBorder( &maModel.mnBorderId ); + FillRef xFill = getStyles().createFill( &maModel.mnFillId ); + + switch( getBiff() ) + { + case BIFF2: + { + sal_uInt8 nFontId, nNumFmtId, nFlags; + rStrm >> nFontId; + rStrm.skip( 1 ); + rStrm >> nNumFmtId >> nFlags; + + // only cell XFs in BIFF2, no parent style, used flags always true + setAllUsedFlags( true ); + + // attributes + maAlignment.setBiff2Data( nFlags ); + maProtection.setBiff2Data( nNumFmtId ); + xBorder->setBiff2Data( nFlags ); + xFill->setBiff2Data( nFlags ); + maModel.mnFontId = static_cast< sal_Int32 >( nFontId ); + maModel.mnNumFmtId = static_cast< sal_Int32 >( nNumFmtId & BIFF2_XF_VALFMT_MASK ); + } + break; + + case BIFF3: + { + sal_uInt32 nBorder; + sal_uInt16 nTypeProt, nAlign, nArea; + sal_uInt8 nFontId, nNumFmtId; + rStrm >> nFontId >> nNumFmtId >> nTypeProt >> nAlign >> nArea >> nBorder; + + // XF type/parent + maModel.mbCellXf = !getFlag( nTypeProt, BIFF_XF_STYLE ); // new in BIFF3 + maModel.mnStyleXfId = extractValue< sal_Int32 >( nAlign, 4, 12 ); // new in BIFF3 + // attribute used flags + setBiffUsedFlags( extractValue< sal_uInt8 >( nTypeProt, 10, 6 ) ); // new in BIFF3 + + // attributes + maAlignment.setBiff3Data( nAlign ); + maProtection.setBiff3Data( nTypeProt ); + xBorder->setBiff3Data( nBorder ); + xFill->setBiff3Data( nArea ); + maModel.mnFontId = static_cast< sal_Int32 >( nFontId ); + maModel.mnNumFmtId = static_cast< sal_Int32 >( nNumFmtId ); + } + break; + + case BIFF4: + { + sal_uInt32 nBorder; + sal_uInt16 nTypeProt, nAlign, nArea; + sal_uInt8 nFontId, nNumFmtId; + rStrm >> nFontId >> nNumFmtId >> nTypeProt >> nAlign >> nArea >> nBorder; + + // XF type/parent + maModel.mbCellXf = !getFlag( nTypeProt, BIFF_XF_STYLE ); + maModel.mnStyleXfId = extractValue< sal_Int32 >( nTypeProt, 4, 12 ); + // attribute used flags + setBiffUsedFlags( extractValue< sal_uInt8 >( nAlign, 10, 6 ) ); + + // attributes + maAlignment.setBiff4Data( nAlign ); + maProtection.setBiff3Data( nTypeProt ); + xBorder->setBiff3Data( nBorder ); + xFill->setBiff3Data( nArea ); + maModel.mnFontId = static_cast< sal_Int32 >( nFontId ); + maModel.mnNumFmtId = static_cast< sal_Int32 >( nNumFmtId ); + } + break; + + case BIFF5: + { + sal_uInt32 nArea, nBorder; + sal_uInt16 nFontId, nNumFmtId, nTypeProt, nAlign; + rStrm >> nFontId >> nNumFmtId >> nTypeProt >> nAlign >> nArea >> nBorder; + + // XF type/parent + maModel.mbCellXf = !getFlag( nTypeProt, BIFF_XF_STYLE ); + maModel.mnStyleXfId = extractValue< sal_Int32 >( nTypeProt, 4, 12 ); + // attribute used flags + setBiffUsedFlags( extractValue< sal_uInt8 >( nAlign, 10, 6 ) ); + + // attributes + maAlignment.setBiff5Data( nAlign ); + maProtection.setBiff3Data( nTypeProt ); + xBorder->setBiff5Data( nBorder, nArea ); + xFill->setBiff5Data( nArea ); + maModel.mnFontId = static_cast< sal_Int32 >( nFontId ); + maModel.mnNumFmtId = static_cast< sal_Int32 >( nNumFmtId ); + } + break; + + case BIFF8: + { + sal_uInt32 nBorder1, nBorder2; + sal_uInt16 nFontId, nNumFmtId, nTypeProt, nAlign, nMiscAttrib, nArea; + rStrm >> nFontId >> nNumFmtId >> nTypeProt >> nAlign >> nMiscAttrib >> nBorder1 >> nBorder2 >> nArea; + + // XF type/parent + maModel.mbCellXf = !getFlag( nTypeProt, BIFF_XF_STYLE ); + maModel.mnStyleXfId = extractValue< sal_Int32 >( nTypeProt, 4, 12 ); + // attribute used flags + setBiffUsedFlags( extractValue< sal_uInt8 >( nMiscAttrib, 10, 6 ) ); + + // attributes + maAlignment.setBiff8Data( nAlign, nMiscAttrib ); + maProtection.setBiff3Data( nTypeProt ); + xBorder->setBiff8Data( nBorder1, nBorder2 ); + xFill->setBiff8Data( nBorder2, nArea ); + maModel.mnFontId = static_cast< sal_Int32 >( nFontId ); + maModel.mnNumFmtId = static_cast< sal_Int32 >( nNumFmtId ); + } + break; + + case BIFF_UNKNOWN: break; + } +} + +void Xf::finalizeImport() +{ + StylesBuffer& rStyles = getStyles(); + + // alignment and protection + maAlignment.finalizeImport(); + maProtection.finalizeImport(); + + /* Enables the used flags, if the formatting attributes differ from the + style XF. In cell XFs Excel uses the cell attributes, if they differ + from the parent style XF (even if the used flag is switched off). + #109899# ...or if the respective flag is not set in parent style XF. + */ + const Xf* pStyleXf = isCellXf() ? rStyles.getStyleXf( maModel.mnStyleXfId ).get() : 0; + if( pStyleXf ) + { + const XfModel& rStyleData = pStyleXf->maModel; + if( !maModel.mbFontUsed ) + maModel.mbFontUsed = !rStyleData.mbFontUsed || (maModel.mnFontId != rStyleData.mnFontId); + if( !maModel.mbNumFmtUsed ) + maModel.mbNumFmtUsed = !rStyleData.mbNumFmtUsed || (maModel.mnNumFmtId != rStyleData.mnNumFmtId); + if( !maModel.mbAlignUsed ) + maModel.mbAlignUsed = !rStyleData.mbAlignUsed || !(maAlignment.getApiData() == pStyleXf->maAlignment.getApiData()); + if( !maModel.mbProtUsed ) + maModel.mbProtUsed = !rStyleData.mbProtUsed || !(maProtection.getApiData() == pStyleXf->maProtection.getApiData()); + if( !maModel.mbBorderUsed ) + maModel.mbBorderUsed = !rStyleData.mbBorderUsed || !rStyles.equalBorders( maModel.mnBorderId, rStyleData.mnBorderId ); + if( !maModel.mbAreaUsed ) + maModel.mbAreaUsed = !rStyleData.mbAreaUsed || !rStyles.equalFills( maModel.mnFillId, rStyleData.mnFillId ); + } + + /* #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( const Alignment* pAlignment = maModel.mbAlignUsed ? &maAlignment : (pStyleXf ? &pStyleXf->maAlignment : 0) ) + { + sal_Int32 nBorderId = maModel.mbBorderUsed ? maModel.mnBorderId : (pStyleXf ? pStyleXf->maModel.mnBorderId : -1); + if( const Border* pBorder = rStyles.getBorder( nBorderId ).get() ) + if( (pAlignment->getApiData().mnRotation != 0) && pBorder->getApiData().hasAnyOuterBorder() ) + meRotationRef = ::com::sun::star::table::CellVertJustify_BOTTOM; + } +} + +FontRef Xf::getFont() const +{ + return getStyles().getFont( maModel.mnFontId ); +} + +bool Xf::hasAnyUsedFlags() const +{ + return + maModel.mbAlignUsed || maModel.mbProtUsed || maModel.mbFontUsed || + maModel.mbNumFmtUsed || maModel.mbBorderUsed || maModel.mbAreaUsed; +} + +void Xf::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + StylesBuffer& rStyles = getStyles(); + + // create and set cell style + if( isCellXf() ) + rPropMap[ PROP_CellStyle ] <<= rStyles.createCellStyle( maModel.mnStyleXfId ); + + if( maModel.mbFontUsed ) + rStyles.writeFontToPropertyMap( rPropMap, maModel.mnFontId ); + if( maModel.mbNumFmtUsed ) + rStyles.writeNumFmtToPropertyMap( rPropMap, maModel.mnNumFmtId ); + if( maModel.mbAlignUsed ) + maAlignment.writeToPropertyMap( rPropMap ); + if( maModel.mbProtUsed ) + maProtection.writeToPropertyMap( rPropMap ); + if( maModel.mbBorderUsed ) + rStyles.writeBorderToPropertyMap( rPropMap, maModel.mnBorderId ); + if( maModel.mbAreaUsed ) + rStyles.writeFillToPropertyMap( rPropMap, maModel.mnFillId ); + if( maModel.mbAlignUsed || maModel.mbBorderUsed ) + rPropMap[ PROP_RotateReference ] <<= meRotationRef; +} + +void Xf::writeToPropertySet( PropertySet& rPropSet ) const +{ + PropertyMap aPropMap; + writeToPropertyMap( aPropMap ); + rPropSet.setProperties( aPropMap ); +} + +void Xf::setBiffUsedFlags( sal_uInt8 nUsedFlags ) +{ + /* Notes about finding the used flags: + - In cell XFs a *set* bit means a used attribute. + - In style XFs a *cleared* bit means a used attribute. + The boolean flags always store true, if the attribute is used. + The "isCellXf() == getFlag(...)" construct evaluates to true in both + mentioned cases: cell XF and set bit; or style XF and cleared bit. + */ + maModel.mbFontUsed = isCellXf() == getFlag( nUsedFlags, BIFF_XF_FONT_USED ); + maModel.mbNumFmtUsed = isCellXf() == getFlag( nUsedFlags, BIFF_XF_NUMFMT_USED ); + maModel.mbAlignUsed = isCellXf() == getFlag( nUsedFlags, BIFF_XF_ALIGN_USED ); + maModel.mbProtUsed = isCellXf() == getFlag( nUsedFlags, BIFF_XF_PROT_USED ); + maModel.mbBorderUsed = isCellXf() == getFlag( nUsedFlags, BIFF_XF_BORDER_USED ); + maModel.mbAreaUsed = isCellXf() == getFlag( nUsedFlags, BIFF_XF_AREA_USED ); +} + +// ============================================================================ + +Dxf::Dxf( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +FontRef Dxf::createFont( bool bAlwaysNew ) +{ + if( bAlwaysNew || !mxFont ) + mxFont.reset( new Font( *this, true ) ); + return mxFont; +} + +BorderRef Dxf::createBorder( bool bAlwaysNew ) +{ + if( bAlwaysNew || !mxBorder ) + mxBorder.reset( new Border( *this, true ) ); + return mxBorder; +} + +FillRef Dxf::createFill( bool bAlwaysNew ) +{ + if( bAlwaysNew || !mxFill ) + mxFill.reset( new Fill( *this, true ) ); + return mxFill; +} + +void Dxf::importNumFmt( const AttributeList& rAttribs ) +{ + mxNumFmt = getStyles().importNumFmt( rAttribs ); +} + +void Dxf::importAlignment( const AttributeList& rAttribs ) +{ + mxAlignment.reset( new Alignment( *this ) ); + mxAlignment->importAlignment( rAttribs ); +} + +void Dxf::importProtection( const AttributeList& rAttribs ) +{ + mxProtection.reset( new Protection( *this ) ); + mxProtection->importProtection( rAttribs ); +} + +void Dxf::importDxf( RecordInputStream& rStrm ) +{ + sal_Int32 nNumFmtId = -1; + OUString aFmtCode; + sal_uInt16 nRecCount; + rStrm.skip( 4 ); // flags + rStrm >> nRecCount; + for( sal_uInt16 nRec = 0; !rStrm.isEof() && (nRec < nRecCount); ++nRec ) + { + sal_uInt16 nSubRecId, nSubRecSize; + sal_Int64 nRecEnd = rStrm.tell(); + rStrm >> nSubRecId >> nSubRecSize; + nRecEnd += nSubRecSize; + switch( nSubRecId ) + { + case OOBIN_DXF_FILL_PATTERN: createFill( false )->importDxfPattern( rStrm ); break; + case OOBIN_DXF_FILL_FGCOLOR: createFill( false )->importDxfFgColor( rStrm ); break; + case OOBIN_DXF_FILL_BGCOLOR: createFill( false )->importDxfBgColor( rStrm ); break; + case OOBIN_DXF_FILL_GRADIENT: createFill( false )->importDxfGradient( rStrm ); break; + case OOBIN_DXF_FILL_STOP: createFill( false )->importDxfStop( rStrm ); break; + case OOBIN_DXF_FONT_COLOR: createFont( false )->importDxfColor( rStrm ); break; + case OOBIN_DXF_BORDER_TOP: createBorder( false )->importDxfBorder( XLS_TOKEN( top ), rStrm ); break; + case OOBIN_DXF_BORDER_BOTTOM: createBorder( false )->importDxfBorder( XLS_TOKEN( bottom ), rStrm ); break; + case OOBIN_DXF_BORDER_LEFT: createBorder( false )->importDxfBorder( XLS_TOKEN( left ), rStrm ); break; + case OOBIN_DXF_BORDER_RIGHT: createBorder( false )->importDxfBorder( XLS_TOKEN( right ), rStrm ); break; + case OOBIN_DXF_FONT_NAME: createFont( false )->importDxfName( rStrm ); break; + case OOBIN_DXF_FONT_WEIGHT: createFont( false )->importDxfWeight( rStrm ); break; + case OOBIN_DXF_FONT_UNDERLINE: createFont( false )->importDxfUnderline( rStrm ); break; + case OOBIN_DXF_FONT_ESCAPEMENT: createFont( false )->importDxfEscapement( rStrm ); break; + case OOBIN_DXF_FONT_ITALIC: createFont( false )->importDxfFlag( XML_i, rStrm ); break; + case OOBIN_DXF_FONT_STRIKE: createFont( false )->importDxfFlag( XML_strike, rStrm ); break; + case OOBIN_DXF_FONT_OUTLINE: createFont( false )->importDxfFlag( XML_outline, rStrm ); break; + case OOBIN_DXF_FONT_SHADOW: createFont( false )->importDxfFlag( XML_shadow, rStrm ); break; + case OOBIN_DXF_FONT_HEIGHT: createFont( false )->importDxfHeight( rStrm ); break; + case OOBIN_DXF_FONT_SCHEME: createFont( false )->importDxfScheme( rStrm ); break; + case OOBIN_DXF_NUMFMT_CODE: aFmtCode = rStrm.readString( false ); break; + case OOBIN_DXF_NUMFMT_ID: nNumFmtId = rStrm.readuInt16(); break; + } + rStrm.seek( nRecEnd ); + } + OSL_ENSURE( !rStrm.isEof() && (rStrm.getRemaining() == 0), "Dxf::importDxf - unexpected remaining data" ); + mxNumFmt = getStyles().createNumFmt( nNumFmtId, aFmtCode ); +} + +void Dxf::importCfRule( BiffInputStream& rStrm, sal_uInt32 nFlags ) +{ + if( getFlag( nFlags, BIFF_CFRULE_FONTBLOCK ) ) + createFont()->importCfRule( rStrm ); + if( getFlag( nFlags, BIFF_CFRULE_ALIGNBLOCK ) ) + rStrm.skip( 8 ); + if( getFlag( nFlags, BIFF_CFRULE_BORDERBLOCK ) ) + createBorder()->importCfRule( rStrm, nFlags ); + if( getFlag( nFlags, BIFF_CFRULE_FILLBLOCK ) ) + createFill()->importCfRule( rStrm, nFlags ); + if( getFlag( nFlags, BIFF_CFRULE_PROTBLOCK ) ) + rStrm.skip( 2 ); +} + +void Dxf::finalizeImport() +{ + if( mxFont.get() ) + mxFont->finalizeImport(); + // number format already finalized by the number formats buffer + if( mxAlignment.get() ) + mxAlignment->finalizeImport(); + if( mxProtection.get() ) + mxProtection->finalizeImport(); + if( mxBorder.get() ) + mxBorder->finalizeImport(); + if( mxFill.get() ) + mxFill->finalizeImport(); +} + +void Dxf::writeToPropertyMap( PropertyMap& rPropMap ) const +{ + if( mxFont.get() ) + mxFont->writeToPropertyMap( rPropMap, FONT_PROPTYPE_CELL ); + if( mxNumFmt.get() ) + mxNumFmt->writeToPropertyMap( rPropMap ); + if( mxAlignment.get() ) + mxAlignment->writeToPropertyMap( rPropMap ); + if( mxProtection.get() ) + mxProtection->writeToPropertyMap( rPropMap ); + if( mxBorder.get() ) + mxBorder->writeToPropertyMap( rPropMap ); + if( mxFill.get() ) + mxFill->writeToPropertyMap( rPropMap ); +} + +void Dxf::writeToPropertySet( PropertySet& rPropSet ) const +{ + PropertyMap aPropMap; + writeToPropertyMap( aPropMap ); + rPropSet.setProperties( aPropMap ); +} + +// ============================================================================ + +namespace { + +const sal_Char* const spcLegacyStyleNamePrefix = "Excel_BuiltIn_"; +const sal_Char* const sppcLegacyStyleNames[] = +{ + "Normal", + "RowLevel_", // outline level will be appended + "ColumnLevel_", // outline level will be appended + "Comma", + "Currency", + "Percent", + "Comma_0", // new in BIFF4 + "Currency_0", + "Hyperlink", // new in BIFF8 + "Followed_Hyperlink" +}; +const sal_Int32 snLegacyStyleNamesCount = static_cast< sal_Int32 >( STATIC_ARRAY_SIZE( sppcLegacyStyleNames ) ); + +const sal_Char* const spcStyleNamePrefix = "Excel Built-in "; +const sal_Char* const sppcStyleNames[] = +{ + "Normal", + "RowLevel_", // outline level will be appended + "ColLevel_", // outline level will be appended + "Comma", + "Currency", + "Percent", + "Comma [0]", // new in BIFF4 + "Currency [0]", + "Hyperlink", // new in BIFF8 + "Followed Hyperlink", + "Note", // new in OOX + "Warning Text", + "", + "", + "", + "Title", + "Heading 1", + "Heading 2", + "Heading 3", + "Heading 4", + "Input", + "Output", + "Calculation", + "Check Cell", + "Linked Cell", + "Total", + "Good", + "Bad", + "Neutral", + "Accent1", + "20% - Accent1", + "40% - Accent1", + "60% - Accent1", + "Accent2", + "20% - Accent2", + "40% - Accent2", + "60% - Accent2", + "Accent3", + "20% - Accent3", + "40% - Accent3", + "60% - Accent3", + "Accent4", + "20% - Accent4", + "40% - Accent4", + "60% - Accent4", + "Accent5", + "20% - Accent5", + "40% - Accent5", + "60% - Accent5", + "Accent6", + "20% - Accent6", + "40% - Accent6", + "60% - Accent6", + "Explanatory Text" +}; +const sal_Int32 snStyleNamesCount = static_cast< sal_Int32 >( STATIC_ARRAY_SIZE( sppcStyleNames ) ); + +OUString lclGetBuiltinStyleName( sal_Int32 nBuiltinId, const OUString& rName, sal_Int32 nLevel = 0 ) +{ + OSL_ENSURE( (0 <= nBuiltinId) && (nBuiltinId < snStyleNamesCount), "lclGetBuiltinStyleName - unknown built-in style" ); + OUStringBuffer aStyleName; + aStyleName.appendAscii( spcStyleNamePrefix ); + if( (0 <= nBuiltinId) && (nBuiltinId < snStyleNamesCount) && (sppcStyleNames[ nBuiltinId ][ 0 ] != 0) ) + aStyleName.appendAscii( sppcStyleNames[ nBuiltinId ] ); + else if( rName.getLength() > 0 ) + aStyleName.append( rName ); + else + aStyleName.append( nBuiltinId ); + if( (nBuiltinId == OOX_STYLE_ROWLEVEL) || (nBuiltinId == OOX_STYLE_COLLEVEL) ) + aStyleName.append( nLevel ); + return aStyleName.makeStringAndClear(); +} + +OUString lclGetBuiltInStyleName( const OUString& rName ) +{ + OUStringBuffer aStyleName; + aStyleName.appendAscii( spcStyleNamePrefix ).append( rName ); + return aStyleName.makeStringAndClear(); +} + +bool lclIsBuiltinStyleName( const OUString& rStyleName, sal_Int32* pnBuiltinId, sal_Int32* pnNextChar ) +{ + // try the other built-in styles + OUString aPrefix = OUString::createFromAscii( spcStyleNamePrefix ); + sal_Int32 nPrefixLen = aPrefix.getLength(); + sal_Int32 nFoundId = 0; + sal_Int32 nNextChar = 0; + if( rStyleName.matchIgnoreAsciiCase( aPrefix ) ) + { + OUString aShortName; + for( sal_Int32 nId = 0; nId < snStyleNamesCount; ++nId ) + { + aShortName = OUString::createFromAscii( sppcStyleNames[ nId ] ); + if( rStyleName.matchIgnoreAsciiCase( aShortName, nPrefixLen ) && + (nNextChar < nPrefixLen + aShortName.getLength()) ) + { + nFoundId = nId; + nNextChar = nPrefixLen + aShortName.getLength(); + } + } + } + + if( nNextChar > 0 ) + { + if( pnBuiltinId ) *pnBuiltinId = nFoundId; + if( pnNextChar ) *pnNextChar = nNextChar; + return true; + } + + if( pnBuiltinId ) *pnBuiltinId = -1; + if( pnNextChar ) *pnNextChar = 0; + return false; +} + +bool lclGetBuiltinStyleId( sal_Int32& rnBuiltinId, sal_Int32& rnLevel, const OUString& rStyleName ) +{ + sal_Int32 nBuiltinId; + sal_Int32 nNextChar; + if( lclIsBuiltinStyleName( rStyleName, &nBuiltinId, &nNextChar ) ) + { + if( (nBuiltinId == OOX_STYLE_ROWLEVEL) || (nBuiltinId == OOX_STYLE_COLLEVEL) ) + { + OUString aLevel = rStyleName.copy( nNextChar ); + sal_Int32 nLevel = aLevel.toInt32(); + if( (0 < nLevel) && (nLevel <= OOX_STYLE_LEVELCOUNT) ) + { + rnBuiltinId = nBuiltinId; + rnLevel = nLevel; + return true; + } + } + else if( rStyleName.getLength() == nNextChar ) + { + rnBuiltinId = nBuiltinId; + rnLevel = 0; + return true; + } + } + rnBuiltinId = -1; + rnLevel = 0; + return false; +} + +} // namespace + +// ---------------------------------------------------------------------------- + +CellStyleModel::CellStyleModel() : + mnXfId( -1 ), + mnBuiltinId( -1 ), + mnLevel( 0 ), + mbBuiltin( false ), + mbCustom( false ), + mbHidden( false ) +{ +} + +bool CellStyleModel::isBuiltin() const +{ + return mbBuiltin && (mnBuiltinId >= 0); +} + +bool CellStyleModel::isDefaultStyle() const +{ + return mbBuiltin && (mnBuiltinId == OOX_STYLE_NORMAL); +} + +// ============================================================================ + +CellStyle::CellStyle( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mbCreated( false ) +{ +} + +void CellStyle::importCellStyle( const AttributeList& rAttribs ) +{ + maModel.maName = rAttribs.getXString( XML_name, OUString() ); + maModel.mnXfId = rAttribs.getInteger( XML_xfId, -1 ); + maModel.mnBuiltinId = rAttribs.getInteger( XML_builtinId, -1 ); + maModel.mnLevel = rAttribs.getInteger( XML_iLevel, 0 ); + maModel.mbBuiltin = rAttribs.hasAttribute( XML_builtinId ); + maModel.mbCustom = rAttribs.getBool( XML_customBuiltin, false ); + maModel.mbHidden = rAttribs.getBool( XML_hidden, false ); +} + +void CellStyle::importCellStyle( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> maModel.mnXfId >> nFlags; + maModel.mnBuiltinId = rStrm.readInt8(); + maModel.mnLevel = rStrm.readInt8(); + rStrm >> maModel.maName; + maModel.mbBuiltin = getFlag( nFlags, OOBIN_CELLSTYLE_BUILTIN ); + maModel.mbCustom = getFlag( nFlags, OOBIN_CELLSTYLE_CUSTOM ); + maModel.mbHidden = getFlag( nFlags, OOBIN_CELLSTYLE_HIDDEN ); +} + +void CellStyle::importStyle( BiffInputStream& rStrm ) +{ + sal_uInt16 nStyleXf; + rStrm >> nStyleXf; + maModel.mnXfId = static_cast< sal_Int32 >( nStyleXf & BIFF_STYLE_XFMASK ); + maModel.mbBuiltin = getFlag( nStyleXf, BIFF_STYLE_BUILTIN ); + if( maModel.mbBuiltin ) + { + maModel.mnBuiltinId = rStrm.readInt8(); + maModel.mnLevel = rStrm.readInt8(); + } + else + { + maModel.maName = (getBiff() == BIFF8) ? + rStrm.readUniString() : rStrm.readByteStringUC( false, getTextEncoding() ); + // #i103281# check if this is a new built-in style introduced in XL2007 + if( (getBiff() == BIFF8) && (rStrm.getNextRecId() == BIFF_ID_STYLEEXT) && rStrm.startNextRecord() ) + { + sal_uInt8 nExtFlags; + rStrm.skip( 12 ); + rStrm >> nExtFlags; + maModel.mbBuiltin = getFlag( nExtFlags, BIFF_STYLEEXT_BUILTIN ); + maModel.mbCustom = getFlag( nExtFlags, BIFF_STYLEEXT_CUSTOM ); + maModel.mbHidden = getFlag( nExtFlags, BIFF_STYLEEXT_HIDDEN ); + if( maModel.mbBuiltin ) + { + maModel.mnBuiltinId = rStrm.readInt8(); + maModel.mnLevel = rStrm.readInt8(); + } + } + } +} + +void CellStyle::createCellStyle() +{ + // #i1624# #i1768# ignore unnamed user styles + if( !mbCreated ) + mbCreated = maFinalName.getLength() == 0; + + /* #i103281# do not create another style of the same name, if it exists + already. This is needed to prevent that styles pasted from clipboard + get duplicated over and over. */ + if( !mbCreated ) try + { + Reference< XNameAccess > xCellStylesNA( getStyleFamily( false ), UNO_QUERY_THROW ); + mbCreated = xCellStylesNA->hasByName( maFinalName ); + } + catch( Exception& ) + { + } + + // create the style object in the document + if( !mbCreated ) try + { + mbCreated = true; + Reference< XStyle > xStyle( createStyleObject( maFinalName, false ), UNO_SET_THROW ); + // write style formatting properties + PropertySet aPropSet( xStyle ); + getStyles().writeStyleXfToPropertySet( aPropSet, maModel.mnXfId ); + if( !maModel.isDefaultStyle() ) + xStyle->setParentStyle( getStyles().getDefaultStyleName() ); + } + catch( Exception& ) + { + } +} + +void CellStyle::finalizeImport( const OUString& rFinalName ) +{ + maFinalName = rFinalName; + if( !maModel.isBuiltin() || maModel.mbCustom ) + createCellStyle(); +} + +// ============================================================================ + +CellStyleBuffer::CellStyleBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +CellStyleRef CellStyleBuffer::importCellStyle( const AttributeList& rAttribs ) +{ + CellStyleRef xCellStyle( new CellStyle( *this ) ); + xCellStyle->importCellStyle( rAttribs ); + insertCellStyle( xCellStyle ); + return xCellStyle; +} + +CellStyleRef CellStyleBuffer::importCellStyle( RecordInputStream& rStrm ) +{ + CellStyleRef xCellStyle( new CellStyle( *this ) ); + xCellStyle->importCellStyle( rStrm ); + insertCellStyle( xCellStyle ); + return xCellStyle; +} + +CellStyleRef CellStyleBuffer::importStyle( BiffInputStream& rStrm ) +{ + CellStyleRef xCellStyle( new CellStyle( *this ) ); + xCellStyle->importStyle( rStrm ); + insertCellStyle( xCellStyle ); + return xCellStyle; +} + +void CellStyleBuffer::finalizeImport() +{ + // calculate final names of all styles + typedef RefMap< OUString, CellStyle, IgnoreCaseCompare > CellStyleNameMap; + CellStyleNameMap aCellStyles; + CellStyleVector 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 + constructed 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 = (getFilterType() == FILTER_BIFF) && (getBiff() == BIFF4) && isWorkbookFile() && (getCurrentSheetIndex() > 0); + try + { + // unfortunately, com.sun.star.style.StyleFamily does not implement XEnumerationAccess... + Reference< XIndexAccess > xStyleFamilyIA( getStyleFamily( false ), UNO_QUERY_THROW ); + for( sal_Int32 nIndex = 0, nCount = xStyleFamilyIA->getCount(); nIndex < nCount; ++nIndex ) + { + Reference< XStyle > xStyle( xStyleFamilyIA->getByIndex( nIndex ), UNO_QUERY_THROW ); + if( bReserveAll || !xStyle->isUserDefined() ) + { + Reference< XNamed > xStyleName( xStyle, UNO_QUERY_THROW ); + // create an empty entry by using ::std::map<>::operator[] + aCellStyles[ xStyleName->getName() ]; + } + } + } + catch( Exception& ) + { + } + + /* Calculate names of built-in styles. Store styles with reserved names + in the aConflictNameStyles list. */ + for( CellStyleVector::iterator aIt = maBuiltinStyles.begin(), aEnd = maBuiltinStyles.end(); aIt != aEnd; ++aIt ) + { + const CellStyleModel& rModel = (*aIt)->getModel(); + OUString aStyleName = lclGetBuiltinStyleName( rModel.mnBuiltinId, rModel.maName, rModel.mnLevel ); + OSL_ENSURE( bReserveAll || (aCellStyles.count( aStyleName ) == 0), + "CellStyleBuffer::finalizeImport - multiple styles with equal built-in identifier" ); + if( aCellStyles.count( aStyleName ) > 0 ) + aConflictNameStyles.push_back( *aIt ); + else + aCellStyles[ aStyleName ] = *aIt; + } + + /* Calculate names of user defined styles. Store styles with reserved + names in the aConflictNameStyles list. */ + for( CellStyleVector::iterator aIt = maUserStyles.begin(), aEnd = maUserStyles.end(); aIt != aEnd; ++aIt ) + { + const CellStyleModel& rModel = (*aIt)->getModel(); + // #i1624# #i1768# ignore unnamed user styles + if( rModel.maName.getLength() > 0 ) + { + if( aCellStyles.count( rModel.maName ) > 0 ) + aConflictNameStyles.push_back( *aIt ); + else + aCellStyles[ rModel.maName ] = *aIt; + } + } + + // find unused names for all styles with conflicting names + for( CellStyleVector::iterator aIt = aConflictNameStyles.begin(), aEnd = aConflictNameStyles.end(); aIt != aEnd; ++aIt ) + { + const CellStyleModel& rModel = (*aIt)->getModel(); + OUString aUnusedName; + sal_Int32 nIndex = 0; + do + { + aUnusedName = OUStringBuffer( rModel.maName ).append( sal_Unicode( ' ' ) ).append( ++nIndex ).makeStringAndClear(); + } + while( aCellStyles.count( aUnusedName ) > 0 ); + aCellStyles[ aUnusedName ] = *aIt; + } + + // set final names and create user-defined and modified built-in cell styles + aCellStyles.forEachMemWithKey( &CellStyle::finalizeImport ); +} + +sal_Int32 CellStyleBuffer::getDefaultXfId() const +{ + return mxDefStyle.get() ? mxDefStyle->getModel().mnXfId : -1; +} + +OUString CellStyleBuffer::getDefaultStyleName() const +{ + return createCellStyle( mxDefStyle ); +} + +OUString CellStyleBuffer::createCellStyle( sal_Int32 nXfId ) const +{ + return createCellStyle( maStylesByXf.get( nXfId ) ); +} + +// private -------------------------------------------------------------------- + +void CellStyleBuffer::insertCellStyle( CellStyleRef xCellStyle ) +{ + const CellStyleModel& rModel = xCellStyle->getModel(); + if( rModel.mnXfId >= 0 ) + { + // insert into the built-in map or user defined map + (rModel.isBuiltin() ? maBuiltinStyles : maUserStyles).push_back( xCellStyle ); + + // insert into the XF identifier map + OSL_ENSURE( maStylesByXf.count( rModel.mnXfId ) == 0, "CellStyleBuffer::insertCellStyle - multiple styles with equal XF identifier" ); + maStylesByXf[ rModel.mnXfId ] = xCellStyle; + + // remember default cell style + if( rModel.isDefaultStyle() ) + mxDefStyle = xCellStyle; + } +} + +OUString CellStyleBuffer::createCellStyle( const CellStyleRef& rxCellStyle ) const +{ + if( rxCellStyle.get() ) + { + rxCellStyle->createCellStyle(); + const OUString& rStyleName = rxCellStyle->getFinalStyleName(); + if( rStyleName.getLength() > 0 ) + return rStyleName; + } + // on error: fallback to default style + return lclGetBuiltinStyleName( OOX_STYLE_NORMAL, OUString() ); +} + +// ============================================================================ + +StylesBuffer::StylesBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + maPalette( rHelper ), + maNumFmts( rHelper ), + maCellStyles( rHelper ) +{ +} + +FontRef StylesBuffer::createFont( sal_Int32* opnFontId ) +{ + if( opnFontId ) *opnFontId = static_cast< sal_Int32 >( maFonts.size() ); + FontRef xFont( new Font( *this, false ) ); + maFonts.push_back( xFont ); + return xFont; +} + +NumberFormatRef StylesBuffer::createNumFmt( sal_Int32 nNumFmtId, const OUString& rFmtCode ) +{ + return maNumFmts.createNumFmt( nNumFmtId, rFmtCode ); +} + +BorderRef StylesBuffer::createBorder( sal_Int32* opnBorderId ) +{ + if( opnBorderId ) *opnBorderId = static_cast< sal_Int32 >( maBorders.size() ); + BorderRef xBorder( new Border( *this, false ) ); + maBorders.push_back( xBorder ); + return xBorder; +} + +FillRef StylesBuffer::createFill( sal_Int32* opnFillId ) +{ + if( opnFillId ) *opnFillId = static_cast< sal_Int32 >( maFills.size() ); + FillRef xFill( new Fill( *this, false ) ); + maFills.push_back( xFill ); + return xFill; +} + +XfRef StylesBuffer::createCellXf( sal_Int32* opnXfId ) +{ + if( opnXfId ) *opnXfId = static_cast< sal_Int32 >( maCellXfs.size() ); + XfRef xXf( new Xf( *this ) ); + maCellXfs.push_back( xXf ); + return xXf; +} + +XfRef StylesBuffer::createStyleXf( sal_Int32* opnXfId ) +{ + if( opnXfId ) *opnXfId = static_cast< sal_Int32 >( maStyleXfs.size() ); + XfRef xXf( new Xf( *this ) ); + maStyleXfs.push_back( xXf ); + return xXf; +} + +DxfRef StylesBuffer::createDxf( sal_Int32* opnDxfId ) +{ + if( opnDxfId ) *opnDxfId = static_cast< sal_Int32 >( maDxfs.size() ); + DxfRef xDxf( new Dxf( *this ) ); + maDxfs.push_back( xDxf ); + return xDxf; +} + +void StylesBuffer::importPaletteColor( const AttributeList& rAttribs ) +{ + maPalette.importPaletteColor( rAttribs ); +} + +NumberFormatRef StylesBuffer::importNumFmt( const AttributeList& rAttribs ) +{ + return maNumFmts.importNumFmt( rAttribs ); +} + +CellStyleRef StylesBuffer::importCellStyle( const AttributeList& rAttribs ) +{ + return maCellStyles.importCellStyle( rAttribs ); +} + +void StylesBuffer::importPaletteColor( RecordInputStream& rStrm ) +{ + maPalette.importPaletteColor( rStrm ); +} + +void StylesBuffer::importNumFmt( RecordInputStream& rStrm ) +{ + maNumFmts.importNumFmt( rStrm ); +} + +void StylesBuffer::importCellStyle( RecordInputStream& rStrm ) +{ + maCellStyles.importCellStyle( rStrm ); +} + +void StylesBuffer::importPalette( BiffInputStream& rStrm ) +{ + maPalette.importPalette( rStrm ); +} + +void StylesBuffer::importFont( BiffInputStream& rStrm ) +{ + /* Font with index 4 is not stored in BIFF. This means effectively, first + font in the BIFF file has index 0, fourth font has index 3, and fifth + font has index 5. Insert a dummy font to correctly map passed font + identifiers. */ + if( maFonts.size() == 4 ) + maFonts.push_back( maFonts.front() ); + + FontRef xFont = createFont(); + xFont->importFont( rStrm ); + + /* #i71033# Set stream text encoding from application font, if CODEPAGE + record is missing. Must be done now (not while finalizeImport() runs), + to be able to read all following byte strings correctly (e.g. cell + style names). */ + if( maFonts.size() == 1 ) + setAppFontEncoding( xFont->getFontEncoding() ); +} + +void StylesBuffer::importFontColor( BiffInputStream& rStrm ) +{ + if( !maFonts.empty() ) + maFonts.back()->importFontColor( rStrm ); +} + +void StylesBuffer::importFormat( BiffInputStream& rStrm ) +{ + maNumFmts.importFormat( rStrm ); +} + +void StylesBuffer::importXf( BiffInputStream& rStrm ) +{ + XfRef xXf( new Xf( *this ) ); + xXf->importXf( rStrm ); + + XfRef xCellXf, xStyleXf; + (xXf->isCellXf() ? xCellXf : xStyleXf) = xXf; + maCellXfs.push_back( xCellXf ); + maStyleXfs.push_back( xStyleXf ); +} + +void StylesBuffer::importStyle( BiffInputStream& rStrm ) +{ + maCellStyles.importStyle( rStrm ); +} + +void StylesBuffer::finalizeImport() +{ + // fonts first, are needed to finalize unit converter and XFs below + maFonts.forEachMem( &Font::finalizeImport ); + // finalize unit coefficients after default font is known + getUnitConverter().finalizeImport(); + // number formats + maNumFmts.finalizeImport(); + // borders and fills + maBorders.forEachMem( &Border::finalizeImport ); + maFills.forEachMem( &Fill::finalizeImport ); + // style XFs and cell XFs + maStyleXfs.forEachMem( &Xf::finalizeImport ); + maCellXfs.forEachMem( &Xf::finalizeImport ); + // built-in and user defined cell styles + maCellStyles.finalizeImport(); + // differential formatting (for conditional formatting) + maDxfs.forEachMem( &Dxf::finalizeImport ); +} + +sal_Int32 StylesBuffer::getPaletteColor( sal_Int32 nPaletteIdx ) const +{ + return maPalette.getColor( nPaletteIdx ); +} + +FontRef StylesBuffer::getFont( sal_Int32 nFontId ) const +{ + return maFonts.get( nFontId ); +} + +BorderRef StylesBuffer::getBorder( sal_Int32 nBorderId ) const +{ + return maBorders.get( nBorderId ); +} + +XfRef StylesBuffer::getCellXf( sal_Int32 nXfId ) const +{ + return maCellXfs.get( nXfId ); +} + +XfRef StylesBuffer::getStyleXf( sal_Int32 nXfId ) const +{ + return maStyleXfs.get( nXfId ); +} + +DxfRef StylesBuffer::getDxf( sal_Int32 nDxfId ) const +{ + return maDxfs.get( nDxfId ); +} + +FontRef StylesBuffer::getFontFromCellXf( sal_Int32 nXfId ) const +{ + FontRef xFont; + if( const Xf* pXf = getCellXf( nXfId ).get() ) + xFont = pXf->getFont(); + return xFont; +} + +FontRef StylesBuffer::getDefaultFont() const +{ + FontRef xDefFont; + if( const Xf* pXf = getStyleXf( maCellStyles.getDefaultXfId() ).get() ) + xDefFont = pXf->getFont(); + // no font from styles - try first loaded font (e.g. BIFF2) + if( !xDefFont ) + xDefFont = maFonts.get( 0 ); + OSL_ENSURE( xDefFont.get(), "StylesBuffer::getDefaultFont - no default font found" ); + return xDefFont; +} + +const FontModel& StylesBuffer::getDefaultFontModel() const +{ + FontRef xDefFont = getDefaultFont(); + return xDefFont.get() ? xDefFont->getModel() : getTheme().getDefaultFontModel(); +} + +bool StylesBuffer::equalBorders( sal_Int32 nBorderId1, sal_Int32 nBorderId2 ) const +{ + if( nBorderId1 == nBorderId2 ) + return true; + + switch( getFilterType() ) + { + case FILTER_OOX: + // in OOXML, borders are assumed to be unique + return false; + + case FILTER_BIFF: + { + // in BIFF, a new border entry has been created for every XF + const Border* pBorder1 = maBorders.get( nBorderId1 ).get(); + const Border* pBorder2 = maBorders.get( nBorderId2 ).get(); + return pBorder1 && pBorder2 && (pBorder1->getApiData() == pBorder2->getApiData()); + } + + case FILTER_UNKNOWN: + break; + } + return false; +} + +bool StylesBuffer::equalFills( sal_Int32 nFillId1, sal_Int32 nFillId2 ) const +{ + if( nFillId1 == nFillId2 ) + return true; + + switch( getFilterType() ) + { + case FILTER_OOX: + // in OOXML, fills are assumed to be unique + return false; + + case FILTER_BIFF: + { + // in BIFF, a new fill entry has been created for every XF + const Fill* pFill1 = maFills.get( nFillId1 ).get(); + const Fill* pFill2 = maFills.get( nFillId2 ).get(); + return pFill1 && pFill2 && (pFill1->getApiData() == pFill2->getApiData()); + } + + case FILTER_UNKNOWN: + break; + } + return false; +} + +OUString StylesBuffer::getDefaultStyleName() const +{ + return maCellStyles.getDefaultStyleName(); +} + +OUString StylesBuffer::createCellStyle( sal_Int32 nXfId ) const +{ + return maCellStyles.createCellStyle( nXfId ); +} + +OUString StylesBuffer::createDxfStyle( sal_Int32 nDxfId ) const +{ + OUString& rStyleName = maDxfStyles[ nDxfId ]; + if( rStyleName.getLength() == 0 ) + { + if( Dxf* pDxf = maDxfs.get( nDxfId ).get() ) + { + rStyleName = OUStringBuffer( CREATE_OUSTRING( "ConditionalStyle_" ) ).append( nDxfId + 1 ).makeStringAndClear(); + // create the style sheet (this may change rStyleName if such a style already exists) + Reference< XStyle > xStyle = createStyleObject( rStyleName, false ); + // write style formatting properties + PropertySet aPropSet( xStyle ); + pDxf->writeToPropertySet( aPropSet ); + } + // on error: fallback to default style + if( rStyleName.getLength() == 0 ) + rStyleName = maCellStyles.getDefaultStyleName(); + } + return rStyleName; +} + +void StylesBuffer::writeFontToPropertyMap( PropertyMap& rPropMap, sal_Int32 nFontId ) const +{ + if( Font* pFont = maFonts.get( nFontId ).get() ) + pFont->writeToPropertyMap( rPropMap, FONT_PROPTYPE_CELL ); +} + +void StylesBuffer::writeNumFmtToPropertyMap( PropertyMap& rPropMap, sal_Int32 nNumFmtId ) const +{ + maNumFmts.writeToPropertyMap( rPropMap, nNumFmtId ); +} + +void StylesBuffer::writeBorderToPropertyMap( PropertyMap& rPropMap, sal_Int32 nBorderId ) const +{ + if( Border* pBorder = maBorders.get( nBorderId ).get() ) + pBorder->writeToPropertyMap( rPropMap ); +} + +void StylesBuffer::writeFillToPropertyMap( PropertyMap& rPropMap, sal_Int32 nFillId ) const +{ + if( Fill* pFill = maFills.get( nFillId ).get() ) + pFill->writeToPropertyMap( rPropMap ); +} + +void StylesBuffer::writeCellXfToPropertyMap( PropertyMap& rPropMap, sal_Int32 nXfId ) const +{ + if( Xf* pXf = maCellXfs.get( nXfId ).get() ) + pXf->writeToPropertyMap( rPropMap ); +} + +void StylesBuffer::writeStyleXfToPropertyMap( PropertyMap& rPropMap, sal_Int32 nXfId ) const +{ + if( Xf* pXf = maStyleXfs.get( nXfId ).get() ) + pXf->writeToPropertyMap( rPropMap ); +} + +void StylesBuffer::writeCellXfToPropertySet( PropertySet& rPropSet, sal_Int32 nXfId ) const +{ + if( Xf* pXf = maCellXfs.get( nXfId ).get() ) + pXf->writeToPropertySet( rPropSet ); +} + +void StylesBuffer::writeStyleXfToPropertySet( PropertySet& rPropSet, sal_Int32 nXfId ) const +{ + if( Xf* pXf = maStyleXfs.get( nXfId ).get() ) + pXf->writeToPropertySet( rPropSet ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/stylesfragment.cxx b/oox/source/xls/stylesfragment.cxx new file mode 100644 index 000000000000..217fce443209 --- /dev/null +++ b/oox/source/xls/stylesfragment.cxx @@ -0,0 +1,331 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/stylesfragment.hxx" +#include "oox/helper/attributelist.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxIndexedColorsContext::OoxIndexedColorsContext( OoxWorkbookFragmentBase& rFragment ) : + OoxWorkbookContextBase( rFragment ) +{ +} + +ContextHandlerRef OoxIndexedColorsContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( indexedColors ): + if( nElement == XLS_TOKEN( rgbColor ) ) getStyles().importPaletteColor( rAttribs ); + break; + } + return 0; +} + +ContextHandlerRef OoxIndexedColorsContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case OOBIN_ID_INDEXEDCOLORS: + if( nRecId == OOBIN_ID_RGBCOLOR ) getStyles().importPaletteColor( rStrm ); + break; + } + return 0; +} + +// ============================================================================ + +ContextHandlerRef OoxFontContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( mxFont.get() ) + mxFont->importAttribs( nElement, rAttribs ); + return 0; +} + +// ============================================================================ + +void OoxBorderContext::onStartElement( const AttributeList& rAttribs ) +{ + if( mxBorder.get() && (getCurrentElement() == XLS_TOKEN( border )) ) + mxBorder->importBorder( rAttribs ); +} + +ContextHandlerRef OoxBorderContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( mxBorder.get() ) switch( getCurrentElement() ) + { + case XLS_TOKEN( border ): + mxBorder->importStyle( nElement, rAttribs ); + return this; + + default: + if( nElement == XLS_TOKEN( color ) ) + mxBorder->importColor( getCurrentElement(), rAttribs ); + } + return 0; +} + +// ============================================================================ + +ContextHandlerRef OoxFillContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( mxFill.get() ) switch( getCurrentElement() ) + { + case XLS_TOKEN( fill ): + switch( nElement ) + { + case XLS_TOKEN( patternFill ): mxFill->importPatternFill( rAttribs ); return this; + case XLS_TOKEN( gradientFill ): mxFill->importGradientFill( rAttribs ); return this; + } + break; + case XLS_TOKEN( patternFill ): + switch( nElement ) + { + case XLS_TOKEN( fgColor ): mxFill->importFgColor( rAttribs ); break; + case XLS_TOKEN( bgColor ): mxFill->importBgColor( rAttribs ); break; + } + break; + case XLS_TOKEN( gradientFill ): + if( nElement == XLS_TOKEN( stop ) ) + { + mfGradPos = rAttribs.getDouble( XML_position, -1.0 ); + return this; + } + break; + case XLS_TOKEN( stop ): + if( nElement == XLS_TOKEN( color ) ) + mxFill->importColor( rAttribs, mfGradPos ); + break; + } + return 0; +} + +// ============================================================================ + +void OoxXfContext::onStartElement( const AttributeList& rAttribs ) +{ + if( mxXf.get() && (getCurrentElement() == XLS_TOKEN( xf )) ) + mxXf->importXf( rAttribs, mbCellXf ); +} + +ContextHandlerRef OoxXfContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( mxXf.get() ) switch( getCurrentElement() ) + { + case XLS_TOKEN( xf ): + switch( nElement ) + { + case XLS_TOKEN( alignment ): mxXf->importAlignment( rAttribs ); break; + case XLS_TOKEN( protection ): mxXf->importProtection( rAttribs ); break; + } + break; + } + return 0; +} + +// ============================================================================ + +ContextHandlerRef OoxDxfContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( mxDxf.get() ) switch( getCurrentElement() ) + { + case XLS_TOKEN( dxf ): + switch( nElement ) + { + case XLS_TOKEN( font ): return new OoxFontContext( *this, mxDxf->createFont() ); + case XLS_TOKEN( border ): return new OoxBorderContext( *this, mxDxf->createBorder() ); + case XLS_TOKEN( fill ): return new OoxFillContext( *this, mxDxf->createFill() ); + + case XLS_TOKEN( numFmt ): mxDxf->importNumFmt( rAttribs ); break; +#if 0 + case XLS_TOKEN( alignment ): mxDxf->importAlignment( rAttribs ); break; + case XLS_TOKEN( protection ): mxDxf->importProtection( rAttribs ); break; +#endif + } + break; + } + return 0; +} + +// ============================================================================ + +OoxStylesFragment::OoxStylesFragment( const WorkbookHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxStylesFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( styleSheet ) ) return this; + break; + + case XLS_TOKEN( styleSheet ): + switch( nElement ) + { + case XLS_TOKEN( colors ): + case XLS_TOKEN( numFmts ): + case XLS_TOKEN( fonts ): + case XLS_TOKEN( borders ): + case XLS_TOKEN( fills ): + case XLS_TOKEN( cellXfs ): + case XLS_TOKEN( cellStyleXfs ): + case XLS_TOKEN( dxfs ): + case XLS_TOKEN( cellStyles ): return this; + } + break; + + case XLS_TOKEN( colors ): + if( nElement == XLS_TOKEN( indexedColors ) ) return new OoxIndexedColorsContext( *this ); + break; + case XLS_TOKEN( numFmts ): + if( nElement == XLS_TOKEN( numFmt ) ) getStyles().importNumFmt( rAttribs ); + break; + case XLS_TOKEN( fonts ): + if( nElement == XLS_TOKEN( font ) ) return new OoxFontContext( *this, getStyles().createFont() ); + break; + case XLS_TOKEN( borders ): + if( nElement == XLS_TOKEN( border ) ) return new OoxBorderContext( *this, getStyles().createBorder() ); + break; + case XLS_TOKEN( fills ): + if( nElement == XLS_TOKEN( fill ) ) return new OoxFillContext( *this, getStyles().createFill() ); + break; + case XLS_TOKEN( cellXfs ): + if( nElement == XLS_TOKEN( xf ) ) return new OoxXfContext( *this, getStyles().createCellXf(), true ); + break; + case XLS_TOKEN( cellStyleXfs ): + if( nElement == XLS_TOKEN( xf ) ) return new OoxXfContext( *this, getStyles().createStyleXf(), false ); + break; + case XLS_TOKEN( dxfs ): + if( nElement == XLS_TOKEN( dxf ) ) return new OoxDxfContext( *this, getStyles().createDxf() ); + break; + case XLS_TOKEN( cellStyles ): + if( nElement == XLS_TOKEN( cellStyle ) ) getStyles().importCellStyle( rAttribs ); + break; + } + return 0; +} + +ContextHandlerRef OoxStylesFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_STYLESHEET ) return this; + break; + + case OOBIN_ID_STYLESHEET: + switch( nRecId ) + { + case OOBIN_ID_COLORS: + case OOBIN_ID_NUMFMTS: + case OOBIN_ID_FONTS: + case OOBIN_ID_BORDERS: + case OOBIN_ID_FILLS: + case OOBIN_ID_CELLXFS: + case OOBIN_ID_CELLSTYLEXFS: + case OOBIN_ID_DXFS: + case OOBIN_ID_CELLSTYLES: return this; + } + break; + + case OOBIN_ID_COLORS: + if( nRecId == OOBIN_ID_INDEXEDCOLORS ) return new OoxIndexedColorsContext( *this ); + break; + case OOBIN_ID_NUMFMTS: + if( nRecId == OOBIN_ID_NUMFMT ) getStyles().importNumFmt( rStrm ); + break; + case OOBIN_ID_FONTS: + if( nRecId == OOBIN_ID_FONT ) getStyles().createFont()->importFont( rStrm ); + break; + case OOBIN_ID_BORDERS: + if( nRecId == OOBIN_ID_BORDER ) getStyles().createBorder()->importBorder( rStrm ); + break; + case OOBIN_ID_FILLS: + if( nRecId == OOBIN_ID_FILL ) getStyles().createFill()->importFill( rStrm ); + break; + case OOBIN_ID_CELLXFS: + if( nRecId == OOBIN_ID_XF ) getStyles().createCellXf()->importXf( rStrm, true ); + break; + case OOBIN_ID_CELLSTYLEXFS: + if( nRecId == OOBIN_ID_XF ) getStyles().createStyleXf()->importXf( rStrm, false ); + break; + case OOBIN_ID_DXFS: + if( nRecId == OOBIN_ID_DXF ) getStyles().createDxf()->importDxf( rStrm ); + break; + case OOBIN_ID_CELLSTYLES: + if( nRecId == OOBIN_ID_CELLSTYLE ) getStyles().importCellStyle( rStrm ); + break; + } + return 0; +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxStylesFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_BORDERS, OOBIN_ID_BORDERS + 1 }, + { OOBIN_ID_CELLSTYLES, OOBIN_ID_CELLSTYLES + 1 }, + { OOBIN_ID_CELLSTYLEXFS, OOBIN_ID_CELLSTYLEXFS + 1 }, + { OOBIN_ID_CELLXFS, OOBIN_ID_CELLXFS + 1 }, + { OOBIN_ID_COLORS, OOBIN_ID_COLORS + 1 }, + { OOBIN_ID_DXFS, OOBIN_ID_DXFS + 1 }, + { OOBIN_ID_FILLS, OOBIN_ID_FILLS + 1 }, + { OOBIN_ID_FONTS, OOBIN_ID_FONTS + 1 }, + { OOBIN_ID_INDEXEDCOLORS, OOBIN_ID_INDEXEDCOLORS + 1 }, + { OOBIN_ID_MRUCOLORS, OOBIN_ID_MRUCOLORS + 1 }, + { OOBIN_ID_NUMFMTS, OOBIN_ID_NUMFMTS + 1 }, + { OOBIN_ID_STYLESHEET, OOBIN_ID_STYLESHEET + 1 }, + { OOBIN_ID_TABLESTYLES, OOBIN_ID_TABLESTYLES + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +void OoxStylesFragment::finalizeImport() +{ + getStyles().finalizeImport(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/tablebuffer.cxx b/oox/source/xls/tablebuffer.cxx new file mode 100644 index 000000000000..452df7a1171a --- /dev/null +++ b/oox/source/xls/tablebuffer.cxx @@ -0,0 +1,173 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/tablebuffer.hxx" +#include <com/sun/star/sheet/XDatabaseRanges.hpp> +#include <com/sun/star/sheet/XDatabaseRange.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/containerhelper.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/addressconverter.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::sheet::XDatabaseRanges; +using ::com::sun::star::sheet::XDatabaseRange; + +namespace oox { +namespace xls { + +// ============================================================================ + +TableModel::TableModel() : + mnId( -1 ), + mnType( XML_worksheet ), + mnHeaderRows( 1 ), + mnTotalsRows( 0 ) +{ +} + +// ============================================================================ + +Table::Table( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mnTokenIndex( -1 ) +{ +} + +void Table::importTable( const AttributeList& rAttribs, sal_Int16 nSheet ) +{ + getAddressConverter().convertToCellRangeUnchecked( maModel.maRange, rAttribs.getString( XML_ref, OUString() ), nSheet ); + maModel.maProgName = rAttribs.getXString( XML_name, OUString() ); + maModel.maDisplayName = rAttribs.getXString( XML_displayName, OUString() ); + maModel.mnId = rAttribs.getInteger( XML_id, -1 ); + maModel.mnType = rAttribs.getToken( XML_tableType, XML_worksheet ); + maModel.mnHeaderRows = rAttribs.getInteger( XML_headerRowCount, 1 ); + maModel.mnTotalsRows = rAttribs.getInteger( XML_totalsRowCount, 0 ); +} + +void Table::importTable( RecordInputStream& rStrm, sal_Int16 nSheet ) +{ + BinRange aBinRange; + sal_Int32 nType; + rStrm >> aBinRange >> nType >> maModel.mnId >> maModel.mnHeaderRows >> maModel.mnTotalsRows; + rStrm.skip( 32 ); + rStrm >> maModel.maProgName >> maModel.maDisplayName; + + getAddressConverter().convertToCellRangeUnchecked( maModel.maRange, aBinRange, nSheet ); + static const sal_Int32 spnTypes[] = { XML_worksheet, XML_TOKEN_INVALID, XML_TOKEN_INVALID, XML_queryTable }; + maModel.mnType = STATIC_ARRAY_SELECT( spnTypes, nType, XML_TOKEN_INVALID ); +} + +void Table::finalizeImport() +{ + // validate cell range + maDestRange = maModel.maRange; + bool bValidRange = getAddressConverter().validateCellRange( maDestRange, true, true ); + + // create database range + if( bValidRange && (maModel.mnId > 0) && (maModel.maDisplayName.getLength() > 0) ) try + { + // find an unused name + Reference< XDatabaseRanges > xDatabaseRanges = getDatabaseRanges(); + Reference< XNameAccess > xNameAccess( xDatabaseRanges, UNO_QUERY_THROW ); + OUString aName = ContainerHelper::getUnusedName( xNameAccess, maModel.maDisplayName, '_' ); + xDatabaseRanges->addNewByName( aName, maModel.maRange ); + Reference< XDatabaseRange > xDatabaseRange( xDatabaseRanges->getByName( aName ), UNO_QUERY_THROW ); + PropertySet aPropSet( xDatabaseRange ); + if( !aPropSet.getProperty( mnTokenIndex, PROP_TokenIndex ) ) + mnTokenIndex = -1; + } + catch( Exception& ) + { + OSL_ENSURE( false, "Table::finalizeImport - cannot create database range" ); + } +} + +// ============================================================================ + +TableBuffer::TableBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +TableRef TableBuffer::importTable( const AttributeList& rAttribs, sal_Int16 nSheet ) +{ + TableRef xTable( new Table( *this ) ); + xTable->importTable( rAttribs, nSheet ); + insertTable( xTable ); + return xTable; +} + +TableRef TableBuffer::importTable( RecordInputStream& rStrm, sal_Int16 nSheet ) +{ + TableRef xTable( new Table( *this ) ); + xTable->importTable( rStrm, nSheet ); + insertTable( xTable ); + return xTable; +} + +void TableBuffer::finalizeImport() +{ + maIdTables.forEachMem( &Table::finalizeImport ); +} + +TableRef TableBuffer::getTable( sal_Int32 nTableId ) const +{ + return maIdTables.get( nTableId ); +} + +TableRef TableBuffer::getTable( const OUString& rDispName ) const +{ + return maNameTables.get( rDispName ); +} + +// private -------------------------------------------------------------------- + +void TableBuffer::insertTable( TableRef xTable ) +{ + sal_Int32 nTableId = xTable->getTableId(); + const OUString& rDispName = xTable->getDisplayName(); + if( (nTableId > 0) && (rDispName.getLength() > 0) ) + { + OSL_ENSURE( maIdTables.find( nTableId ) == maIdTables.end(), "TableBuffer::insertTable - multiple table identifier" ); + maIdTables[ nTableId ] = xTable; + OSL_ENSURE( maNameTables.find( rDispName ) == maNameTables.end(), "TableBuffer::insertTable - multiple table name" ); + maNameTables[ rDispName ] = xTable; + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/tablefragment.cxx b/oox/source/xls/tablefragment.cxx new file mode 100644 index 000000000000..800a45cd9530 --- /dev/null +++ b/oox/source/xls/tablefragment.cxx @@ -0,0 +1,86 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/tablefragment.hxx" + +using ::rtl::OUString; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; + +namespace oox { +namespace xls { + +// ============================================================================ + +OoxTableFragment::OoxTableFragment( const WorksheetHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxTableFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( table ) ) + mxTable = getTables().importTable( rAttribs, getSheetIndex() ); + break; + } + return 0; +} + +ContextHandlerRef OoxTableFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_TABLE ) + mxTable = getTables().importTable( rStrm, getSheetIndex() ); + break; + } + return 0; +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxTableFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_TABLE, OOBIN_ID_TABLE + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/themebuffer.cxx b/oox/source/xls/themebuffer.cxx new file mode 100644 index 000000000000..23617287dc57 --- /dev/null +++ b/oox/source/xls/themebuffer.cxx @@ -0,0 +1,122 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/themebuffer.hxx" +#include "oox/xls/stylesbuffer.hxx" +#include "tokens.hxx" + +using ::oox::drawingml::ClrScheme; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +/** Specifies default theme fonts for a specific locale. */ +struct BuiltinThemeFont +{ + const sal_Char* mpcLocale; /// The locale for this font setting. + const sal_Char* mpcHeadFont; /// Default heading font. + const sal_Char* mpcBodyFont; /// Default body font. +}; + +#define FONT_JA "\357\274\255\357\274\263 \357\274\260\343\202\264\343\202\267\343\203\203\343\202\257" +#define FONT_KO "\353\247\221\354\235\200 \352\263\240\353\224\225" +#define FONT_CS "\345\256\213\344\275\223" +#define FONT_CT "\346\226\260\347\264\260\346\230\216\351\253\224" + +static const BuiltinThemeFont spBuiltinThemeFonts[] = +{ // locale headings font body font + { "*", "Cambria", "Calibri" }, // Default + { "ar", "Times New Roman", "Arial" }, // Arabic + { "bn", "Vrinda", "Vrinda" }, // Bengali + { "div", "MV Boli", "MV Boli" }, // Divehi + { "fa", "Times New Roman", "Arial" }, // Farsi + { "gu", "Shruti", "Shruti" }, // Gujarati + { "he", "Times New Roman", "Arial" }, // Hebrew + { "hi", "Mangal", "Mangal" }, // Hindi + { "ja", FONT_JA, FONT_JA }, // Japanese + { "kn", "Tunga", "Tunga" }, // Kannada + { "ko", FONT_KO, FONT_KO }, // Korean + { "kok", "Mangal", "Mangal" }, // Konkani + { "ml", "Kartika", "Kartika" }, // Malayalam + { "mr", "Mangal", "Mangal" }, // Marathi + { "pa", "Raavi", "Raavi" }, // Punjabi + { "sa", "Mangal", "Mangal" }, // Sanskrit + { "syr", "Estrangelo Edessa", "Estrangelo Edessa" }, // Syriac + { "ta", "Latha", "Latha" }, // Tamil + { "te", "Gautami", "Gautami" }, // Telugu + { "th", "Tahoma", "Tahoma" }, // Thai + { "ur", "Times New Roman", "Arial" }, // Urdu + { "vi", "Times New Roman", "Arial" }, // Vietnamese + { "zh", FONT_CS, FONT_CS }, // Chinese, Simplified + { "zh-HK", FONT_CT, FONT_CT }, // Chinese, Hong Kong + { "zh-MO", FONT_CT, FONT_CT }, // Chinese, Macau + { "zh-TW", FONT_CT, FONT_CT } // Chinese, Taiwan +}; + +} // namespace + +// ---------------------------------------------------------------------------- + +ThemeBuffer::ThemeBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mxDefFontModel( new FontModel ) +{ + switch( getFilterType() ) + { + case FILTER_OOX: + //! TODO: locale dependent font name + mxDefFontModel->maName = CREATE_OUSTRING( "Cambria" ); + mxDefFontModel->mfHeight = 11.0; + break; + case FILTER_BIFF: + //! TODO: BIFF dependent font name + mxDefFontModel->maName = CREATE_OUSTRING( "Arial" ); + mxDefFontModel->mfHeight = 10.0; + break; + case FILTER_UNKNOWN: break; + } +} + +ThemeBuffer::~ThemeBuffer() +{ +} + +sal_Int32 ThemeBuffer::getColorByToken( sal_Int32 nToken ) const +{ + sal_Int32 nColor = 0; + return getClrScheme().getColor( nToken, nColor ) ? nColor : API_RGB_TRANSPARENT; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/unitconverter.cxx b/oox/source/xls/unitconverter.cxx new file mode 100644 index 000000000000..170a1f66f2e2 --- /dev/null +++ b/oox/source/xls/unitconverter.cxx @@ -0,0 +1,258 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/unitconverter.hxx" +#include <rtl/math.hxx> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/awt/DeviceInfo.hpp> +#include <com/sun/star/awt/XFont.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include "oox/core/filterbase.hxx" +#include "oox/xls/stylesbuffer.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::awt::FontDescriptor; +using ::com::sun::star::awt::XDevice; +using ::com::sun::star::awt::DeviceInfo; +using ::com::sun::star::awt::XFont; +using ::com::sun::star::util::Date; +using ::com::sun::star::util::DateTime; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const double MM100_PER_INCH = 2540.0; +const double MM100_PER_POINT = MM100_PER_INCH / 72.0; +const double MM100_PER_TWIP = MM100_PER_POINT / 20.0; +const double MM100_PER_EMU = 1.0 / 360.0; + +// ---------------------------------------------------------------------------- + +/** Returns true, if the passed year is a leap year. */ +inline sal_Int32 lclIsLeapYear( sal_Int32 nYear ) +{ + return ((nYear % 4) == 0) && (((nYear % 100) != 0) || ((nYear % 400) == 0)); +} + +void lclSkipYearBlock( sal_Int32& ornDays, sal_uInt16& ornYear, sal_Int32 nDaysInBlock, sal_Int32 nYearsPerBlock, sal_Int32 nMaxBlocks ) +{ + sal_Int32 nBlocks = ::std::min< sal_Int32 >( ornDays / nDaysInBlock, nMaxBlocks ); + ornYear = static_cast< sal_uInt16 >( ornYear + nYearsPerBlock * nBlocks ); + ornDays -= nBlocks * nDaysInBlock; +} + +/** Returns the number of days before the passed date, starting from the null + date 0000-Jan-01, using standard leap year conventions. */ +sal_Int32 lclGetDays( const Date& rDate ) +{ + // number of days in all full years before passed date including all leap days + sal_Int32 nDays = rDate.Year * 365 + ((rDate.Year + 3) / 4) - ((rDate.Year + 99) / 100) + ((rDate.Year + 399) / 400); + OSL_ENSURE( (1 <= rDate.Month) && (rDate.Month <= 12), "lclGetDays - invalid month" ); + OSL_ENSURE( (1 <= rDate.Day) && (rDate.Day <= 31), "lclGetDays - invalid day" ); // yes, this is weak... + if( (1 <= rDate.Month) && (rDate.Month <= 12) ) + { + // number of days at start of month jan feb mar apr may jun jul aug sep oct nov dec + static const sal_Int32 spnCumDays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + // add number of days in full months before passed date + nDays += spnCumDays[ rDate.Month - 1 ]; + // add number of days from passed date (this adds one day too much) + nDays += rDate.Day; + /* Remove the one day added too much if there is no leap day before + the passed day in the passed year. This means: remove the day, if + we are in january or february (leap day not reached if existing), + or if the passed year is not a leap year. */ + if( (rDate.Month < 3) || !lclIsLeapYear( rDate.Year ) ) + --nDays; + } + return nDays; +} + +} // namespace + +// ---------------------------------------------------------------------------- + +UnitConverter::UnitConverter( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + maCoeffs( UNIT_ENUM_SIZE, 1.0 ), + mnNullDate( lclGetDays( Date( 30, 12, 1899 ) ) ) +{ + // initialize constant and default coefficients + const DeviceInfo& rDeviceInfo = getBaseFilter().getGraphicHelper().getDeviceInfo(); + maCoeffs[ UNIT_INCH ] = MM100_PER_INCH; + maCoeffs[ UNIT_POINT ] = MM100_PER_POINT; + maCoeffs[ UNIT_TWIP ] = MM100_PER_TWIP; + maCoeffs[ UNIT_EMU ] = MM100_PER_EMU; + maCoeffs[ UNIT_SCREENX ] = (rDeviceInfo.PixelPerMeterX > 0) ? (100000.0 / rDeviceInfo.PixelPerMeterX) : 50.0; + maCoeffs[ UNIT_SCREENY ] = (rDeviceInfo.PixelPerMeterY > 0) ? (100000.0 / rDeviceInfo.PixelPerMeterY) : 50.0; + maCoeffs[ UNIT_REFDEVX ] = 12.5; // default: 1 px = 0.125 mm + maCoeffs[ UNIT_REFDEVY ] = 12.5; // default: 1 px = 0.125 mm + maCoeffs[ UNIT_DIGIT ] = 200.0; // default: 1 digit = 2 mm + maCoeffs[ UNIT_SPACE ] = 100.0; // default 1 space = 1 mm + + // error code maps + addErrorCode( BIFF_ERR_NULL, CREATE_OUSTRING( "#NULL!" ) ); + addErrorCode( BIFF_ERR_DIV0, CREATE_OUSTRING( "#DIV/0!" ) ); + addErrorCode( BIFF_ERR_VALUE, CREATE_OUSTRING( "#VALUE!" ) ); + addErrorCode( BIFF_ERR_REF, CREATE_OUSTRING( "#REF!" ) ); + addErrorCode( BIFF_ERR_NAME, CREATE_OUSTRING( "#NAME?" ) ); + addErrorCode( BIFF_ERR_NUM, CREATE_OUSTRING( "#NUM!" ) ); + addErrorCode( BIFF_ERR_NA, CREATE_OUSTRING( "#NA" ) ); +} + +void UnitConverter::finalizeImport() +{ + Reference< XDevice > xDevice = getReferenceDevice(); + if( xDevice.is() ) + { + // get reference device metric first, needed to get character widths below + DeviceInfo aInfo = xDevice->getInfo(); + maCoeffs[ UNIT_REFDEVX ] = 100000.0 / aInfo.PixelPerMeterX; + maCoeffs[ UNIT_REFDEVY ] = 100000.0 / aInfo.PixelPerMeterY; + + // get character widths from default font + if( const Font* pDefFont = getStyles().getDefaultFont().get() ) + { + // XDevice expects pixels in font descriptor, but font contains twips + FontDescriptor aDesc = pDefFont->getFontDescriptor(); + aDesc.Height = static_cast< sal_Int16 >( scaleValue( aDesc.Height, UNIT_TWIP, UNIT_REFDEVX ) + 0.5 ); + Reference< XFont > xFont = xDevice->getFont( aDesc ); + if( xFont.is() ) + { + // get maximum width of all digits + sal_Int32 nDigitWidth = 0; + for( sal_Unicode cChar = '0'; cChar <= '9'; ++cChar ) + nDigitWidth = ::std::max( nDigitWidth, scaleToMm100( xFont->getCharWidth( cChar ), UNIT_REFDEVX ) ); + if( nDigitWidth > 0 ) + maCoeffs[ UNIT_DIGIT ] = nDigitWidth; + // get width of space character + sal_Int32 nSpaceWidth = scaleToMm100( xFont->getCharWidth( ' ' ), UNIT_REFDEVX ); + if( nSpaceWidth > 0 ) + maCoeffs[ UNIT_SPACE ] = nSpaceWidth; + } + } + } +} + +void UnitConverter::finalizeNullDate( const Date& rNullDate ) +{ + // convert the nulldate to number of days since 0000-Jan-01 + mnNullDate = lclGetDays( rNullDate ); +} + +// conversion ----------------------------------------------------------------- + +double UnitConverter::scaleValue( double fValue, Unit eFromUnit, Unit eToUnit ) const +{ + return (eFromUnit == eToUnit) ? fValue : (fValue * getCoefficient( eFromUnit ) / getCoefficient( eToUnit )); +} + +sal_Int32 UnitConverter::scaleToMm100( double fValue, Unit eUnit ) const +{ + return static_cast< sal_Int32 >( fValue * getCoefficient( eUnit ) + 0.5 ); +} + +double UnitConverter::scaleFromMm100( sal_Int32 nMm100, Unit eUnit ) const +{ + return static_cast< double >( nMm100 ) / getCoefficient( eUnit ); +} + +double UnitConverter::calcSerialFromDateTime( const DateTime& rDateTime ) const +{ + sal_Int32 nDays = lclGetDays( Date( rDateTime.Day, rDateTime.Month, rDateTime.Year ) ) - mnNullDate; + OSL_ENSURE( nDays >= 0, "UnitConverter::calcDateTimeSerial - invalid date" ); + OSL_ENSURE( (rDateTime.Hours <= 23) && (rDateTime.Minutes <= 59) && (rDateTime.Seconds <= 59), "UnitConverter::calcDateTimeSerial - invalid time" ); + return nDays + rDateTime.Hours / 24.0 + rDateTime.Minutes / 1440.0 + rDateTime.Seconds / 86400.0; +} + +DateTime UnitConverter::calcDateTimeFromSerial( double fSerial ) const +{ + DateTime aDateTime( 0, 0, 0, 0, 1, 1, 0 ); + double fDays = 0.0; + double fTime = modf( fSerial, &fDays ); + + // calculate date from number of days with O(1) complexity + sal_Int32 nDays = getLimitedValue< sal_Int32, double >( fDays + mnNullDate, 0, 3652424 ); + // skip year 0, assumed to be a leap year. By starting at year 1, leap years can be handled easily + if( nDays >= 366 ) { ++aDateTime.Year; nDays -= 366; } + // skip full blocks of 400, 100, 4 years, and remaining full years + lclSkipYearBlock( nDays, aDateTime.Year, 400 * 365 + 97, 400, 24 ); + lclSkipYearBlock( nDays, aDateTime.Year, 100 * 365 + 24, 100, 3 ); + lclSkipYearBlock( nDays, aDateTime.Year, 4 * 365 + 1, 4, 24 ); + lclSkipYearBlock( nDays, aDateTime.Year, 365, 1, 3 ); + // skip full months of current year + static const sal_Int32 spnDaysInMonth[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + if( (nDays >= 59) && !lclIsLeapYear( aDateTime.Year ) ) ++nDays; + const sal_Int32* pnDaysInMonth = spnDaysInMonth; + while( *pnDaysInMonth >= nDays ) { ++aDateTime.Month; nDays -= *pnDaysInMonth; ++pnDaysInMonth; } + aDateTime.Day = static_cast< sal_uInt16 >( nDays + 1 ); + + // calculate time from fractional part of serial + sal_Int32 nTime = getLimitedValue< sal_Int32, double >( fTime * 86400, 0, 86399 ); + aDateTime.Seconds = static_cast< sal_uInt16 >( nTime % 60 ); + nTime /= 60; + aDateTime.Minutes = static_cast< sal_uInt16 >( nTime % 60 ); + aDateTime.Hours = static_cast< sal_uInt16 >( nTime / 60 ); + + return aDateTime; +} + +OUString UnitConverter::calcOoxErrorCode( sal_uInt8 nErrorCode ) const +{ + BiffErrorCodeMap::const_iterator aIt = maBiffErrCodes.find( nErrorCode ); + return (aIt == maBiffErrCodes.end()) ? CREATE_OUSTRING( "#N/A" ) : aIt->second; +} + +sal_uInt8 UnitConverter::calcBiffErrorCode( const OUString& rErrorCode ) const +{ + OoxErrorCodeMap::const_iterator aIt = maOoxErrCodes.find( rErrorCode ); + return (aIt == maOoxErrCodes.end()) ? BIFF_ERR_NA : aIt->second; +} + +void UnitConverter::addErrorCode( sal_uInt8 nErrorCode, const OUString& rErrorCode ) +{ + maOoxErrCodes[ rErrorCode ] = nErrorCode; + maBiffErrCodes[ nErrorCode ] = rErrorCode; +} + +double UnitConverter::getCoefficient( Unit eUnit ) const +{ + OSL_ENSURE( static_cast< size_t >( eUnit ) < UNIT_ENUM_SIZE, "UnitConverter::getCoefficient - invalid unit" ); + return maCoeffs[ static_cast< size_t >( eUnit ) ]; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/viewsettings.cxx b/oox/source/xls/viewsettings.cxx new file mode 100644 index 000000000000..05dfa7bf9f96 --- /dev/null +++ b/oox/source/xls/viewsettings.cxx @@ -0,0 +1,832 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/viewsettings.hxx" +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/document/XViewDataSupplier.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <comphelper/mediadescriptor.hxx> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/containerhelper.hxx" +#include "oox/helper/propertymap.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/unitconverter.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/xls/worksheetbuffer.hxx" + +using ::rtl::OUString; +using ::com::sun::star::awt::Point; +using ::com::sun::star::awt::Size; +using ::com::sun::star::container::XNameContainer; +using ::com::sun::star::container::XIndexContainer; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::document::XViewDataSupplier; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::uno::Any; +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_THROW; +using ::oox::core::FilterBase; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_Int32 OOX_BOOKVIEW_TABBARRATIO_DEF = 600; /// Default tabbar ratio. +const sal_Int32 OOX_SHEETVIEW_NORMALZOOM_DEF = 100; /// Default zoom for normal view. +const sal_Int32 OOX_SHEETVIEW_SHEETLAYZOOM_DEF = 60; /// Default zoom for pagebreak preview. +const sal_Int32 OOX_SHEETVIEW_PAGELAYZOOM_DEF = 100; /// Default zoom for page layout view. + +const sal_uInt8 OOBIN_PANE_FROZEN = 0x01; +const sal_uInt8 OOBIN_PANE_FROZENNOSPLIT = 0x02; + +const sal_uInt16 OOBIN_SHEETVIEW_WINPROTECTED = 0x0001; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWFORMULAS = 0x0002; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWGRID = 0x0004; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWHEADINGS = 0x0008; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWZEROS = 0x0010; +const sal_uInt16 OOBIN_SHEETVIEW_RIGHTTOLEFT = 0x0020; +const sal_uInt16 OOBIN_SHEETVIEW_SELECTED = 0x0040; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWRULER = 0x0080; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWOUTLINE = 0x0100; +const sal_uInt16 OOBIN_SHEETVIEW_DEFGRIDCOLOR = 0x0200; +const sal_uInt16 OOBIN_SHEETVIEW_SHOWWHITESPACE = 0x0400; + +const sal_uInt16 OOBIN_CHARTSHEETVIEW_SELECTED = 0x0001; +const sal_uInt16 OOBIN_CHARTSHEETVIEW_ZOOMTOFIT = 0x0002; + +const sal_uInt8 OOBIN_WBVIEW_HIDDEN = 0x01; +const sal_uInt8 OOBIN_WBVIEW_MINIMIZED = 0x02; +const sal_uInt8 OOBIN_WBVIEW_SHOWHORSCROLL = 0x08; +const sal_uInt8 OOBIN_WBVIEW_SHOWVERSCROLL = 0x10; +const sal_uInt8 OOBIN_WBVIEW_SHOWTABBAR = 0x20; +const sal_uInt8 OOBIN_WBVIEW_AUTOFILTERGROUP = 0x40; + +const sal_uInt8 BIFF_PANE_BOTTOMRIGHT = 0; /// Bottom-right pane. +const sal_uInt8 BIFF_PANE_TOPRIGHT = 1; /// Right, or top-right pane. +const sal_uInt8 BIFF_PANE_BOTTOMLEFT = 2; /// Bottom, or bottom-left pane. +const sal_uInt8 BIFF_PANE_TOPLEFT = 3; /// Single, top, left, or top-left pane. + +const sal_uInt16 BIFF_WINDOW1_HIDDEN = 0x0001; +const sal_uInt16 BIFF_WINDOW1_MINIMIZED = 0x0002; +const sal_uInt16 BIFF_WINDOW1_SHOWHORSCROLL = 0x0008; +const sal_uInt16 BIFF_WINDOW1_SHOWVERSCROLL = 0x0010; +const sal_uInt16 BIFF_WINDOW1_SHOWTABBAR = 0x0020; + +const sal_uInt16 BIFF_WINDOW2_SHOWFORMULAS = 0x0001; +const sal_uInt16 BIFF_WINDOW2_SHOWGRID = 0x0002; +const sal_uInt16 BIFF_WINDOW2_SHOWHEADINGS = 0x0004; +const sal_uInt16 BIFF_WINDOW2_FROZEN = 0x0008; +const sal_uInt16 BIFF_WINDOW2_SHOWZEROS = 0x0010; +const sal_uInt16 BIFF_WINDOW2_DEFGRIDCOLOR = 0x0020; +const sal_uInt16 BIFF_WINDOW2_RIGHTTOLEFT = 0x0040; +const sal_uInt16 BIFF_WINDOW2_SHOWOUTLINE = 0x0080; +const sal_uInt16 BIFF_WINDOW2_FROZENNOSPLIT = 0x0100; +const sal_uInt16 BIFF_WINDOW2_SELECTED = 0x0200; +const sal_uInt16 BIFF_WINDOW2_DISPLAYED = 0x0400; +const sal_uInt16 BIFF_WINDOW2_PAGEBREAKMODE = 0x0800; + +// Attention: view settings in Calc do not use com.sun.star.view.DocumentZoomType! +const sal_Int16 API_ZOOMTYPE_PERCENT = 0; /// Zoom value in percent. + +const sal_Int32 API_ZOOMVALUE_MIN = 20; /// Minimum zoom in Calc. +const sal_Int32 API_ZOOMVALUE_MAX = 400; /// Maximum zoom in Calc. + +// no predefined constants for split mode +const sal_Int16 API_SPLITMODE_NONE = 0; /// No splits in window. +const sal_Int16 API_SPLITMODE_SPLIT = 1; /// Window is split. +const sal_Int16 API_SPLITMODE_FREEZE = 2; /// Window has frozen panes. + +// no predefined constants for pane idetifiers +const sal_Int16 API_SPLITPANE_TOPLEFT = 0; /// Top-left, or top pane. +const sal_Int16 API_SPLITPANE_TOPRIGHT = 1; /// Top-right pane. +const sal_Int16 API_SPLITPANE_BOTTOMLEFT = 2; /// Bottom-left, bottom, left, or single pane. +const sal_Int16 API_SPLITPANE_BOTTOMRIGHT = 3; /// Bottom-right, or right pane. + +// ---------------------------------------------------------------------------- + +/** Returns the OOXML pane identifier from the passed OOBIN or BIFF pane id. */ +sal_Int32 lclGetOoxPaneId( sal_Int32 nBinPaneId, sal_Int32 nDefaultPaneId ) +{ + static const sal_Int32 spnPaneIds[] = { XML_bottomRight, XML_topRight, XML_bottomLeft, XML_topLeft }; + return STATIC_ARRAY_SELECT( spnPaneIds, nBinPaneId, nDefaultPaneId ); +} + +} // namespace + +// ============================================================================ + +PaneSelectionModel::PaneSelectionModel() : + mnActiveCellId( 0 ) +{ +} + +// ---------------------------------------------------------------------------- + +SheetViewModel::SheetViewModel() : + mnWorkbookViewId( 0 ), + mnViewType( XML_normal ), + mnActivePaneId( XML_topLeft ), + mnPaneState( XML_split ), + mfSplitX( 0.0 ), + mfSplitY( 0.0 ), + mnCurrentZoom( 0 ), + mnNormalZoom( 0 ), + mnSheetLayoutZoom( 0 ), + mnPageLayoutZoom( 0 ), + mbSelected( false ), + mbRightToLeft( false ), + mbDefGridColor( true ), + mbShowFormulas( false ), + mbShowGrid( true ), + mbShowHeadings( true ), + mbShowZeros( true ), + mbShowOutline( true ), + mbZoomToFit( false ) +{ + maGridColor.setIndexed( OOX_COLOR_WINDOWTEXT ); +} + +bool SheetViewModel::isPageBreakPreview() const +{ + return mnViewType == XML_pageBreakPreview; +} + +sal_Int32 SheetViewModel::getNormalZoom() const +{ + const sal_Int32& rnZoom = isPageBreakPreview() ? mnNormalZoom : mnCurrentZoom; + sal_Int32 nZoom = (rnZoom > 0) ? rnZoom : OOX_SHEETVIEW_NORMALZOOM_DEF; + return getLimitedValue< sal_Int32 >( nZoom, API_ZOOMVALUE_MIN, API_ZOOMVALUE_MAX ); +} + +sal_Int32 SheetViewModel::getPageBreakZoom() const +{ + const sal_Int32& rnZoom = isPageBreakPreview() ? mnCurrentZoom : mnSheetLayoutZoom; + sal_Int32 nZoom = (rnZoom > 0) ? rnZoom : OOX_SHEETVIEW_SHEETLAYZOOM_DEF; + return getLimitedValue< sal_Int32 >( nZoom, API_ZOOMVALUE_MIN, API_ZOOMVALUE_MAX ); +} + +sal_Int32 SheetViewModel::getGridColor( const FilterBase& rFilter ) const +{ + return mbDefGridColor ? API_RGB_TRANSPARENT : maGridColor.getColor( rFilter.getGraphicHelper() ); +} + +const PaneSelectionModel* SheetViewModel::getPaneSelection( sal_Int32 nPaneId ) const +{ + return maPaneSelMap.get( nPaneId ).get(); +} + +const PaneSelectionModel* SheetViewModel::getActiveSelection() const +{ + return getPaneSelection( mnActivePaneId ); +} + +PaneSelectionModel& SheetViewModel::createPaneSelection( sal_Int32 nPaneId ) +{ + PaneSelectionModelMap::mapped_type& rxPaneSel = maPaneSelMap[ nPaneId ]; + if( !rxPaneSel ) + rxPaneSel.reset( new PaneSelectionModel ); + return *rxPaneSel; +} + +// ---------------------------------------------------------------------------- + +SheetViewSettings::SheetViewSettings( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ) +{ +} + +void SheetViewSettings::importSheetView( const AttributeList& rAttribs ) +{ + SheetViewModel& rModel = *createSheetView(); + rModel.maGridColor.setIndexed( rAttribs.getInteger( XML_colorId, OOX_COLOR_WINDOWTEXT ) ); + rModel.maFirstPos = getAddressConverter().createValidCellAddress( rAttribs.getString( XML_topLeftCell, OUString() ), getSheetIndex(), false ); + rModel.mnWorkbookViewId = rAttribs.getToken( XML_workbookViewId, 0 ); + rModel.mnViewType = rAttribs.getToken( XML_view, XML_normal ); + rModel.mnCurrentZoom = rAttribs.getInteger( XML_zoomScale, 100 ); + rModel.mnNormalZoom = rAttribs.getInteger( XML_zoomScaleNormal, 0 ); + rModel.mnSheetLayoutZoom = rAttribs.getInteger( XML_zoomScaleSheetLayoutView, 0 ); + rModel.mnPageLayoutZoom = rAttribs.getInteger( XML_zoomScalePageLayoutView, 0 ); + rModel.mbSelected = rAttribs.getBool( XML_tabSelected, false ); + rModel.mbRightToLeft = rAttribs.getBool( XML_rightToLeft, false ); + rModel.mbDefGridColor = rAttribs.getBool( XML_defaultGridColor, true ); + rModel.mbShowFormulas = rAttribs.getBool( XML_showFormulas, false ); + rModel.mbShowGrid = rAttribs.getBool( XML_showGridLines, true ); + rModel.mbShowHeadings = rAttribs.getBool( XML_showRowColHeaders, true ); + rModel.mbShowZeros = rAttribs.getBool( XML_showZeros, true ); + rModel.mbShowOutline = rAttribs.getBool( XML_showOutlineSymbols, true ); +} + +void SheetViewSettings::importPane( const AttributeList& rAttribs ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importPane - missing sheet view model" ); + if( !maSheetViews.empty() ) + { + SheetViewModel& rModel = *maSheetViews.back(); + rModel.maSecondPos = getAddressConverter().createValidCellAddress( rAttribs.getString( XML_topLeftCell, OUString() ), getSheetIndex(), false ); + rModel.mnActivePaneId = rAttribs.getToken( XML_activePane, XML_topLeft ); + rModel.mnPaneState = rAttribs.getToken( XML_state, XML_split ); + rModel.mfSplitX = rAttribs.getDouble( XML_xSplit, 0.0 ); + rModel.mfSplitY = rAttribs.getDouble( XML_ySplit, 0.0 ); + } +} + +void SheetViewSettings::importSelection( const AttributeList& rAttribs ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importSelection - missing sheet view model" ); + if( !maSheetViews.empty() ) + { + // pane this selection belongs to + sal_Int32 nPaneId = rAttribs.getToken( XML_pane, XML_topLeft ); + PaneSelectionModel& rSelData = maSheetViews.back()->createPaneSelection( nPaneId ); + // cursor position + rSelData.maActiveCell = getAddressConverter().createValidCellAddress( rAttribs.getString( XML_activeCell, OUString() ), getSheetIndex(), false ); + rSelData.mnActiveCellId = rAttribs.getInteger( XML_activeCellId, 0 ); + // selection + rSelData.maSelection.clear(); + getAddressConverter().convertToCellRangeList( rSelData.maSelection, rAttribs.getString( XML_sqref, OUString() ), getSheetIndex(), false ); + } +} + +void SheetViewSettings::importChartSheetView( const AttributeList& rAttribs ) +{ + SheetViewModel& rModel = *createSheetView(); + rModel.mnWorkbookViewId = rAttribs.getToken( XML_workbookViewId, 0 ); + rModel.mnCurrentZoom = rAttribs.getInteger( XML_zoomScale, 100 ); + rModel.mbSelected = rAttribs.getBool( XML_tabSelected, false ); + rModel.mbZoomToFit = rAttribs.getBool( XML_zoomToFit, false ); +} + +void SheetViewSettings::importSheetView( RecordInputStream& rStrm ) +{ + SheetViewModel& rModel = *createSheetView(); + sal_uInt16 nFlags; + sal_Int32 nViewType; + BinAddress aFirstPos; + rStrm >> nFlags >> nViewType >> aFirstPos; + rModel.maGridColor.importColorId( rStrm ); + rModel.mnCurrentZoom = rStrm.readuInt16(); + rModel.mnNormalZoom = rStrm.readuInt16(); + rModel.mnSheetLayoutZoom = rStrm.readuInt16(); + rModel.mnPageLayoutZoom = rStrm.readuInt16(); + rStrm >> rModel.mnWorkbookViewId; + + rModel.maFirstPos = getAddressConverter().createValidCellAddress( aFirstPos, getSheetIndex(), false ); + static const sal_Int32 spnViewTypes[] = { XML_normal, XML_pageBreakPreview, XML_pageLayout }; + rModel.mnViewType = STATIC_ARRAY_SELECT( spnViewTypes, nViewType, XML_normal ); + rModel.mbSelected = getFlag( nFlags, OOBIN_SHEETVIEW_SELECTED ); + rModel.mbRightToLeft = getFlag( nFlags, OOBIN_SHEETVIEW_RIGHTTOLEFT ); + rModel.mbDefGridColor = getFlag( nFlags, OOBIN_SHEETVIEW_DEFGRIDCOLOR ); + rModel.mbShowFormulas = getFlag( nFlags, OOBIN_SHEETVIEW_SHOWFORMULAS ); + rModel.mbShowGrid = getFlag( nFlags, OOBIN_SHEETVIEW_SHOWGRID ); + rModel.mbShowHeadings = getFlag( nFlags, OOBIN_SHEETVIEW_SHOWHEADINGS ); + rModel.mbShowZeros = getFlag( nFlags, OOBIN_SHEETVIEW_SHOWZEROS ); + rModel.mbShowOutline = getFlag( nFlags, OOBIN_SHEETVIEW_SHOWOUTLINE ); +} + +void SheetViewSettings::importPane( RecordInputStream& rStrm ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importPane - missing sheet view model" ); + if( !maSheetViews.empty() ) + { + SheetViewModel& rModel = *maSheetViews.back(); + + BinAddress aSecondPos; + sal_Int32 nActivePaneId; + sal_uInt8 nFlags; + rStrm >> rModel.mfSplitX >> rModel.mfSplitY >> aSecondPos >> nActivePaneId >> nFlags; + + rModel.maSecondPos = getAddressConverter().createValidCellAddress( aSecondPos, getSheetIndex(), false ); + rModel.mnActivePaneId = lclGetOoxPaneId( nActivePaneId, XML_topLeft ); + rModel.mnPaneState = getFlagValue( nFlags, OOBIN_PANE_FROZEN, getFlagValue( nFlags, OOBIN_PANE_FROZENNOSPLIT, XML_frozen, XML_frozenSplit ), XML_split ); + } +} + +void SheetViewSettings::importSelection( RecordInputStream& rStrm ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importSelection - missing sheet view model" ); + if( !maSheetViews.empty() ) + { + // pane this selection belongs to + sal_Int32 nPaneId = rStrm.readInt32(); + PaneSelectionModel& rPaneSel = maSheetViews.back()->createPaneSelection( lclGetOoxPaneId( nPaneId, -1 ) ); + // cursor position + BinAddress aActiveCell; + rStrm >> aActiveCell >> rPaneSel.mnActiveCellId; + rPaneSel.maActiveCell = getAddressConverter().createValidCellAddress( aActiveCell, getSheetIndex(), false ); + // selection + BinRangeList aSelection; + rStrm >> aSelection; + rPaneSel.maSelection.clear(); + getAddressConverter().convertToCellRangeList( rPaneSel.maSelection, aSelection, getSheetIndex(), false ); + } +} + +void SheetViewSettings::importChartSheetView( RecordInputStream& rStrm ) +{ + SheetViewModel& rModel = *createSheetView(); + sal_uInt16 nFlags; + rStrm >> nFlags >> rModel.mnCurrentZoom >> rModel.mnWorkbookViewId; + + rModel.mbSelected = getFlag( nFlags, OOBIN_CHARTSHEETVIEW_SELECTED ); + rModel.mbZoomToFit = getFlag( nFlags, OOBIN_CHARTSHEETVIEW_ZOOMTOFIT ); +} + +void SheetViewSettings::importWindow2( BiffInputStream& rStrm ) +{ + OSL_ENSURE( maSheetViews.empty(), "SheetViewSettings::importWindow2 - multiple WINDOW2 records" ); + SheetViewModel& rModel = *createSheetView(); + if( getBiff() == BIFF2 ) + { + rModel.mbShowFormulas = rStrm.readuInt8() != 0; + rModel.mbShowGrid = rStrm.readuInt8() != 0; + rModel.mbShowHeadings = rStrm.readuInt8() != 0; + rModel.mnPaneState = (rStrm.readuInt8() == 0) ? XML_split : XML_frozen; + rModel.mbShowZeros = rStrm.readuInt8() != 0; + BinAddress aFirstPos; + rStrm >> aFirstPos; + rModel.maFirstPos = getAddressConverter().createValidCellAddress( aFirstPos, getSheetIndex(), false ); + rModel.mbDefGridColor = rStrm.readuInt8() != 0; + rModel.maGridColor.importColorRgb( rStrm ); + } + else + { + sal_uInt16 nFlags; + BinAddress aFirstPos; + rStrm >> nFlags >> aFirstPos; + + rModel.maFirstPos = getAddressConverter().createValidCellAddress( aFirstPos, getSheetIndex(), false ); + rModel.mnPaneState = getFlagValue( nFlags, BIFF_WINDOW2_FROZEN, getFlagValue( nFlags, BIFF_WINDOW2_FROZENNOSPLIT, XML_frozen, XML_frozenSplit ), XML_split ); + rModel.mbSelected = getFlag( nFlags, BIFF_WINDOW2_SELECTED ); + rModel.mbRightToLeft = getFlag( nFlags, BIFF_WINDOW2_RIGHTTOLEFT ); + rModel.mbDefGridColor = getFlag( nFlags, BIFF_WINDOW2_DEFGRIDCOLOR ); + rModel.mbShowFormulas = getFlag( nFlags, BIFF_WINDOW2_SHOWFORMULAS ); + rModel.mbShowGrid = getFlag( nFlags, BIFF_WINDOW2_SHOWGRID ); + rModel.mbShowHeadings = getFlag( nFlags, BIFF_WINDOW2_SHOWHEADINGS ); + rModel.mbShowZeros = getFlag( nFlags, BIFF_WINDOW2_SHOWZEROS ); + rModel.mbShowOutline = getFlag( nFlags, BIFF_WINDOW2_SHOWOUTLINE ); + + if( getBiff() == BIFF8 ) + { + rModel.mnViewType = getFlagValue( nFlags, BIFF_WINDOW2_PAGEBREAKMODE, XML_pageBreakPreview, XML_normal ); + + rModel.maGridColor.importColorId( rStrm ); + // zoom data not included in chart sheets + if( (getSheetType() != SHEETTYPE_CHARTSHEET) && (rStrm.getRemaining() >= 6) ) + { + rStrm.skip( 2 ); + sal_uInt16 nPageZoom, nNormalZoom; + rStrm >> nPageZoom >> nNormalZoom; + rModel.mnSheetLayoutZoom = nPageZoom; + rModel.mnNormalZoom = nNormalZoom; + } + } + else + { + rModel.maGridColor.importColorRgb( rStrm ); + } + } +} + +void SheetViewSettings::importPane( BiffInputStream& rStrm ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importPane - missing leading WINDOW2 record" ); + if( !maSheetViews.empty() ) + { + sal_uInt8 nActivePaneId; + sal_uInt16 nSplitX, nSplitY; + BinAddress aSecondPos; + rStrm >> nSplitX >> nSplitY >> aSecondPos >> nActivePaneId; + + SheetViewModel& rModel = *maSheetViews.back(); + rModel.mfSplitX = nSplitX; + rModel.mfSplitY = nSplitY; + rModel.maSecondPos = getAddressConverter().createValidCellAddress( aSecondPos, getSheetIndex(), false ); + rModel.mnActivePaneId = lclGetOoxPaneId( nActivePaneId, XML_topLeft ); + } +} + +void SheetViewSettings::importScl( BiffInputStream& rStrm ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importScl - missing leading WINDOW2 record" ); + if( !maSheetViews.empty() ) + { + sal_uInt16 nNum, nDenom; + rStrm >> nNum >> nDenom; + OSL_ENSURE( nDenom > 0, "SheetViewSettings::importScl - invalid denominator" ); + if( nDenom > 0 ) + maSheetViews.back()->mnCurrentZoom = getLimitedValue< sal_Int32, sal_uInt16 >( (nNum * 100) / nDenom, 10, 400 ); + } +} + +void SheetViewSettings::importSelection( BiffInputStream& rStrm ) +{ + OSL_ENSURE( !maSheetViews.empty(), "SheetViewSettings::importPane - missing leading WINDOW2 record" ); + if( !maSheetViews.empty() ) + { + // pane this selection belongs to + sal_uInt8 nPaneId = rStrm.readuInt8(); + PaneSelectionModel& rPaneSel = maSheetViews.back()->createPaneSelection( lclGetOoxPaneId( nPaneId, -1 ) ); + // cursor position + BinAddress aActiveCell; + sal_uInt16 nActiveCellId; + rStrm >> aActiveCell >> nActiveCellId; + rPaneSel.maActiveCell = getAddressConverter().createValidCellAddress( aActiveCell, getSheetIndex(), false ); + rPaneSel.mnActiveCellId = nActiveCellId; + // selection + rPaneSel.maSelection.clear(); + BinRangeList aSelection; + aSelection.read( rStrm, false ); + getAddressConverter().convertToCellRangeList( rPaneSel.maSelection, aSelection, getSheetIndex(), false ); + } +} + +void SheetViewSettings::finalizeImport() +{ + // force creation of sheet view model to get the Excel defaults + SheetViewModelRef xModel = maSheetViews.empty() ? createSheetView() : maSheetViews.front(); + + // #i59590# #158194# special handling for chart sheets (Excel ignores some settings in chart sheets) + if( getSheetType() == SHEETTYPE_CHARTSHEET ) + { + xModel->maPaneSelMap.clear(); + xModel->maFirstPos = xModel->maSecondPos = CellAddress( getSheetIndex(), 0, 0 ); + xModel->mnViewType = XML_normal; + xModel->mnActivePaneId = XML_topLeft; + xModel->mnPaneState = XML_split; + xModel->mfSplitX = xModel->mfSplitY = 0.0; + xModel->mbRightToLeft = false; + xModel->mbDefGridColor = true; + xModel->mbShowFormulas = false; + xModel->mbShowGrid = true; + xModel->mbShowHeadings = true; + xModel->mbShowZeros = true; + xModel->mbShowOutline = true; + } + + // mirrored sheet (this is not a view setting in Calc) + if( xModel->mbRightToLeft ) + { + PropertySet aPropSet( getSheet() ); + aPropSet.setProperty( PROP_TableLayout, ::com::sun::star::text::WritingMode2::RL_TB ); + } + + // sheet selected (active sheet must be selected) + bool bSelected = xModel->mbSelected || (getSheetIndex() == getViewSettings().getActiveCalcSheet()); + + // visible area and current cursor position (selection not supported via API) + CellAddress aFirstPos = xModel->maFirstPos; + const PaneSelectionModel* pPaneSel = xModel->getActiveSelection(); + CellAddress aCursor = pPaneSel ? pPaneSel->maActiveCell : aFirstPos; + + // freeze/split position default + sal_Int16 nHSplitMode = API_SPLITMODE_NONE; + sal_Int16 nVSplitMode = API_SPLITMODE_NONE; + sal_Int32 nHSplitPos = 0; + sal_Int32 nVSplitPos = 0; + // active pane default + sal_Int16 nActivePane = API_SPLITPANE_BOTTOMLEFT; + + // freeze/split position + if( (xModel->mnPaneState == XML_frozen) || (xModel->mnPaneState == XML_frozenSplit) ) + { + /* Frozen panes: handle split position as row/column positions. + #i35812# Excel uses number of visible rows/columns in the + frozen area (rows/columns scolled outside are not incuded), + Calc uses absolute position of first unfrozen row/column. */ + const CellAddress& rMaxApiPos = getAddressConverter().getMaxApiAddress(); + if( (xModel->mfSplitX >= 1.0) && (xModel->maFirstPos.Column + xModel->mfSplitX <= rMaxApiPos.Column) ) + nHSplitPos = static_cast< sal_Int32 >( xModel->maFirstPos.Column + xModel->mfSplitX ); + nHSplitMode = (nHSplitPos > 0) ? API_SPLITMODE_FREEZE : API_SPLITMODE_NONE; + if( (xModel->mfSplitY >= 1.0) && (xModel->maFirstPos.Row + xModel->mfSplitY <= rMaxApiPos.Row) ) + nVSplitPos = static_cast< sal_Int32 >( xModel->maFirstPos.Row + xModel->mfSplitY ); + nVSplitMode = (nVSplitPos > 0) ? API_SPLITMODE_FREEZE : API_SPLITMODE_NONE; + } + else if( xModel->mnPaneState == XML_split ) + { + // split window: view settings API uses twips... + nHSplitPos = getLimitedValue< sal_Int32, double >( xModel->mfSplitX + 0.5, 0, SAL_MAX_INT32 ); + nHSplitMode = (nHSplitPos > 0) ? API_SPLITMODE_SPLIT : API_SPLITMODE_NONE; + nVSplitPos = getLimitedValue< sal_Int32, double >( xModel->mfSplitY + 0.5, 0, SAL_MAX_INT32 ); + nVSplitMode = (nVSplitPos > 0) ? API_SPLITMODE_SPLIT : API_SPLITMODE_NONE; + } + + // active pane + switch( xModel->mnActivePaneId ) + { + // no horizontal split -> always use left panes + // no vertical split -> always use *bottom* panes + case XML_topLeft: + nActivePane = (nVSplitMode == API_SPLITMODE_NONE) ? API_SPLITPANE_BOTTOMLEFT : API_SPLITPANE_TOPLEFT; + break; + case XML_topRight: + nActivePane = (nHSplitMode == API_SPLITMODE_NONE) ? + ((nVSplitMode == API_SPLITMODE_NONE) ? API_SPLITPANE_BOTTOMLEFT : API_SPLITPANE_TOPLEFT) : + ((nVSplitMode == API_SPLITMODE_NONE) ? API_SPLITPANE_BOTTOMRIGHT : API_SPLITPANE_TOPRIGHT); + break; + case XML_bottomLeft: + nActivePane = API_SPLITPANE_BOTTOMLEFT; + break; + case XML_bottomRight: + nActivePane = (nHSplitMode == API_SPLITMODE_NONE) ? API_SPLITPANE_BOTTOMLEFT : API_SPLITPANE_BOTTOMRIGHT; + break; + } + + // write the sheet view settings into the property sequence + PropertyMap aPropMap; + aPropMap[ PROP_TableSelected ] <<= bSelected; + aPropMap[ PROP_CursorPositionX ] <<= aCursor.Column; + aPropMap[ PROP_CursorPositionY ] <<= aCursor.Row; + aPropMap[ PROP_HorizontalSplitMode ] <<= nHSplitMode; + aPropMap[ PROP_VerticalSplitMode ] <<= nVSplitMode; + aPropMap[ PROP_HorizontalSplitPositionTwips ] <<= nHSplitPos; + aPropMap[ PROP_VerticalSplitPositionTwips ] <<= nVSplitPos; + aPropMap[ PROP_ActiveSplitRange ] <<= nActivePane; + aPropMap[ PROP_PositionLeft ] <<= aFirstPos.Column; + aPropMap[ PROP_PositionTop ] <<= aFirstPos.Row; + aPropMap[ PROP_PositionRight ] <<= xModel->maSecondPos.Column; + aPropMap[ PROP_PositionBottom ] <<= ((nVSplitPos > 0) ? xModel->maSecondPos.Row : xModel->maFirstPos.Row); + aPropMap[ PROP_ZoomType ] <<= API_ZOOMTYPE_PERCENT; + aPropMap[ PROP_ZoomValue ] <<= static_cast< sal_Int16 >( xModel->getNormalZoom() ); + aPropMap[ PROP_PageViewZoomValue ] <<= static_cast< sal_Int16 >( xModel->getPageBreakZoom() ); + aPropMap[ PROP_GridColor ] <<= xModel->getGridColor( getBaseFilter() ); + aPropMap[ PROP_ShowPageBreakPreview ] <<= xModel->isPageBreakPreview(); + aPropMap[ PROP_ShowFormulas ] <<= xModel->mbShowFormulas; + aPropMap[ PROP_ShowGrid ] <<= xModel->mbShowGrid; + aPropMap[ PROP_HasColumnRowHeaders ] <<= xModel->mbShowHeadings; + aPropMap[ PROP_ShowZeroValues ] <<= xModel->mbShowZeros; + aPropMap[ PROP_IsOutlineSymbolsSet ] <<= xModel->mbShowOutline; + + // store sheet view settings in global view settings object + getViewSettings().setSheetViewSettings( getSheetIndex(), xModel, Any( aPropMap.makePropertyValueSequence() ) ); +} + +// private -------------------------------------------------------------------- + +SheetViewModelRef SheetViewSettings::createSheetView() +{ + SheetViewModelRef xModel( new SheetViewModel ); + maSheetViews.push_back( xModel ); + return xModel; +} + +// ============================================================================ + +WorkbookViewModel::WorkbookViewModel() : + mnWinX( 0 ), + mnWinY( 0 ), + mnWinWidth( 0 ), + mnWinHeight( 0 ), + mnActiveSheet( 0 ), + mnFirstVisSheet( 0 ), + mnTabBarWidth( OOX_BOOKVIEW_TABBARRATIO_DEF ), + mnVisibility( XML_visible ), + mbShowTabBar( true ), + mbShowHorScroll( true ), + mbShowVerScroll( true ), + mbMinimized( false ) +{ +} + +// ---------------------------------------------------------------------------- + +ViewSettings::ViewSettings( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ), + mbValidOleSize( false ) +{ +} + +void ViewSettings::importWorkbookView( const AttributeList& rAttribs ) +{ + WorkbookViewModel& rModel = createWorkbookView(); + rModel.mnWinX = rAttribs.getInteger( XML_xWindow, 0 ); + rModel.mnWinY = rAttribs.getInteger( XML_yWindow, 0 ); + rModel.mnWinWidth = rAttribs.getInteger( XML_windowWidth, 0 ); + rModel.mnWinHeight = rAttribs.getInteger( XML_windowHeight, 0 ); + rModel.mnActiveSheet = rAttribs.getInteger( XML_activeTab, 0 ); + rModel.mnFirstVisSheet = rAttribs.getInteger( XML_firstSheet, 0 ); + rModel.mnTabBarWidth = rAttribs.getInteger( XML_tabRatio, 600 ); + rModel.mnVisibility = rAttribs.getToken( XML_visibility, XML_visible ); + rModel.mbShowTabBar = rAttribs.getBool( XML_showSheetTabs, true ); + rModel.mbShowHorScroll = rAttribs.getBool( XML_showHorizontalScroll, true ); + rModel.mbShowVerScroll = rAttribs.getBool( XML_showVerticalScroll, true ); + rModel.mbMinimized = rAttribs.getBool( XML_minimized, false ); +} + +void ViewSettings::importOleSize( const AttributeList& rAttribs ) +{ + OUString aRange = rAttribs.getString( XML_ref, OUString() ); + mbValidOleSize = getAddressConverter().convertToCellRange( maOleSize, aRange, 0, true, false ); +} + +void ViewSettings::importWorkbookView( RecordInputStream& rStrm ) +{ + WorkbookViewModel& rModel = createWorkbookView(); + sal_uInt8 nFlags; + rStrm >> rModel.mnWinX >> rModel.mnWinY >> rModel.mnWinWidth >> rModel.mnWinHeight >> rModel.mnTabBarWidth >> rModel.mnFirstVisSheet >> rModel.mnActiveSheet >> nFlags; + rModel.mnVisibility = getFlagValue( nFlags, OOBIN_WBVIEW_HIDDEN, XML_hidden, XML_visible ); + rModel.mbShowTabBar = getFlag( nFlags, OOBIN_WBVIEW_SHOWTABBAR ); + rModel.mbShowHorScroll = getFlag( nFlags, OOBIN_WBVIEW_SHOWHORSCROLL ); + rModel.mbShowVerScroll = getFlag( nFlags, OOBIN_WBVIEW_SHOWVERSCROLL ); + rModel.mbMinimized = getFlag( nFlags, OOBIN_WBVIEW_MINIMIZED ); +} + +void ViewSettings::importOleSize( RecordInputStream& rStrm ) +{ + BinRange aBinRange; + rStrm >> aBinRange; + mbValidOleSize = getAddressConverter().convertToCellRange( maOleSize, aBinRange, 0, true, false ); +} + +void ViewSettings::importWindow1( BiffInputStream& rStrm ) +{ + sal_uInt16 nWinX, nWinY, nWinWidth, nWinHeight; + rStrm >> nWinX >> nWinY >> nWinWidth >> nWinHeight; + + // WINDOW1 record occures in every sheet in BIFF4W + OSL_ENSURE( maBookViews.empty() || ((getBiff() == BIFF4) && isWorkbookFile()), + "ViewSettings::importWindow1 - multiple WINDOW1 records" ); + WorkbookViewModel& rModel = createWorkbookView(); + rModel.mnWinX = nWinX; + rModel.mnWinY = nWinY; + rModel.mnWinWidth = nWinWidth; + rModel.mnWinHeight = nWinHeight; + + if( getBiff() <= BIFF4 ) + { + sal_uInt8 nHidden; + rStrm >> nHidden; + rModel.mnVisibility = (nHidden == 0) ? XML_visible : XML_hidden; + } + else + { + sal_uInt16 nFlags, nActiveTab, nFirstVisTab, nSelectCnt, nTabBarWidth; + rStrm >> nFlags >> nActiveTab >> nFirstVisTab >> nSelectCnt >> nTabBarWidth; + + rModel.mnActiveSheet = nActiveTab; + rModel.mnFirstVisSheet = nFirstVisTab; + rModel.mnTabBarWidth = nTabBarWidth; + rModel.mnVisibility = getFlagValue( nFlags, BIFF_WINDOW1_HIDDEN, XML_hidden, XML_visible ); + rModel.mbMinimized = getFlag( nFlags, BIFF_WINDOW1_MINIMIZED ); + rModel.mbShowHorScroll = getFlag( nFlags, BIFF_WINDOW1_SHOWHORSCROLL ); + rModel.mbShowVerScroll = getFlag( nFlags, BIFF_WINDOW1_SHOWVERSCROLL ); + rModel.mbShowTabBar = getFlag( nFlags, BIFF_WINDOW1_SHOWTABBAR ); + } +} + +void ViewSettings::importOleSize( BiffInputStream& rStrm ) +{ + rStrm.skip( 2 ); + BinRange aBinRange; + aBinRange.read( rStrm, false ); + mbValidOleSize = getAddressConverter().convertToCellRange( maOleSize, aBinRange, 0, true, false ); +} + +void ViewSettings::setSheetViewSettings( sal_Int16 nSheet, const SheetViewModelRef& rxSheetView, const Any& rProperties ) +{ + maSheetViews[ nSheet ] = rxSheetView; + maSheetProps[ nSheet ] = rProperties; +} + +void ViewSettings::setSheetUsedArea( const CellRangeAddress& rUsedArea ) +{ + maSheetUsedAreas[ rUsedArea.Sheet ] = rUsedArea; +} + +void ViewSettings::finalizeImport() +{ + const WorksheetBuffer& rWorksheets = getWorksheets(); + if( rWorksheets.getWorksheetCount() <= 0 ) return; + + // force creation of workbook view model to get the Excel defaults + const WorkbookViewModel& rModel = maBookViews.empty() ? createWorkbookView() : *maBookViews.front(); + + // show object mode is part of workbook settings + sal_Int16 nShowMode = getWorkbookSettings().getApiShowObjectMode(); + + // view settings for all sheets + Reference< XNameContainer > xSheetsNC = ContainerHelper::createNameContainer( getGlobalFactory() ); + if( !xSheetsNC.is() ) return; + for( SheetPropertiesMap::const_iterator aIt = maSheetProps.begin(), aEnd = maSheetProps.end(); aIt != aEnd; ++aIt ) + ContainerHelper::insertByName( xSheetsNC, rWorksheets.getCalcSheetName( aIt->first ), aIt->second ); + + // use active sheet to set sheet properties that are document-global in Calc + sal_Int16 nActiveSheet = getActiveCalcSheet(); + SheetViewModelRef& rxActiveSheetView = maSheetViews[ nActiveSheet ]; + OSL_ENSURE( rxActiveSheetView.get(), "ViewSettings::finalizeImport - missing active sheet view settings" ); + if( !rxActiveSheetView ) + rxActiveSheetView.reset( new SheetViewModel ); + + Reference< XIndexContainer > xContainer = ContainerHelper::createIndexContainer( getGlobalFactory() ); + if( xContainer.is() ) try + { + PropertyMap aPropMap; + aPropMap[ PROP_Tables ] <<= xSheetsNC; + aPropMap[ PROP_ActiveTable ] <<= rWorksheets.getCalcSheetName( nActiveSheet ); + aPropMap[ PROP_HasHorizontalScrollBar ] <<= rModel.mbShowHorScroll; + aPropMap[ PROP_HasVerticalScrollBar ] <<= rModel.mbShowVerScroll; + aPropMap[ PROP_HasSheetTabs ] <<= rModel.mbShowTabBar; + aPropMap[ PROP_RelativeHorizontalTabbarWidth ] <<= double( rModel.mnTabBarWidth / 1000.0 ); + aPropMap[ PROP_ShowObjects ] <<= nShowMode; + aPropMap[ PROP_ShowCharts ] <<= nShowMode; + aPropMap[ PROP_ShowDrawing ] <<= nShowMode; + aPropMap[ PROP_GridColor ] <<= rxActiveSheetView->getGridColor( getBaseFilter() ); + aPropMap[ PROP_ShowPageBreakPreview ] <<= rxActiveSheetView->isPageBreakPreview(); + aPropMap[ PROP_ShowFormulas ] <<= rxActiveSheetView->mbShowFormulas; + aPropMap[ PROP_ShowGrid ] <<= rxActiveSheetView->mbShowGrid; + aPropMap[ PROP_HasColumnRowHeaders ] <<= rxActiveSheetView->mbShowHeadings; + aPropMap[ PROP_ShowZeroValues ] <<= rxActiveSheetView->mbShowZeros; + aPropMap[ PROP_IsOutlineSymbolsSet ] <<= rxActiveSheetView->mbShowOutline; + + xContainer->insertByIndex( 0, Any( aPropMap.makePropertyValueSequence() ) ); + Reference< XIndexAccess > xIAccess( xContainer, UNO_QUERY_THROW ); + Reference< XViewDataSupplier > xViewDataSuppl( getDocument(), UNO_QUERY_THROW ); + xViewDataSuppl->setViewData( xIAccess ); + } + catch( Exception& ) + { + OSL_ENSURE( false, "ViewSettings::finalizeImport - cannot create document view settings" ); + } + + /* Set visible area to be used if this document is an embedded OLE object. + #i44077# If a new OLE object is inserted from file, there is no OLESIZE + record in the Excel file. In this case, use the used area calculated + from file contents (used cells and drawing objects). */ + maOleSize.Sheet = nActiveSheet; + const CellRangeAddress* pVisibleArea = mbValidOleSize ? + &maOleSize : ContainerHelper::getMapElement( maSheetUsedAreas, nActiveSheet ); + if( pVisibleArea ) + { + // calculate the visible area in units of 1/100 mm + PropertySet aRangeProp( getCellRangeFromDoc( *pVisibleArea ) ); + Point aPos; + Size aSize; + if( aRangeProp.getProperty( aPos, PROP_Position ) && aRangeProp.getProperty( aSize, PROP_Size ) ) + { + // set the visible area as sequence of long at the media descriptor + Sequence< sal_Int32 > aWinExtent( 4 ); + aWinExtent[ 0 ] = aPos.X; + aWinExtent[ 1 ] = aPos.Y; + aWinExtent[ 2 ] = aPos.X + aSize.Width; + aWinExtent[ 3 ] = aPos.Y + aSize.Height; + getBaseFilter().getMediaDescriptor()[ CREATE_OUSTRING( "WinExtent" ) ] <<= aWinExtent; + } + } +} + +sal_Int16 ViewSettings::getActiveCalcSheet() const +{ + return maBookViews.empty() ? 0 : ::std::max< sal_Int16 >( getWorksheets().getCalcSheetIndex( maBookViews.front()->mnActiveSheet ), 0 ); +} + +// private -------------------------------------------------------------------- + +WorkbookViewModel& ViewSettings::createWorkbookView() +{ + WorkbookViewModelRef xModel( new WorkbookViewModel ); + maBookViews.push_back( xModel ); + return *xModel; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox diff --git a/oox/source/xls/webquerybuffer.cxx b/oox/source/xls/webquerybuffer.cxx new file mode 100644 index 000000000000..2babebd4a8d7 --- /dev/null +++ b/oox/source/xls/webquerybuffer.cxx @@ -0,0 +1,199 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/webquerybuffer.hxx" +#include "oox/helper/attributelist.hxx" + +#define DEBUG_OOX_WEBQUERY_BUFFER 1 + +#if DEBUG_OOX_WEBQUERY_BUFFER +#include <stdio.h> +#endif + +using ::rtl::OUString; + +namespace oox { +namespace xls { + +const sal_Int32 Connection::CONNECTION_ODBC_SOURCE = 1; +const sal_Int32 Connection::CONNECTION_DAO_SOURCE = 2; +const sal_Int32 Connection::CONNECTION_FILE_SOURCE = 3; +const sal_Int32 Connection::CONNECTION_WEBQUERY = 4; +const sal_Int32 Connection::CONNECTION_OLEDB_SOURCE = 5; +const sal_Int32 Connection::CONNECTION_TEXT_SOURCE = 6; +const sal_Int32 Connection::CONNECTION_ADO_RECORD_SET = 7; +const sal_Int32 Connection::CONNECTION_DSP = 8; + +// ============================================================================ + +WebQueryBuffer::WebQueryBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ + maQueryTableMap.clear(); +} + +void WebQueryBuffer::importQueryTable( const AttributeList& rAttribs ) +{ + OUString aName = rAttribs.getString( XML_name, OUString() ); + if ( !aName.getLength() ) + return; + + QueryTable aQTable; + aQTable.mnConnectionId = rAttribs.getInteger( XML_connectionId, 0 ); + + maQueryTableMap.insert( QueryTableHashMap::value_type( aName, aQTable ) ); + + // All documented attributes of queryTable: + // adjustColumnWidth (bool) + // applyAlignmentFormats (bool) + // applyBorderFormats (bool) + // applyFontFormats (bool) + // applyNumberFormats (bool) + // applyPatternFormats (bool) + // applyWidthHeightFormats (bool) + // autoFormatId (unsigned int) + // backgroundRefresh (bool) + // connectionId (unsigned int) + // disableEdit (bool) + // disableRefresh (bool) + // fillFormulas (bool) + // firstBackgroundRefresh (bool) + // growShrinkType (insertClear, insertDelete, overwriteClear) + // headers (bool) + // intermediate (bool) + // name (string) + // preserveFormatting(bool) + // refreshOnLoad (bool) + // removeDataOnSave (bool) + // rowNumbers (bool) +} + +void WebQueryBuffer::importConnection( const AttributeList& rAttribs ) +{ + if ( !rAttribs.hasAttribute( XML_id ) || !rAttribs.hasAttribute( XML_name ) ) + { + mnCurConnId = -1; + return; + } + + sal_uInt32 nId = rAttribs.getUnsigned( XML_id, 0 ); + if ( maConnections.size() < (nId + 1) ) + maConnections.resize(nId + 1); + + Connection aConn; + aConn.maName = rAttribs.getString( XML_name, OUString() ); + aConn.mnType = rAttribs.getInteger( XML_type, 0 ); + maConnections[nId] = aConn; + mnCurConnId = nId; + + // All documented attributes of connection. + // background (bool) + // credentials (integrated, none, prompt, stored) + // deleted (bool) + // description (string) + // id (unsigned int) + // interval (unsigned int) + // keepAlive (bool) + // minRefreshableVersion (unsigned byte) + // name (string) + // new (bool) + // odcFile (string) + // onlyUseConnectionFile (bool) + // reconnectionMethod (unsigned int) + // refreshedVersion (unsigned byte) + // refreshOnLoad (bool) + // saveData (bool) + // savePassword (bool) + // singleSignOnId (string) + // sourceFile (string) + // type (unsigned int) +} + +void WebQueryBuffer::importWebPr( const AttributeList& rAttribs ) +{ + if ( 0 > mnCurConnId ) + return; + + Connection& rConn = maConnections[mnCurConnId]; + rConn.mpProperties.reset( new WebProperties ); + WebProperties* pWebPr = static_cast< WebProperties* >( rConn.mpProperties.get() ); + pWebPr->maURL = rAttribs.getString( XML_url, OUString() ); + + // All available attributes: + // consecutive (bool) + // editPage (string) + // firstRow (bool) + // htmlFormat (all, none, rtf) + // htmlTables (bool) + // parsePre (bool) + // post (string) + // sourceData (bool) + // textDates (bool) + // url (string) + // xl2000 (bool) + // xl97 (bool) + // xml (bool) +} + +void WebQueryBuffer::dump() const +{ +#if DEBUG_OOX_WEBQUERY_BUFFER + fprintf(stdout, "----------------------------------------\n"); + { + using ::std::vector; + vector< Connection >::const_iterator itr = maConnections.begin(), itrEnd = maConnections.end(); + sal_Int32 nId = 0; + for (; itr != itrEnd; ++itr, ++nId) + { + if ( itr->mnType == Connection::CONNECTION_WEBQUERY ) + { + WebProperties* pWebPr = static_cast< WebProperties* >( itr->mpProperties.get() ); + fprintf(stdout, "WebQueryBuffer::dump: id = %d url = %s\n", + (int)nId, + OUStringToOString(pWebPr->maURL, RTL_TEXTENCODING_UTF8).getStr()); + } + } + } + + QueryTableHashMap::const_iterator itr = maQueryTableMap.begin(), itrEnd = maQueryTableMap.end(); + for (; itr != itrEnd; ++itr) + { + fprintf(stdout, "WebQueryBuffer::dump: name = %s connection ID = %d\n", + OUStringToOString(itr->first, RTL_TEXTENCODING_UTF8).getStr(), + (int)(itr->second.mnConnectionId)); + } + + fprintf(stdout, "----------------------------------------\n"); + fflush(stdout); +#endif +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/workbookfragment.cxx b/oox/source/xls/workbookfragment.cxx new file mode 100644 index 000000000000..6e43a32495be --- /dev/null +++ b/oox/source/xls/workbookfragment.cxx @@ -0,0 +1,738 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/workbookfragment.hxx" +#include <com/sun/star/table/CellAddress.hpp> +#include "oox/helper/attributelist.hxx" +#include "oox/helper/progressbar.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/ole/olestorage.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/drawingml/themefragmenthandler.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/chartsheetfragment.hxx" +#include "oox/xls/connectionsfragment.hxx" +#include "oox/xls/externallinkbuffer.hxx" +#include "oox/xls/externallinkfragment.hxx" +#include "oox/xls/pivotcachebuffer.hxx" +#include "oox/xls/sharedstringsbuffer.hxx" +#include "oox/xls/sharedstringsfragment.hxx" +#include "oox/xls/stylesfragment.hxx" +#include "oox/xls/tablebuffer.hxx" +#include "oox/xls/themebuffer.hxx" +#include "oox/xls/viewsettings.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/xls/worksheetbuffer.hxx" +#include "oox/xls/worksheetfragment.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +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::io::XInputStream; +using ::com::sun::star::table::CellAddress; +using ::oox::core::ContextHandlerRef; +using ::oox::core::FragmentHandlerRef; +using ::oox::core::RecordInfo; +using ::oox::core::Relation; +using ::oox::drawingml::ThemeFragmentHandler; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const double PROGRESS_LENGTH_GLOBALS = 0.1; /// 10% of progress bar for globals import. + +} // namespace + +// ============================================================================ + +OoxWorkbookFragment::OoxWorkbookFragment( + const WorkbookHelper& rHelper, const OUString& rFragmentPath ) : + OoxWorkbookFragmentBase( rHelper, rFragmentPath ) +{ +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxWorkbookFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nElement == XLS_TOKEN( workbook ) ) return this; + break; + + case XLS_TOKEN( workbook ): + switch( nElement ) + { + case XLS_TOKEN( sheets ): + case XLS_TOKEN( bookViews ): + case XLS_TOKEN( externalReferences ): + case XLS_TOKEN( definedNames ): + case XLS_TOKEN( pivotCaches ): return this; + + case XLS_TOKEN( fileSharing ): getWorkbookSettings().importFileSharing( rAttribs ); break; + case XLS_TOKEN( workbookPr ): getWorkbookSettings().importWorkbookPr( rAttribs ); break; + case XLS_TOKEN( calcPr ): getWorkbookSettings().importCalcPr( rAttribs ); break; + case XLS_TOKEN( oleSize ): getViewSettings().importOleSize( rAttribs ); break; + } + break; + + case XLS_TOKEN( sheets ): + if( nElement == XLS_TOKEN( sheet ) ) getWorksheets().importSheet( rAttribs ); + break; + case XLS_TOKEN( bookViews ): + if( nElement == XLS_TOKEN( workbookView ) ) getViewSettings().importWorkbookView( rAttribs ); + break; + case XLS_TOKEN( externalReferences ): + if( nElement == XLS_TOKEN( externalReference ) ) importExternalReference( rAttribs ); + break; + case XLS_TOKEN( definedNames ): + if( nElement == XLS_TOKEN( definedName ) ) { importDefinedName( rAttribs ); return this; } // collect formula + break; + case XLS_TOKEN( pivotCaches ): + if( nElement == XLS_TOKEN( pivotCache ) ) importPivotCache( rAttribs ); + break; + } + return 0; +} + +void OoxWorkbookFragment::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( definedName ): + if( mxCurrName.get() ) mxCurrName->setFormula( rChars ); + break; + } +} + +ContextHandlerRef OoxWorkbookFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_WORKBOOK ) return this; + break; + + case OOBIN_ID_WORKBOOK: + switch( nRecId ) + { + case OOBIN_ID_SHEETS: + case OOBIN_ID_BOOKVIEWS: + case OOBIN_ID_EXTERNALREFS: + case OOBIN_ID_PIVOTCACHES: return this; + + case OOBIN_ID_FILESHARING: getWorkbookSettings().importFileSharing( rStrm ); break; + case OOBIN_ID_WORKBOOKPR: getWorkbookSettings().importWorkbookPr( rStrm ); break; + case OOBIN_ID_CALCPR: getWorkbookSettings().importCalcPr( rStrm ); break; + case OOBIN_ID_OLESIZE: getViewSettings().importOleSize( rStrm ); break; + case OOBIN_ID_DEFINEDNAME: getDefinedNames().importDefinedName( rStrm ); break; + } + break; + + case OOBIN_ID_SHEETS: + if( nRecId == OOBIN_ID_SHEET ) getWorksheets().importSheet( rStrm ); + break; + case OOBIN_ID_BOOKVIEWS: + if( nRecId == OOBIN_ID_WORKBOOKVIEW ) getViewSettings().importWorkbookView( rStrm ); + break; + + case OOBIN_ID_EXTERNALREFS: + switch( nRecId ) + { + case OOBIN_ID_EXTERNALREF: importExternalRef( rStrm ); break; + case OOBIN_ID_EXTERNALSELF: getExternalLinks().importExternalSelf( rStrm ); break; + case OOBIN_ID_EXTERNALSAME: getExternalLinks().importExternalSame( rStrm ); break; + case OOBIN_ID_EXTERNALADDIN: getExternalLinks().importExternalAddin( rStrm ); break; + case OOBIN_ID_EXTERNALSHEETS: getExternalLinks().importExternalSheets( rStrm ); break; + } + break; + + case OOBIN_ID_PIVOTCACHES: + if( nRecId == OOBIN_ID_PIVOTCACHE ) importPivotCache( rStrm ); + } + return 0; +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxWorkbookFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_BOOKVIEWS, OOBIN_ID_BOOKVIEWS + 1 }, + { OOBIN_ID_EXTERNALREFS, OOBIN_ID_EXTERNALREFS + 1 }, + { OOBIN_ID_FUNCTIONGROUPS, OOBIN_ID_FUNCTIONGROUPS + 2 }, + { OOBIN_ID_PIVOTCACHE, OOBIN_ID_PIVOTCACHE + 1 }, + { OOBIN_ID_PIVOTCACHES, OOBIN_ID_PIVOTCACHES + 1 }, + { OOBIN_ID_SHEETS, OOBIN_ID_SHEETS + 1 }, + { OOBIN_ID_WORKBOOK, OOBIN_ID_WORKBOOK + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +void OoxWorkbookFragment::finalizeImport() +{ + ISegmentProgressBarRef xGlobalSegment = getProgressBar().createSegment( PROGRESS_LENGTH_GLOBALS ); + + // read the theme substream + OUString aThemeFragmentPath = getFragmentPathFromFirstType( CREATE_OFFICEDOC_RELATIONSTYPE( "theme" ) ); + if( aThemeFragmentPath.getLength() > 0 ) + importOoxFragment( new ThemeFragmentHandler( getFilter(), aThemeFragmentPath, getTheme() ) ); + xGlobalSegment->setPosition( 0.25 ); + + // read the styles substream (requires finalized theme buffer) + OUString aStylesFragmentPath = getFragmentPathFromFirstType( CREATE_OFFICEDOC_RELATIONSTYPE( "styles" ) ); + if( aStylesFragmentPath.getLength() > 0 ) + importOoxFragment( new OoxStylesFragment( *this, aStylesFragmentPath ) ); + xGlobalSegment->setPosition( 0.5 ); + + // read the shared string table substream (requires finalized styles buffer) + OUString aSstFragmentPath = getFragmentPathFromFirstType( CREATE_OFFICEDOC_RELATIONSTYPE( "sharedStrings" ) ); + if( aSstFragmentPath.getLength() > 0 ) + importOoxFragment( new OoxSharedStringsFragment( *this, aSstFragmentPath ) ); + xGlobalSegment->setPosition( 0.75 ); + + // read the connections substream + OUString aConnFragmentPath = getFragmentPathFromFirstType( CREATE_OFFICEDOC_RELATIONSTYPE( "connections" ) ); + if( aConnFragmentPath.getLength() > 0 ) + importOoxFragment( new OoxConnectionsFragment( *this, aConnFragmentPath ) ); + xGlobalSegment->setPosition( 1.0 ); + + /* Create fragments for all sheets, before importing them. Needed to do + some preprocessing in the fragment constructors, e.g. loading the table + fragments for all sheets that are needed before the cell formulas are + loaded. */ + typedef ::std::vector< FragmentHandlerRef > SheetFragmentVector; + SheetFragmentVector aSheetFragments; + WorksheetBuffer& rWorksheets = getWorksheets(); + sal_Int32 nWorksheetCount = rWorksheets.getWorksheetCount(); + for( sal_Int32 nWorksheet = 0; nWorksheet < nWorksheetCount; ++nWorksheet ) + { + sal_Int16 nCalcSheet = rWorksheets.getCalcSheetIndex( nWorksheet ); + const Relation* pRelation = getRelations().getRelationFromRelId( rWorksheets.getWorksheetRelId( nWorksheet ) ); + if( (nCalcSheet >= 0) && pRelation ) + { + // get fragment path of the sheet + OUString aFragmentPath = getFragmentPathFromRelation( *pRelation ); + OSL_ENSURE( aFragmentPath.getLength() > 0, "OoxWorkbookFragment::finalizeImport - cannot access sheet fragment" ); + if( aFragmentPath.getLength() > 0 ) + { + ::rtl::Reference< OoxWorksheetFragmentBase > xFragment; + double fSegmentLength = getProgressBar().getFreeLength() / (nWorksheetCount - nWorksheet); + ISegmentProgressBarRef xSheetSegment = getProgressBar().createSegment( fSegmentLength ); + + // create the fragment according to the sheet type + if( pRelation->maType == CREATE_OFFICEDOC_RELATIONSTYPE( "worksheet" ) ) + { + xFragment.set( new OoxWorksheetFragment( *this, aFragmentPath, xSheetSegment, SHEETTYPE_WORKSHEET, nCalcSheet ) ); + } + else if( pRelation->maType == CREATE_OFFICEDOC_RELATIONSTYPE( "chartsheet" ) ) + { + xFragment.set( new OoxChartsheetFragment( *this, aFragmentPath, xSheetSegment, nCalcSheet ) ); + } + else if( (pRelation->maType == CREATE_MSOFFICE_RELATIONSTYPE( "xlMacrosheet" )) || + (pRelation->maType == CREATE_MSOFFICE_RELATIONSTYPE( "xlIntlMacrosheet" )) ) + { + xFragment.set( new OoxWorksheetFragment( *this, aFragmentPath, xSheetSegment, SHEETTYPE_MACROSHEET, nCalcSheet ) ); + } + else if( pRelation->maType == CREATE_OFFICEDOC_RELATIONSTYPE( "dialogsheet" ) ) + { + xFragment.set( new OoxWorksheetFragment( *this, aFragmentPath, xSheetSegment, SHEETTYPE_DIALOGSHEET, nCalcSheet ) ); + } + + // insert the fragment into the map + OSL_ENSURE( xFragment.is(), "OoxWorkbookFragment::finalizeImport - unknown sheet type" ); + OSL_ENSURE( !xFragment.is() || xFragment->isValidSheet(), "OoxWorkbookFragment::finalizeImport - missing sheet in document" ); + if( xFragment.is() && xFragment->isValidSheet() ) + aSheetFragments.push_back( xFragment.get() ); + } + } + } + + // create all defined names and database ranges + getDefinedNames().finalizeImport(); + getTables().finalizeImport(); + + // load all worksheets + for( SheetFragmentVector::iterator aIt = aSheetFragments.begin(), aEnd = aSheetFragments.end(); aIt != aEnd; ++aIt ) + { + // import the sheet fragment + importOoxFragment( *aIt ); + // delete fragment object, will free all allocated sheet buffers + aIt->clear(); + } + + // open the VBA project storage + OUString aVbaFragmentPath = getFragmentPathFromFirstType( CREATE_MSOFFICE_RELATIONSTYPE( "vbaProject" ) ); + if( aVbaFragmentPath.getLength() > 0 ) + { + Reference< XInputStream > xInStrm = getBaseFilter().openInputStream( aVbaFragmentPath ); + if( xInStrm.is() ) + setVbaProjectStorage( StorageRef( new ::oox::ole::OleStorage( getGlobalFactory(), xInStrm, false ) ) ); + } + + // final conversions, e.g. calculation settings and view settings + finalizeWorkbookImport(); +} + +// private -------------------------------------------------------------------- + +void OoxWorkbookFragment::importExternalReference( const AttributeList& rAttribs ) +{ + if( ExternalLink* pExtLink = getExternalLinks().importExternalReference( rAttribs ).get() ) + importExternalLinkFragment( *pExtLink ); +} + +void OoxWorkbookFragment::importDefinedName( const AttributeList& rAttribs ) +{ + mxCurrName = getDefinedNames().importDefinedName( rAttribs ); +} + +void OoxWorkbookFragment::importPivotCache( const AttributeList& rAttribs ) +{ + sal_Int32 nCacheId = rAttribs.getInteger( XML_cacheId, -1 ); + OUString aRelId = rAttribs.getString( R_TOKEN( id ), OUString() ); + importPivotCacheDefFragment( aRelId, nCacheId ); +} + +void OoxWorkbookFragment::importExternalRef( RecordInputStream& rStrm ) +{ + if( ExternalLink* pExtLink = getExternalLinks().importExternalRef( rStrm ).get() ) + importExternalLinkFragment( *pExtLink ); +} + +void OoxWorkbookFragment::importPivotCache( RecordInputStream& rStrm ) +{ + sal_Int32 nCacheId = rStrm.readInt32(); + OUString aRelId = rStrm.readString(); + importPivotCacheDefFragment( aRelId, nCacheId ); +} + +void OoxWorkbookFragment::importExternalLinkFragment( ExternalLink& rExtLink ) +{ + OUString aFragmentPath = getFragmentPathFromRelId( rExtLink.getRelId() ); + if( aFragmentPath.getLength() > 0 ) + importOoxFragment( new OoxExternalLinkFragment( *this, aFragmentPath, rExtLink ) ); +} + +void OoxWorkbookFragment::importPivotCacheDefFragment( const OUString& rRelId, sal_Int32 nCacheId ) +{ + // pivot caches will be imported on demand, here we just store the fragment path in the buffer + getPivotCaches().registerPivotCacheFragment( nCacheId, getFragmentPathFromRelId( rRelId ) ); +} + +// ============================================================================ + +BiffWorkbookFragment::BiffWorkbookFragment( const WorkbookHelper& rHelper, const OUString& rStrmName ) : + BiffWorkbookFragmentBase( rHelper, rStrmName ) +{ +} + +bool BiffWorkbookFragment::importFragment() +{ + bool bRet = false; + + BiffFragmentType eFragment = startFragment( getBiff() ); + switch( eFragment ) + { + case BIFF_FRAGMENT_GLOBALS: + { + // import workbook globals fragment and create sheets in document + ISegmentProgressBarRef xGlobalsProgress = getProgressBar().createSegment( PROGRESS_LENGTH_GLOBALS ); + bRet = importGlobalsFragment( *xGlobalsProgress ); + // load sheet fragments (do not return false in bRet on missing/broken sheets) + WorksheetBuffer& rWorksheets = getWorksheets(); + bool bNextSheet = bRet; + for( sal_Int32 nWorksheet = 0, nWorksheetCount = rWorksheets.getWorksheetCount(); bNextSheet && (nWorksheet < nWorksheetCount); ++nWorksheet ) + { + // try to start a new sheet fragment + double fSegmentLength = getProgressBar().getFreeLength() / (nWorksheetCount - nWorksheet); + ISegmentProgressBarRef xSheetProgress = getProgressBar().createSegment( fSegmentLength ); + BiffFragmentType eSheetFragment = startFragment( getBiff(), rWorksheets.getBiffRecordHandle( nWorksheet ) ); + sal_Int16 nCalcSheet = rWorksheets.getCalcSheetIndex( nWorksheet ); + bNextSheet = importSheetFragment( *xSheetProgress, eSheetFragment, nCalcSheet ); + } + } + break; + + case BIFF_FRAGMENT_WORKSPACE: + { + bRet = importWorkspaceFragment(); + // sheets are embedded in workspace fragment, nothing to do here + } + break; + + case BIFF_FRAGMENT_WORKSHEET: + case BIFF_FRAGMENT_CHARTSHEET: + case BIFF_FRAGMENT_MACROSHEET: + { + /* Single sheet without globals + - #i62752# possible in all BIFF versions + - do not return false in bRet on missing/broken sheets. */ + getWorksheets().initializeSingleSheet(); + importSheetFragment( getProgressBar(), eFragment, 0 ); + // success, even if stream is broken + bRet = true; + } + break; + + default:; + } + + // final conversions, e.g. calculation settings and view settings + if( bRet ) + finalizeWorkbookImport(); + + return bRet; +} + +bool BiffWorkbookFragment::importWorkspaceFragment() +{ + // enable workbook mode, has not been set yet in BIFF4 workspace files + setIsWorkbookFile(); + + WorksheetBuffer& rWorksheets = getWorksheets(); + bool bRet = true; + + // import the workspace globals + ISegmentProgressBarRef xGlobalsProgress = getProgressBar().createSegment( PROGRESS_LENGTH_GLOBALS ); + bool bLoop = true; + while( bRet && bLoop && mrStrm.startNextRecord() && (mrStrm.getRecId() != BIFF_ID_EOF) ) + { + switch( mrStrm.getRecId() ) + { + case BIFF_ID_SHEET: rWorksheets.importSheet( mrStrm ); break; + case BIFF_ID_CODEPAGE: setCodePage( mrStrm.readuInt16() ); break; + case BIFF_ID_FILEPASS: bRet = getCodecHelper().importFilePass( mrStrm ); break; + case BIFF_ID_SHEETHEADER: mrStrm.rewindRecord(); bLoop = false; break; + } + } + xGlobalsProgress->setPosition( 1.0 ); + + // load sheet fragments (do not return false in bRet on missing/broken sheets) + bool bNextSheet = bRet; + for( sal_Int32 nWorksheet = 0, nWorksheetCount = rWorksheets.getWorksheetCount(); bNextSheet && (nWorksheet < nWorksheetCount); ++nWorksheet ) + { + // try to start a new sheet fragment (with leading SHEETHEADER record) + bNextSheet = mrStrm.startNextRecord() && (mrStrm.getRecId() == BIFF_ID_SHEETHEADER); + if( bNextSheet ) + { + double fSegmentLength = getProgressBar().getFreeLength() / (nWorksheetCount - nWorksheet); + ISegmentProgressBarRef xSheetProgress = getProgressBar().createSegment( fSegmentLength ); + /* Read current sheet name (sheet substreams may not be in the + same order as SHEET records are). */ + mrStrm.skip( 4 ); + OUString aSheetName = mrStrm.readByteStringUC( false, getTextEncoding() ); + sal_Int16 nCurrSheet = rWorksheets.getCalcSheetIndex( aSheetName ); + // load the sheet fragment records + BiffFragmentType eSheetFragment = startFragment( getBiff() ); + bNextSheet = importSheetFragment( *xSheetProgress, eSheetFragment, nCurrSheet ); + // do not return false in bRet on missing/broken sheets + } + } + + return bRet; +} + +bool BiffWorkbookFragment::importGlobalsFragment( ISegmentProgressBar& rProgressBar ) +{ + WorkbookSettings& rWorkbookSett = getWorkbookSettings(); + ViewSettings& rViewSett = getViewSettings(); + SharedStringsBuffer& rSharedStrings = getSharedStrings(); + StylesBuffer& rStyles = getStyles(); + WorksheetBuffer& rWorksheets = getWorksheets(); + PivotCacheBuffer& rPivotCaches = getPivotCaches(); + bool bHasVbaProject = false; + bool bEmptyVbaProject = false; + + // collect records that need to be loaded in a second pass + typedef ::std::vector< sal_Int64 > RecordHandleVec; + RecordHandleVec aExtLinkRecs; + + bool bRet = true; + bool bLoop = true; + while( bRet && bLoop && mrStrm.startNextRecord() ) + { + sal_uInt16 nRecId = mrStrm.getRecId(); + bool bExtLinkRec = false; + + /* #i56376# BIFF5-BIFF8: If an EOF record for globals is missing, + simulate it. The issue is about a document where the sheet fragment + starts directly after the EXTSST record, without terminating the + globals fragment with an EOF record. */ + if( isBofRecord() || (nRecId == BIFF_ID_EOF) ) + { + bLoop = false; + } + else switch( nRecId ) + { + // records in all BIFF versions + case BIFF_ID_CODEPAGE: setCodePage( mrStrm.readuInt16() ); break; + case BIFF_ID_DATEMODE: rWorkbookSett.importDateMode( mrStrm ); break; + case BIFF_ID_FILEPASS: bRet = getCodecHelper().importFilePass( mrStrm ); break; + case BIFF_ID_PRECISION: rWorkbookSett.importPrecision( mrStrm ); break; + case BIFF_ID_WINDOW1: rViewSett.importWindow1( mrStrm ); break; + + // BIFF specific records + default: switch( getBiff() ) + { + case BIFF2: switch( nRecId ) + { + case BIFF2_ID_DEFINEDNAME: bExtLinkRec = true; break; + case BIFF2_ID_EXTERNALNAME: bExtLinkRec = true; break; + case BIFF_ID_EXTERNSHEET: bExtLinkRec = true; break; + case BIFF2_ID_FONT: rStyles.importFont( mrStrm ); break; + case BIFF_ID_FONTCOLOR: rStyles.importFontColor( mrStrm ); break; + case BIFF2_ID_FORMAT: rStyles.importFormat( mrStrm ); break; + case BIFF2_ID_XF: rStyles.importXf( mrStrm ); break; + } + break; + + case BIFF3: switch( nRecId ) + { + case BIFF_ID_CRN: bExtLinkRec = true; break; + case BIFF3_ID_DEFINEDNAME: bExtLinkRec = true; break; + case BIFF3_ID_EXTERNALNAME: bExtLinkRec = true; break; + case BIFF_ID_EXTERNSHEET: bExtLinkRec = true; break; + case BIFF_ID_FILESHARING: rWorkbookSett.importFileSharing( mrStrm ); break; + case BIFF3_ID_FONT: rStyles.importFont( mrStrm ); break; + case BIFF2_ID_FORMAT: rStyles.importFormat( mrStrm ); break; + case BIFF_ID_HIDEOBJ: rWorkbookSett.importHideObj( mrStrm ); break; + case BIFF_ID_PALETTE: rStyles.importPalette( mrStrm ); break; + case BIFF_ID_STYLE: rStyles.importStyle( mrStrm ); break; + case BIFF_ID_XCT: bExtLinkRec = true; break; + case BIFF3_ID_XF: rStyles.importXf( mrStrm ); break; + } + break; + + case BIFF4: switch( nRecId ) + { + case BIFF_ID_CRN: bExtLinkRec = true; break; + case BIFF3_ID_DEFINEDNAME: bExtLinkRec = true; break; + case BIFF3_ID_EXTERNALNAME: bExtLinkRec = true; break; + case BIFF_ID_EXTERNSHEET: bExtLinkRec = true; break; + case BIFF_ID_FILESHARING: rWorkbookSett.importFileSharing( mrStrm ); break; + case BIFF3_ID_FONT: rStyles.importFont( mrStrm ); break; + case BIFF4_ID_FORMAT: rStyles.importFormat( mrStrm ); break; + case BIFF_ID_HIDEOBJ: rWorkbookSett.importHideObj( mrStrm ); break; + case BIFF_ID_PALETTE: rStyles.importPalette( mrStrm ); break; + case BIFF_ID_STYLE: rStyles.importStyle( mrStrm ); break; + case BIFF_ID_XCT: bExtLinkRec = true; break; + case BIFF4_ID_XF: rStyles.importXf( mrStrm ); break; + } + break; + + case BIFF5: switch( nRecId ) + { + case BIFF_ID_BOOKBOOL: rWorkbookSett.importBookBool( mrStrm ); break; + case BIFF_ID_CRN: bExtLinkRec = true; break; + case BIFF5_ID_DEFINEDNAME: bExtLinkRec = true; break; + case BIFF5_ID_EXTERNALNAME: bExtLinkRec = true; break; + case BIFF_ID_EXTERNSHEET: bExtLinkRec = true; break; + case BIFF_ID_FILESHARING: rWorkbookSett.importFileSharing( mrStrm ); break; + case BIFF5_ID_FONT: rStyles.importFont( mrStrm ); break; + case BIFF4_ID_FORMAT: rStyles.importFormat( mrStrm ); break; + case BIFF_ID_HIDEOBJ: rWorkbookSett.importHideObj( mrStrm ); break; + case BIFF_ID_OLESIZE: rViewSett.importOleSize( mrStrm ); break; + case BIFF_ID_PALETTE: rStyles.importPalette( mrStrm ); break; + case BIFF_ID_PIVOTCACHE: rPivotCaches.importPivotCacheRef( mrStrm ); break; + case BIFF_ID_SHEET: rWorksheets.importSheet( mrStrm ); break; + case BIFF_ID_STYLE: rStyles.importStyle( mrStrm ); break; + case BIFF_ID_XCT: bExtLinkRec = true; break; + case BIFF5_ID_XF: rStyles.importXf( mrStrm ); break; + } + break; + + case BIFF8: switch( nRecId ) + { + case BIFF_ID_BOOKBOOL: rWorkbookSett.importBookBool( mrStrm ); break; + case BIFF_ID_CODENAME: rWorkbookSett.importCodeName( mrStrm ); break; + case BIFF_ID_CRN: bExtLinkRec = true; break; + case BIFF5_ID_DEFINEDNAME: bExtLinkRec = true; break; + case BIFF_ID_EXTERNALBOOK: bExtLinkRec = true; break; + case BIFF5_ID_EXTERNALNAME: bExtLinkRec = true; break; + case BIFF_ID_EXTERNSHEET: bExtLinkRec = true; break; + case BIFF_ID_FILESHARING: rWorkbookSett.importFileSharing( mrStrm ); break; + case BIFF5_ID_FONT: rStyles.importFont( mrStrm ); break; + case BIFF4_ID_FORMAT: rStyles.importFormat( mrStrm ); break; + case BIFF_ID_HIDEOBJ: rWorkbookSett.importHideObj( mrStrm ); break; + case BIFF_ID_OLESIZE: rViewSett.importOleSize( mrStrm ); break; + case BIFF_ID_PALETTE: rStyles.importPalette( mrStrm ); break; + case BIFF_ID_PIVOTCACHE: rPivotCaches.importPivotCacheRef( mrStrm ); break; + case BIFF_ID_SHEET: rWorksheets.importSheet( mrStrm ); break; + case BIFF_ID_SST: rSharedStrings.importSst( mrStrm ); break; + case BIFF_ID_STYLE: rStyles.importStyle( mrStrm ); break; + case BIFF_ID_USESELFS: rWorkbookSett.importUsesElfs( mrStrm ); break; + case BIFF_ID_VBAPROJECT: bHasVbaProject = true; break; + case BIFF_ID_VBAPROJECTEMPTY: bEmptyVbaProject = true; break; + case BIFF_ID_XCT: bExtLinkRec = true; break; + case BIFF5_ID_XF: rStyles.importXf( mrStrm ); break; + } + break; + + case BIFF_UNKNOWN: break; + } + } + + if( bExtLinkRec ) + aExtLinkRecs.push_back( mrStrm.getRecHandle() ); + } + + // finalize global buffers + rProgressBar.setPosition( 0.5 ); + if( bRet ) + { + rSharedStrings.finalizeImport(); + rStyles.finalizeImport(); + } + + /* Import external link data (EXTERNSHEET, EXTERNALNAME, DEFINEDNAME) + which need existing internal sheets (SHEET records). The SHEET records + may follow the external links records in some BIFF versions. */ + if( bRet && !aExtLinkRecs.empty() ) + { + // remember current stream position (the EOF record) + sal_Int64 nEofHandle = mrStrm.getRecHandle(); + // this fragment class implements import of external link records + BiffExternalLinkFragment aLinkFragment( *this, true ); + // import all records by using their cached record handle + for( RecordHandleVec::const_iterator aIt = aExtLinkRecs.begin(), aEnd = aExtLinkRecs.end(); (aIt != aEnd) && mrStrm.startRecordByHandle( *aIt ); ++aIt ) + aLinkFragment.importRecord(); + // finalize global buffers + aLinkFragment.finalizeImport(); + // seek back to the EOF record of the workbook globals fragment + bRet = mrStrm.startRecordByHandle( nEofHandle ); + } + + // open the VBA project storage + if( bHasVbaProject && !bEmptyVbaProject ) + setVbaProjectStorage( getBaseFilter().openSubStorage( CREATE_OUSTRING( "_VBA_PROJECT_CUR" ), false ) ); + + // #i56376# missing EOF - rewind before worksheet BOF record (see above) + if( bRet && isBofRecord() ) + mrStrm.rewindRecord(); + + rProgressBar.setPosition( 1.0 ); + return bRet; +} + +bool BiffWorkbookFragment::importSheetFragment( ISegmentProgressBar& rProgressBar, BiffFragmentType eFragment, sal_Int16 nCalcSheet ) +{ + // no Calc sheet - skip the fragment + if( nCalcSheet < 0 ) + return skipFragment(); + + // find the sheet type for this fragment + WorksheetType eSheetType = SHEETTYPE_EMPTYSHEET; + switch( eFragment ) + { + case BIFF_FRAGMENT_WORKSHEET: eSheetType = SHEETTYPE_WORKSHEET; break; + case BIFF_FRAGMENT_CHARTSHEET: eSheetType = SHEETTYPE_CHARTSHEET; break; + case BIFF_FRAGMENT_MACROSHEET: eSheetType = SHEETTYPE_MACROSHEET; break; + case BIFF_FRAGMENT_MODULESHEET: eSheetType = SHEETTYPE_MODULESHEET; break; + case BIFF_FRAGMENT_EMPTYSHEET: eSheetType = SHEETTYPE_EMPTYSHEET; break; + default: return false; + } + + /* #i11183# Clear buffers that are used per-sheet, e.g. external links in + BIFF4W and BIFF5 files, or defined names in BIFF4W files. */ + createBuffersPerSheet( nCalcSheet ); + + // preprocess some records + switch( getBiff() ) + { + // load the workbook globals fragment records in BIFF2-BIFF4 + case BIFF2: + case BIFF3: + case BIFF4: + { + // remember current record to seek back below + sal_Int64 nRecHandle = mrStrm.getRecHandle(); + // import the global records + ISegmentProgressBarRef xGlobalsProgress = rProgressBar.createSegment( PROGRESS_LENGTH_GLOBALS ); + importGlobalsFragment( *xGlobalsProgress ); + // rewind stream to fragment BOF record + mrStrm.startRecordByHandle( nRecHandle ); + } + break; + + // load the external link records for this sheet in BIFF5 + case BIFF5: + { + // remember current record to seek back below + sal_Int64 nRecHandle = mrStrm.getRecHandle(); + // fragment implementing import of external link records + BiffExternalLinkFragment( *this, false ).importFragment(); + // rewind stream to fragment BOF record + mrStrm.startRecordByHandle( nRecHandle ); + } + break; + + case BIFF8: + break; + + case BIFF_UNKNOWN: + break; + } + + // create the worksheet fragment + ISegmentProgressBarRef xSheetProgress = rProgressBar.createSegment( rProgressBar.getFreeLength() ); + ::boost::shared_ptr< BiffWorksheetFragmentBase > xFragment; + switch( eSheetType ) + { + case SHEETTYPE_WORKSHEET: + case SHEETTYPE_MACROSHEET: + case SHEETTYPE_DIALOGSHEET: + xFragment.reset( new BiffWorksheetFragment( *this, xSheetProgress, eSheetType, nCalcSheet ) ); + break; + case SHEETTYPE_CHARTSHEET: + xFragment.reset( new BiffChartsheetFragment( *this, xSheetProgress, nCalcSheet ) ); + break; + case SHEETTYPE_MODULESHEET: + case SHEETTYPE_EMPTYSHEET: + xFragment.reset( new BiffSkipWorksheetFragment( *this, xSheetProgress, nCalcSheet ) ); + break; + } + // load the sheet fragment records + return xFragment->isValidSheet() && xFragment->importFragment(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/workbookhelper.cxx b/oox/source/xls/workbookhelper.cxx new file mode 100644 index 000000000000..ceba5690004c --- /dev/null +++ b/oox/source/xls/workbookhelper.cxx @@ -0,0 +1,1005 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/workbookhelper.hxx" +#include <osl/thread.h> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XNamedRange.hpp> +#include <com/sun/star/sheet/XNamedRanges.hpp> +#include <com/sun/star/sheet/XDatabaseRanges.hpp> +#include <com/sun/star/sheet/XExternalDocLinks.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include "properties.hxx" +#include "oox/helper/progressbar.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/drawingml/theme.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/biffcodec.hxx" +#include "oox/xls/defnamesbuffer.hxx" +#include "oox/xls/excelchartconverter.hxx" +#include "oox/xls/excelfilter.hxx" +#include "oox/xls/excelvbaproject.hxx" +#include "oox/xls/externallinkbuffer.hxx" +#include "oox/xls/formulaparser.hxx" +#include "oox/xls/pagesettings.hxx" +#include "oox/xls/pivotcachebuffer.hxx" +#include "oox/xls/pivottablebuffer.hxx" +#include "oox/xls/scenariobuffer.hxx" +#include "oox/xls/sharedstringsbuffer.hxx" +#include "oox/xls/stylesbuffer.hxx" +#include "oox/xls/tablebuffer.hxx" +#include "oox/xls/themebuffer.hxx" +#include "oox/xls/unitconverter.hxx" +#include "oox/xls/viewsettings.hxx" +#include "oox/xls/webquerybuffer.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/xls/worksheetbuffer.hxx" + +using ::rtl::OUString; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +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::container::XIndexAccess; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::container::XNameContainer; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::awt::XDevice; +using ::com::sun::star::document::XActionLockable; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::table::XCell; +using ::com::sun::star::table::XCellRange; +using ::com::sun::star::sheet::XSpreadsheetDocument; +using ::com::sun::star::sheet::XSpreadsheet; +using ::com::sun::star::sheet::XNamedRange; +using ::com::sun::star::sheet::XNamedRanges; +using ::com::sun::star::sheet::XDatabaseRanges; +using ::com::sun::star::sheet::XExternalDocLinks; +using ::com::sun::star::style::XStyle; +using ::com::sun::star::style::XStyleFamiliesSupplier; +using ::oox::core::BinaryFilterBase; +using ::oox::core::FilterBase; +using ::oox::core::FragmentHandler; +using ::oox::core::XmlFilterBase; +using ::oox::drawingml::Theme; + +namespace oox { +namespace xls { + +// ============================================================================ + +bool IgnoreCaseCompare::operator()( const OUString& rName1, const OUString& rName2 ) const +{ + // there is no wrapper in rtl::OUString, TODO: compare with collator + return ::rtl_ustr_compareIgnoreAsciiCase_WithLength( + rName1.getStr(), rName1.getLength(), rName2.getStr(), rName2.getLength() ) < 0; +} + +// ============================================================================ + +class WorkbookData +{ +public: + explicit WorkbookData( ExcelFilter& rFilter ); + explicit WorkbookData( ExcelBiffFilter& rFilter, BiffType eBiff ); + ~WorkbookData(); + + /** Returns true, if this helper refers to a valid document. */ + inline bool isValid() const { return mxDoc.is(); } + + // filter ----------------------------------------------------------------- + + /** Returns the base filter object (base class of all filters). */ + inline FilterBase& getBaseFilter() const { return mrBaseFilter; } + /** Returns the filter progress bar. */ + inline SegmentProgressBar& getProgressBar() const { return *mxProgressBar; } + /** Returns the file type of the current filter. */ + inline FilterType getFilterType() const { return meFilterType; } + /** Returns true, if the file is a multi-sheet document, or false if single-sheet. */ + inline bool isWorkbookFile() const { return mbWorkbook; } + /** Returns the index of the current sheet in the Calc document. */ + inline sal_Int16 getCurrentSheetIndex() const { return mnCurrSheet; } + /** Sets the index of the current sheet in the Calc document. */ + inline void setCurrentSheetIndex( sal_Int16 nSheet ) { mnCurrSheet = nSheet; } + /** Returns the VBA project storage. */ + inline StorageRef getVbaProjectStorage() const { return mxVbaPrjStrg; } + /** Sets the VBA project storage. */ + inline void setVbaProjectStorage( const StorageRef& rxVbaPrjStrg ) { mxVbaPrjStrg = rxVbaPrjStrg; } + + // document model --------------------------------------------------------- + + /** Returns a reference to the source/target spreadsheet document model. */ + inline Reference< XSpreadsheetDocument > getDocument() const { return mxDoc; } + /** Returns the reference device of the document. */ + Reference< XDevice > getReferenceDevice() const; + /** Returns the container for defined names from the Calc document. */ + Reference< XNamedRanges > getNamedRanges() const; + /** Returns the container for database ranges from the Calc document. */ + Reference< XDatabaseRanges > getDatabaseRanges() const; + /** Returns the container for external documents from the Calc document. */ + Reference< XExternalDocLinks > getExternalDocLinks() const; + /** Returns the container for DDE links from the Calc document. */ + Reference< XNameAccess > getDdeLinks() const; + /** Returns the cell or page styles container from the Calc document. */ + Reference< XNameContainer > getStyleFamily( bool bPageStyles ) const; + /** Returns the specified cell or page style from the Calc document. */ + Reference< XStyle > getStyleObject( const OUString& rStyleName, bool bPageStyle ) const; + /** Creates and returns a defined name on-the-fly in the Calc document. */ + Reference< XNamedRange > createNamedRangeObject( OUString& orName, sal_Int32 nNameFlags ) const; + /** Creates and returns a com.sun.star.style.Style object for cells or pages. */ + Reference< XStyle > createStyleObject( OUString& orStyleName, bool bPageStyle ) const; + + // buffers ---------------------------------------------------------------- + + /** Returns the global workbook settings object. */ + inline WorkbookSettings& getWorkbookSettings() const { return *mxWorkbookSettings; } + /** Returns the workbook and sheet view settings object. */ + inline ViewSettings& getViewSettings() const { return *mxViewSettings; } + /** Returns the worksheet buffer containing sheet names and properties. */ + inline WorksheetBuffer& getWorksheets() const { return *mxWorksheets; } + /** Returns the office theme object read from the theme substorage. */ + inline ThemeBuffer& getTheme() const { return *mxTheme; } + /** Returns all cell formatting objects read from the styles substream. */ + inline StylesBuffer& getStyles() const { return *mxStyles; } + /** Returns the shared strings read from the shared strings substream. */ + inline SharedStringsBuffer& getSharedStrings() const { return *mxSharedStrings; } + /** Returns the external links read from the external links substream. */ + inline ExternalLinkBuffer& getExternalLinks() const { return *mxExtLinks; } + /** Returns the defined names read from the workbook globals. */ + inline DefinedNamesBuffer& getDefinedNames() const { return *mxDefNames; } + /** Returns the tables collection (equivalent to Calc's database ranges). */ + inline TableBuffer& getTables() const { return *mxTables; } + /** Returns the scenarios collection. */ + inline ScenarioBuffer& getScenarios() const { return *mxScenarios; } + /** Returns the web queries. */ + inline WebQueryBuffer& getWebQueries() const { return *mxWebQueries; } + /** Returns the collection of pivot caches. */ + inline PivotCacheBuffer& getPivotCaches() const { return *mxPivotCaches; } + /** Returns the collection of pivot tables. */ + inline PivotTableBuffer& getPivotTables() { return *mxPivotTables; } + + // converters ------------------------------------------------------------- + + /** Returns the import formula parser. */ + inline FormulaParser& getFormulaParser() const { return *mxFmlaParser; } + /** Returns the measurement unit converter. */ + inline UnitConverter& getUnitConverter() const { return *mxUnitConverter; } + /** Returns the converter for string to cell address/range conversion. */ + inline AddressConverter& getAddressConverter() const { return *mxAddrConverter; } + /** Returns the chart object converter. */ + inline ExcelChartConverter& getChartConverter() const { return *mxChartConverter; } + /** Returns the page/print settings converter. */ + inline PageSettingsConverter& getPageSettingsConverter() const { return *mxPageSettConverter; } + + // OOX specific ----------------------------------------------------------- + + /** Returns the base OOX filter object. */ + inline XmlFilterBase& getOoxFilter() const { return *mpOoxFilter; } + + // BIFF specific ---------------------------------------------------------- + + /** Returns the base BIFF filter object. */ + inline BinaryFilterBase& getBiffFilter() const { return *mpBiffFilter; } + /** Returns the BIFF type in binary filter. */ + inline BiffType getBiff() const { return meBiff; } + /** Returns the text encoding used to import/export byte strings. */ + inline rtl_TextEncoding getTextEncoding() const { return meTextEnc; } + /** Sets the text encoding to import/export byte strings. */ + void setTextEncoding( rtl_TextEncoding eTextEnc ); + /** Sets code page read from a CODEPAGE record for byte string import. */ + void setCodePage( sal_uInt16 nCodePage ); + /** Sets text encoding from the default application font, if CODEPAGE record is missing. */ + void setAppFontEncoding( rtl_TextEncoding eAppFontEnc ); + /** Enables workbook file mode, used for BIFF4 workspace files. */ + void setIsWorkbookFile(); + /** Recreates global buffers that are used per sheet in specific BIFF versions. */ + void createBuffersPerSheet( sal_Int16 nSheet ); + /** Returns the codec helper that stores the encoder/decoder object. */ + inline BiffCodecHelper& getCodecHelper() { return *mxCodecHelper; } + +private: + /** Initializes some basic members and sets needed document properties. */ + void initialize( bool bWorkbookFile ); + /** Finalizes the filter process (sets some needed document properties). */ + void finalize(); + +private: + typedef ::std::auto_ptr< SegmentProgressBar > ProgressBarPtr; + typedef ::std::auto_ptr< WorkbookSettings > WorkbookSettPtr; + typedef ::std::auto_ptr< ViewSettings > ViewSettingsPtr; + typedef ::std::auto_ptr< WorksheetBuffer > WorksheetBfrPtr; + typedef ::boost::shared_ptr< ThemeBuffer > ThemeBfrRef; + typedef ::std::auto_ptr< StylesBuffer > StylesBfrPtr; + typedef ::std::auto_ptr< SharedStringsBuffer > SharedStrBfrPtr; + typedef ::std::auto_ptr< ExternalLinkBuffer > ExtLinkBfrPtr; + typedef ::std::auto_ptr< DefinedNamesBuffer > DefNamesBfrPtr; + typedef ::std::auto_ptr< TableBuffer > TableBfrPtr; + typedef ::std::auto_ptr< ScenarioBuffer > ScenarioBfrPtr; + typedef ::std::auto_ptr< WebQueryBuffer > WebQueryBfrPtr; + typedef ::std::auto_ptr< PivotCacheBuffer > PivotCacheBfrPtr; + typedef ::std::auto_ptr< PivotTableBuffer > PivotTableBfrPtr; + typedef ::std::auto_ptr< FormulaParser > FormulaParserPtr; + typedef ::std::auto_ptr< UnitConverter > UnitConvPtr; + typedef ::std::auto_ptr< AddressConverter > AddressConvPtr; + typedef ::std::auto_ptr< ExcelChartConverter > ExcelChartConvPtr; + typedef ::std::auto_ptr< PageSettingsConverter > PageSettConvPtr; + typedef ::std::auto_ptr< BiffCodecHelper > BiffCodecHelperPtr; + + OUString maCellStyles; /// Style family name for cell styles. + OUString maPageStyles; /// Style family name for page styles. + OUString maCellStyleServ; /// Service name for a cell style. + OUString maPageStyleServ; /// Service name for a page style. + Reference< XSpreadsheetDocument > mxDoc; /// Document model. + FilterBase& mrBaseFilter; /// Base filter object. + ExcelFilterBase& mrExcelBase; /// Base object for registration of this structure. + FilterType meFilterType; /// File type of the filter. + ProgressBarPtr mxProgressBar; /// The progress bar. + StorageRef mxVbaPrjStrg; /// Storage containing the VBA project. + sal_Int16 mnCurrSheet; /// Current sheet index in Calc dcument. + bool mbWorkbook; /// True = multi-sheet file. + + // buffers + WorkbookSettPtr mxWorkbookSettings; /// Global workbook settings. + ViewSettingsPtr mxViewSettings; /// Workbook and sheet view settings. + WorksheetBfrPtr mxWorksheets; /// Sheet info buffer. + ThemeBfrRef mxTheme; /// Formatting theme from theme substream. + StylesBfrPtr mxStyles; /// All cell style objects from styles substream. + SharedStrBfrPtr mxSharedStrings; /// All strings from shared strings substream. + ExtLinkBfrPtr mxExtLinks; /// All external links. + DefNamesBfrPtr mxDefNames; /// All defined names. + TableBfrPtr mxTables; /// All tables (database ranges). + ScenarioBfrPtr mxScenarios; /// All scenarios. + WebQueryBfrPtr mxWebQueries; /// Web queries buffer. + PivotCacheBfrPtr mxPivotCaches; /// All pivot caches in the document. + PivotTableBfrPtr mxPivotTables; /// All pivot tables in the document. + + // converters + FormulaParserPtr mxFmlaParser; /// Import formula parser. + UnitConvPtr mxUnitConverter; /// General unit converter. + AddressConvPtr mxAddrConverter; /// Cell address and cell range address converter. + ExcelChartConvPtr mxChartConverter; /// Chart object converter. + PageSettConvPtr mxPageSettConverter; /// Page/print settings converter. + + // OOX specific + XmlFilterBase* mpOoxFilter; /// Base OOX filter object. + + // BIFF specific + BinaryFilterBase* mpBiffFilter; /// Base BIFF filter object. + BiffCodecHelperPtr mxCodecHelper; /// Encoder/decoder helper. + BiffType meBiff; /// BIFF version for BIFF import/export. + rtl_TextEncoding meTextEnc; /// BIFF byte string text encoding. + bool mbHasCodePage; /// True = CODEPAGE record exists in imported stream. +}; + +// ---------------------------------------------------------------------------- + +WorkbookData::WorkbookData( ExcelFilter& rFilter ) : + mrBaseFilter( rFilter ), + mrExcelBase( rFilter ), + meFilterType( FILTER_OOX ), + mpOoxFilter( &rFilter ), + mpBiffFilter( 0 ), + meBiff( BIFF_UNKNOWN ) +{ + // register at the filter, needed for virtual callbacks (even during construction) + mrExcelBase.registerWorkbookData( *this ); + initialize( true ); +} + +WorkbookData::WorkbookData( ExcelBiffFilter& rFilter, BiffType eBiff ) : + mrBaseFilter( rFilter ), + mrExcelBase( rFilter ), + meFilterType( FILTER_BIFF ), + mpOoxFilter( 0 ), + mpBiffFilter( &rFilter ), + meBiff( eBiff ) +{ + // register at the filter, needed for virtual callbacks (even during construction) + mrExcelBase.registerWorkbookData( *this ); + initialize( eBiff >= BIFF5 ); +} + +WorkbookData::~WorkbookData() +{ + finalize(); + mrExcelBase.unregisterWorkbookData(); +} + +// document model ------------------------------------------------------------- + +Reference< XDevice > WorkbookData::getReferenceDevice() const +{ + PropertySet aPropSet( mxDoc ); + Reference< XDevice > xDevice; + aPropSet.getProperty( xDevice, PROP_ReferenceDevice ); + return xDevice; +} + +Reference< XNamedRanges > WorkbookData::getNamedRanges() const +{ + PropertySet aPropSet( mxDoc ); + Reference< XNamedRanges > xNamedRanges; + aPropSet.getProperty( xNamedRanges, PROP_NamedRanges ); + return xNamedRanges; +} + +Reference< XDatabaseRanges > WorkbookData::getDatabaseRanges() const +{ + PropertySet aPropSet( mxDoc ); + Reference< XDatabaseRanges > xDatabaseRanges; + aPropSet.getProperty( xDatabaseRanges, PROP_DatabaseRanges ); + return xDatabaseRanges; +} + +Reference< XExternalDocLinks > WorkbookData::getExternalDocLinks() const +{ + PropertySet aPropSet( mxDoc ); + Reference< XExternalDocLinks > xDocLinks; + aPropSet.getProperty( xDocLinks, PROP_ExternalDocLinks ); + return xDocLinks; +} + +Reference< XNameAccess > WorkbookData::getDdeLinks() const +{ + PropertySet aPropSet( mxDoc ); + Reference< XNameAccess > xDdeLinks; + aPropSet.getProperty( xDdeLinks, PROP_DDELinks ); + return xDdeLinks; +} + +Reference< XNameContainer > WorkbookData::getStyleFamily( bool bPageStyles ) const +{ + Reference< XNameContainer > xStylesNC; + try + { + Reference< XStyleFamiliesSupplier > xFamiliesSup( mxDoc, UNO_QUERY_THROW ); + Reference< XNameAccess > xFamiliesNA( xFamiliesSup->getStyleFamilies(), UNO_QUERY_THROW ); + xStylesNC.set( xFamiliesNA->getByName( bPageStyles ? maPageStyles : maCellStyles ), UNO_QUERY ); + } + catch( Exception& ) + { + } + OSL_ENSURE( xStylesNC.is(), "WorkbookData::getStyleFamily - cannot access style family" ); + return xStylesNC; +} + +Reference< XStyle > WorkbookData::getStyleObject( const OUString& rStyleName, bool bPageStyle ) const +{ + Reference< XStyle > xStyle; + try + { + Reference< XNameContainer > xStylesNC( getStyleFamily( bPageStyle ), UNO_SET_THROW ); + xStyle.set( xStylesNC->getByName( rStyleName ), UNO_QUERY ); + } + catch( Exception& ) + { + } + OSL_ENSURE( xStyle.is(), "WorkbookData::getStyleObject - cannot access style object" ); + return xStyle; +} + +Reference< XNamedRange > WorkbookData::createNamedRangeObject( OUString& orName, sal_Int32 nNameFlags ) const +{ + // find an unused name + Reference< XNamedRanges > xNamedRanges = getNamedRanges(); + Reference< XNameAccess > xNameAccess( xNamedRanges, UNO_QUERY ); + if( xNameAccess.is() ) + orName = ContainerHelper::getUnusedName( xNameAccess, orName, '_' ); + + // create the name and insert it into the Calc document + Reference< XNamedRange > xNamedRange; + if( xNamedRanges.is() && (orName.getLength() > 0) ) try + { + xNamedRanges->addNewByName( orName, OUString(), CellAddress( 0, 0, 0 ), nNameFlags ); + xNamedRange.set( xNamedRanges->getByName( orName ), UNO_QUERY ); + } + catch( Exception& ) + { + } + OSL_ENSURE( xNamedRange.is(), "WorkbookData::createNamedRangeObject - cannot create defined name" ); + return xNamedRange; +} + +Reference< XStyle > WorkbookData::createStyleObject( OUString& orStyleName, bool bPageStyle ) const +{ + Reference< XStyle > xStyle; + try + { + Reference< XNameContainer > xStylesNC( getStyleFamily( bPageStyle ), UNO_SET_THROW ); + xStyle.set( mrBaseFilter.getModelFactory()->createInstance( bPageStyle ? maPageStyleServ : maCellStyleServ ), UNO_QUERY_THROW ); + orStyleName = ContainerHelper::insertByUnusedName( xStylesNC, orStyleName, ' ', Any( xStyle ), false ); + } + catch( Exception& ) + { + } + OSL_ENSURE( xStyle.is(), "WorkbookData::createStyleObject - cannot create style" ); + return xStyle; +} + +// BIFF specific -------------------------------------------------------------- + +void WorkbookData::setTextEncoding( rtl_TextEncoding eTextEnc ) +{ + if( eTextEnc != RTL_TEXTENCODING_DONTKNOW ) + meTextEnc = eTextEnc; +} + +void WorkbookData::setCodePage( sal_uInt16 nCodePage ) +{ + setTextEncoding( BiffHelper::calcTextEncodingFromCodePage( nCodePage ) ); + mbHasCodePage = true; +} + +void WorkbookData::setAppFontEncoding( rtl_TextEncoding eAppFontEnc ) +{ + if( !mbHasCodePage ) + setTextEncoding( eAppFontEnc ); +} + +void WorkbookData::setIsWorkbookFile() +{ + OSL_ENSURE( meBiff == BIFF4, "WorkbookData::setIsWorkbookFile - invalid call" ); + mbWorkbook = true; +} + +void WorkbookData::createBuffersPerSheet( sal_Int16 nSheet ) +{ + // set mnCurrSheet to enable usage of WorkbookHelper::getCurrentSheetIndex() + mnCurrSheet = nSheet; + switch( meBiff ) + { + case BIFF2: + case BIFF3: + OSL_ENSURE( mnCurrSheet == 0, "WorkbookData::createBuffersPerSheet - unexpected sheet index" ); + mxDefNames->setLocalCalcSheet( mnCurrSheet ); + break; + + case BIFF4: + OSL_ENSURE( mbWorkbook || (mnCurrSheet == 0), "WorkbookData::createBuffersPerSheet - unexpected sheet index" ); + // #i11183# sheets in BIFF4W files have own styles and names + if( mbWorkbook && (mnCurrSheet > 0) ) + { + mxStyles.reset( new StylesBuffer( *this ) ); + mxDefNames.reset( new DefinedNamesBuffer( *this ) ); + mxExtLinks.reset( new ExternalLinkBuffer( *this ) ); + } + mxDefNames->setLocalCalcSheet( mnCurrSheet ); + break; + + case BIFF5: + // BIFF5 stores external references per sheet + mxExtLinks.reset( new ExternalLinkBuffer( *this ) ); + break; + + case BIFF8: + break; + + case BIFF_UNKNOWN: + break; + } + mnCurrSheet = -1; +} + +// private -------------------------------------------------------------------- + +void WorkbookData::initialize( bool bWorkbookFile ) +{ + maCellStyles = CREATE_OUSTRING( "CellStyles" ); + maPageStyles = CREATE_OUSTRING( "PageStyles" ); + maCellStyleServ = CREATE_OUSTRING( "com.sun.star.style.CellStyle" ); + maPageStyleServ = CREATE_OUSTRING( "com.sun.star.style.PageStyle" ); + mnCurrSheet = -1; + mbWorkbook = bWorkbookFile; + meTextEnc = osl_getThreadTextEncoding(); + mbHasCodePage = false; + + // the spreadsheet document + mxDoc.set( mrBaseFilter.getModel(), UNO_QUERY ); + OSL_ENSURE( mxDoc.is(), "WorkbookData::initialize - no spreadsheet document" ); + + mxWorkbookSettings.reset( new WorkbookSettings( *this ) ); + mxViewSettings.reset( new ViewSettings( *this ) ); + mxWorksheets.reset( new WorksheetBuffer( *this ) ); + mxTheme.reset( new ThemeBuffer( *this ) ); + mxStyles.reset( new StylesBuffer( *this ) ); + mxSharedStrings.reset( new SharedStringsBuffer( *this ) ); + mxExtLinks.reset( new ExternalLinkBuffer( *this ) ); + mxDefNames.reset( new DefinedNamesBuffer( *this ) ); + mxTables.reset( new TableBuffer( *this ) ); + mxScenarios.reset( new ScenarioBuffer( *this ) ); + mxWebQueries.reset( new WebQueryBuffer( *this ) ); + mxPivotCaches.reset( new PivotCacheBuffer( *this ) ); + mxPivotTables.reset( new PivotTableBuffer( *this ) ); + + mxUnitConverter.reset( new UnitConverter( *this ) ); + mxAddrConverter.reset( new AddressConverter( *this ) ); + mxChartConverter.reset( new ExcelChartConverter( *this ) ); + mxPageSettConverter.reset( new PageSettingsConverter( *this ) ); + + // set some document properties needed during import + if( mrBaseFilter.isImportFilter() ) + { + PropertySet aPropSet( mxDoc ); + // enable editing read-only documents (e.g. from read-only files) + aPropSet.setProperty( PROP_IsChangeReadOnlyEnabled, true ); + // #i76026# disable Undo while loading the document + aPropSet.setProperty( PROP_IsUndoEnabled, false ); + // #i79826# disable calculating automatic row height while loading the document + aPropSet.setProperty( PROP_IsAdjustHeightEnabled, false ); + // disable automatic update of linked sheets and DDE links + aPropSet.setProperty( PROP_IsExecuteLinkEnabled, false ); + // #i79890# disable automatic update of defined names + Reference< XActionLockable > xLockable( getNamedRanges(), UNO_QUERY ); + if( xLockable.is() ) + xLockable->addActionLock(); + + //! TODO: localize progress bar text + mxProgressBar.reset( new SegmentProgressBar( mrBaseFilter.getStatusIndicator(), CREATE_OUSTRING( "Loading..." ) ) ); + mxFmlaParser.reset( new FormulaParser( *this ) ); + } + else if( mrBaseFilter.isExportFilter() ) + { + //! TODO: localize progress bar text + mxProgressBar.reset( new SegmentProgressBar( mrBaseFilter.getStatusIndicator(), CREATE_OUSTRING( "Saving..." ) ) ); + } + + // filter specific + switch( getFilterType() ) + { + case FILTER_BIFF: + mxCodecHelper.reset( new BiffCodecHelper( *this ) ); + break; + + case FILTER_OOX: + break; + + case FILTER_UNKNOWN: + break; + } +} + +void WorkbookData::finalize() +{ + // set some document properties needed after import + if( mrBaseFilter.isImportFilter() ) + { + PropertySet aPropSet( mxDoc ); + // #i74668# do not insert default sheets + aPropSet.setProperty( PROP_IsLoaded, true ); + // #i79890# enable automatic update of defined names (before IsAdjustHeightEnabled!) + Reference< XActionLockable > xLockable( getNamedRanges(), UNO_QUERY ); + if( xLockable.is() ) + xLockable->removeActionLock(); + // enable automatic update of linked sheets and DDE links + aPropSet.setProperty( PROP_IsExecuteLinkEnabled, true ); + // #i79826# enable updating automatic row height after loading the document + aPropSet.setProperty( PROP_IsAdjustHeightEnabled, true ); + // #i76026# enable Undo after loading the document + aPropSet.setProperty( PROP_IsUndoEnabled, true ); + // disable editing read-only documents (e.g. from read-only files) + aPropSet.setProperty( PROP_IsChangeReadOnlyEnabled, false ); + } +} + +// ============================================================================ + +WorkbookHelper::~WorkbookHelper() +{ +} + +// filter --------------------------------------------------------------------- + +FilterBase& WorkbookHelper::getBaseFilter() const +{ + return mrBookData.getBaseFilter(); +} + +Reference< XMultiServiceFactory > WorkbookHelper::getGlobalFactory() const +{ + return mrBookData.getBaseFilter().getGlobalFactory(); +} + +FilterType WorkbookHelper::getFilterType() const +{ + return mrBookData.getFilterType(); +} + +SegmentProgressBar& WorkbookHelper::getProgressBar() const +{ + return mrBookData.getProgressBar(); +} + +bool WorkbookHelper::isWorkbookFile() const +{ + return mrBookData.isWorkbookFile(); +} + +sal_Int16 WorkbookHelper::getCurrentSheetIndex() const +{ + return mrBookData.getCurrentSheetIndex(); +} + +void WorkbookHelper::setCurrentSheetIndex( sal_Int16 nSheet ) +{ + mrBookData.setCurrentSheetIndex( nSheet ); +} + +void WorkbookHelper::setVbaProjectStorage( const StorageRef& rxVbaPrjStrg ) +{ + mrBookData.setVbaProjectStorage( rxVbaPrjStrg ); +} + +void WorkbookHelper::finalizeWorkbookImport() +{ + // workbook settings, document and sheet view settings + mrBookData.getWorkbookSettings().finalizeImport(); + mrBookData.getViewSettings().finalizeImport(); + + /* Insert all pivot tables. Must be done after loading all sheets, because + data pilots expect existing source data on creation. */ + mrBookData.getPivotTables().finalizeImport(); + + /* Insert scenarios after all sheet processing is done, because new hidden + sheets are created for scenarios which would confuse code that relies + on certain sheet indexes. Must be done after pivot tables too. */ + mrBookData.getScenarios().finalizeImport(); + + /* Set 'Default' page style to automatic page numbering (default is manual + number 1). Otherwise hidden sheets (e.g. for scenarios) which have + 'Default' page style will break automatic page numbering for following + sheets. Automatic numbering is set by passing the value 0. */ + PropertySet aDefPageStyle( getStyleObject( CREATE_OUSTRING( "Default" ), true ) ); + aDefPageStyle.setProperty< sal_Int16 >( PROP_FirstPageNumber, 0 ); + + /* Import the VBA project (after finalizing workbook settings which + contains the workbook code name). */ + StorageRef xVbaPrjStrg = mrBookData.getVbaProjectStorage(); + if( xVbaPrjStrg.get() && xVbaPrjStrg->isStorage() ) + { + ExcelVbaProject aVbaProject( getGlobalFactory(), getDocument() ); + aVbaProject.importVbaProject( *xVbaPrjStrg, getBaseFilter().getGraphicHelper() ); + } +} + +// document model ------------------------------------------------------------- + +Reference< XSpreadsheetDocument > WorkbookHelper::getDocument() const +{ + return mrBookData.getDocument(); +} + +Reference< XMultiServiceFactory > WorkbookHelper::getDocumentFactory() const +{ + return mrBookData.getBaseFilter().getModelFactory(); +} + +Reference< XDevice > WorkbookHelper::getReferenceDevice() const +{ + return mrBookData.getReferenceDevice(); +} + +Reference< XNamedRanges > WorkbookHelper::getNamedRanges() const +{ + return mrBookData.getNamedRanges(); +} + +Reference< XDatabaseRanges > WorkbookHelper::getDatabaseRanges() const +{ + return mrBookData.getDatabaseRanges(); +} + +Reference< XExternalDocLinks > WorkbookHelper::getExternalDocLinks() const +{ + return mrBookData.getExternalDocLinks(); +} + +Reference< XNameAccess > WorkbookHelper::getDdeLinks() const +{ + return mrBookData.getDdeLinks(); +} + +Reference< XSpreadsheet > WorkbookHelper::getSheetFromDoc( sal_Int16 nSheet ) const +{ + Reference< XSpreadsheet > xSheet; + try + { + Reference< XIndexAccess > xSheetsIA( getDocument()->getSheets(), UNO_QUERY_THROW ); + xSheet.set( xSheetsIA->getByIndex( nSheet ), UNO_QUERY_THROW ); + } + catch( Exception& ) + { + } + return xSheet; +} + +Reference< XSpreadsheet > WorkbookHelper::getSheetFromDoc( const OUString& rSheet ) const +{ + Reference< XSpreadsheet > xSheet; + try + { + Reference< XNameAccess > xSheetsNA( getDocument()->getSheets(), UNO_QUERY_THROW ); + xSheet.set( xSheetsNA->getByName( rSheet ), UNO_QUERY ); + } + catch( Exception& ) + { + } + return xSheet; +} + +Reference< XCell > WorkbookHelper::getCellFromDoc( const CellAddress& rAddress ) const +{ + Reference< XCell > xCell; + try + { + Reference< XSpreadsheet > xSheet( getSheetFromDoc( rAddress.Sheet ), UNO_SET_THROW ); + xCell = xSheet->getCellByPosition( rAddress.Column, rAddress.Row ); + } + catch( Exception& ) + { + } + return xCell; +} + +Reference< XCellRange > WorkbookHelper::getCellRangeFromDoc( const CellRangeAddress& rRange ) const +{ + Reference< XCellRange > xRange; + try + { + Reference< XSpreadsheet > xSheet( getSheetFromDoc( rRange.Sheet ), UNO_SET_THROW ); + xRange = xSheet->getCellRangeByPosition( rRange.StartColumn, rRange.StartRow, rRange.EndColumn, rRange.EndRow ); + } + catch( Exception& ) + { + } + return xRange; +} + +Reference< XNameContainer > WorkbookHelper::getStyleFamily( bool bPageStyles ) const +{ + return mrBookData.getStyleFamily( bPageStyles ); +} + +Reference< XStyle > WorkbookHelper::getStyleObject( const OUString& rStyleName, bool bPageStyle ) const +{ + return mrBookData.getStyleObject( rStyleName, bPageStyle ); +} + +Reference< XNamedRange > WorkbookHelper::createNamedRangeObject( OUString& orName, sal_Int32 nNameFlags ) const +{ + return mrBookData.createNamedRangeObject( orName, nNameFlags ); +} + +Reference< XStyle > WorkbookHelper::createStyleObject( OUString& orStyleName, bool bPageStyle ) const +{ + return mrBookData.createStyleObject( orStyleName, bPageStyle ); +} + +// buffers -------------------------------------------------------------------- + +WorkbookSettings& WorkbookHelper::getWorkbookSettings() const +{ + return mrBookData.getWorkbookSettings(); +} + +ViewSettings& WorkbookHelper::getViewSettings() const +{ + return mrBookData.getViewSettings(); +} + +WorksheetBuffer& WorkbookHelper::getWorksheets() const +{ + return mrBookData.getWorksheets(); +} + +ThemeBuffer& WorkbookHelper::getTheme() const +{ + return mrBookData.getTheme(); +} + +StylesBuffer& WorkbookHelper::getStyles() const +{ + return mrBookData.getStyles(); +} + +SharedStringsBuffer& WorkbookHelper::getSharedStrings() const +{ + return mrBookData.getSharedStrings(); +} + +ExternalLinkBuffer& WorkbookHelper::getExternalLinks() const +{ + return mrBookData.getExternalLinks(); +} + +DefinedNamesBuffer& WorkbookHelper::getDefinedNames() const +{ + return mrBookData.getDefinedNames(); +} + +TableBuffer& WorkbookHelper::getTables() const +{ + return mrBookData.getTables(); +} + +ScenarioBuffer& WorkbookHelper::getScenarios() const +{ + return mrBookData.getScenarios(); +} + +WebQueryBuffer& WorkbookHelper::getWebQueries() const +{ + return mrBookData.getWebQueries(); +} + +PivotCacheBuffer& WorkbookHelper::getPivotCaches() const +{ + return mrBookData.getPivotCaches(); +} + +PivotTableBuffer& WorkbookHelper::getPivotTables() const +{ + return mrBookData.getPivotTables(); +} + +// converters ----------------------------------------------------------------- + +FormulaParser& WorkbookHelper::getFormulaParser() const +{ + return mrBookData.getFormulaParser(); +} + +UnitConverter& WorkbookHelper::getUnitConverter() const +{ + return mrBookData.getUnitConverter(); +} + +AddressConverter& WorkbookHelper::getAddressConverter() const +{ + return mrBookData.getAddressConverter(); +} + +ExcelChartConverter& WorkbookHelper::getChartConverter() const +{ + return mrBookData.getChartConverter(); +} + +PageSettingsConverter& WorkbookHelper::getPageSettingsConverter() const +{ + return mrBookData.getPageSettingsConverter(); +} + +// OOX specific --------------------------------------------------------------- + +XmlFilterBase& WorkbookHelper::getOoxFilter() const +{ + OSL_ENSURE( mrBookData.getFilterType() == FILTER_OOX, "WorkbookHelper::getOoxFilter - invalid call" ); + return mrBookData.getOoxFilter(); +} + +bool WorkbookHelper::importOoxFragment( const ::rtl::Reference< FragmentHandler >& rxHandler ) +{ + return getOoxFilter().importFragment( rxHandler ); +} + +// BIFF specific -------------------------------------------------------------- + +BinaryFilterBase& WorkbookHelper::getBiffFilter() const +{ + OSL_ENSURE( mrBookData.getFilterType() == FILTER_BIFF, "WorkbookHelper::getBiffFilter - invalid call" ); + return mrBookData.getBiffFilter(); +} + +BiffType WorkbookHelper::getBiff() const +{ + return mrBookData.getBiff(); +} + +rtl_TextEncoding WorkbookHelper::getTextEncoding() const +{ + return mrBookData.getTextEncoding(); +} + +void WorkbookHelper::setTextEncoding( rtl_TextEncoding eTextEnc ) +{ + mrBookData.setTextEncoding( eTextEnc ); +} + +void WorkbookHelper::setCodePage( sal_uInt16 nCodePage ) +{ + mrBookData.setCodePage( nCodePage ); +} + +void WorkbookHelper::setAppFontEncoding( rtl_TextEncoding eAppFontEnc ) +{ + mrBookData.setAppFontEncoding( eAppFontEnc ); +} + +void WorkbookHelper::setIsWorkbookFile() +{ + mrBookData.setIsWorkbookFile(); +} + +void WorkbookHelper::createBuffersPerSheet( sal_Int16 nSheet ) +{ + mrBookData.createBuffersPerSheet( nSheet ); +} + +BiffCodecHelper& WorkbookHelper::getCodecHelper() const +{ + return mrBookData.getCodecHelper(); +} + +// ============================================================================ + +namespace prv { + +WorkbookDataOwner::WorkbookDataOwner( WorkbookDataRef xBookData ) : + mxBookData( xBookData ) +{ +} + +WorkbookDataOwner::~WorkbookDataOwner() +{ +} + +} // namespace prv + +// ---------------------------------------------------------------------------- + +WorkbookHelperRoot::WorkbookHelperRoot( ExcelFilter& rFilter ) : + prv::WorkbookDataOwner( prv::WorkbookDataRef( new WorkbookData( rFilter ) ) ), + WorkbookHelper( *mxBookData ) +{ +} + +WorkbookHelperRoot::WorkbookHelperRoot( ExcelBiffFilter& rFilter, BiffType eBiff ) : + prv::WorkbookDataOwner( prv::WorkbookDataRef( new WorkbookData( rFilter, eBiff ) ) ), + WorkbookHelper( *mxBookData ) +{ +} + +bool WorkbookHelperRoot::isValid() const +{ + return mxBookData->isValid(); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/workbooksettings.cxx b/oox/source/xls/workbooksettings.cxx new file mode 100644 index 000000000000..51d60ebafe3b --- /dev/null +++ b/oox/source/xls/workbooksettings.cxx @@ -0,0 +1,370 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/workbooksettings.hxx" +#include <com/sun/star/sheet/XCalculatable.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <comphelper/mediadescriptor.hxx> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/unitconverter.hxx" + +using ::rtl::OUString; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::sheet::XCalculatable; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::util::Date; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::comphelper::MediaDescriptor; +using ::oox::core::CodecHelper; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt32 OOBIN_WORKBOOKPR_DATE1904 = 0x00000001; +const sal_uInt32 OOBIN_WORKBOOKPR_STRIPEXT = 0x00000080; + +const sal_uInt16 OOBIN_CALCPR_A1 = 0x0002; +const sal_uInt16 OOBIN_CALCPR_ITERATE = 0x0004; +const sal_uInt16 OOBIN_CALCPR_FULLPRECISION = 0x0008; +const sal_uInt16 OOBIN_CALCPR_CALCCOMPLETED = 0x0010; +const sal_uInt16 OOBIN_CALCPR_CALCONSAVE = 0x0020; +const sal_uInt16 OOBIN_CALCPR_CONCURRENT = 0x0040; +const sal_uInt16 OOBIN_CALCPR_MANUALPROC = 0x0080; + +// no predefined constants for show objects mode +const sal_Int16 API_SHOWMODE_SHOW = 0; /// Show drawing objects. +const sal_Int16 API_SHOWMODE_HIDE = 1; /// Hide drawing objects. +const sal_Int16 API_SHOWMODE_PLACEHOLDER = 2; /// Show placeholders for drawing objects. + +} // namespace + +// ============================================================================ + +FileSharingModel::FileSharingModel() : + mnPasswordHash( 0 ), + mbRecommendReadOnly( false ) +{ +} + +// ============================================================================ + +WorkbookSettingsModel::WorkbookSettingsModel() : + mnShowObjectMode( XML_all ), + mnUpdateLinksMode( XML_userSet ), + mnDefaultThemeVer( -1 ), + mbDateMode1904( false ), + mbSaveExtLinkValues( true ) +{ +} + +void WorkbookSettingsModel::setBinObjectMode( sal_uInt16 nObjMode ) +{ + static const sal_Int32 spnObjModes[] = { XML_all, XML_placeholders, XML_none }; + mnShowObjectMode = STATIC_ARRAY_SELECT( spnObjModes, nObjMode, XML_all ); +} + +// ============================================================================ + +CalcSettingsModel::CalcSettingsModel() : + mfIterateDelta( 0.001 ), + mnCalcId( -1 ), + mnRefMode( XML_A1 ), + mnCalcMode( XML_auto ), + mnIterateCount( 100 ), + mnProcCount( -1 ), + mbCalcOnSave( true ), + mbCalcCompleted( true ), + mbFullPrecision( true ), + mbIterate( false ), + mbConcurrent( true ), + mbUseNlr( false ) +{ +} + +// ============================================================================ + +WorkbookSettings::WorkbookSettings( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +void WorkbookSettings::importFileSharing( const AttributeList& rAttribs ) +{ + maFileSharing.maUserName = rAttribs.getXString( XML_userName, OUString() ); + maFileSharing.mnPasswordHash = CodecHelper::getPasswordHash( rAttribs, XML_reservationPassword ); + maFileSharing.mbRecommendReadOnly = rAttribs.getBool( XML_readOnlyRecommended, false ); +} + +void WorkbookSettings::importWorkbookPr( const AttributeList& rAttribs ) +{ + maBookSettings.maCodeName = rAttribs.getString( XML_codeName, OUString() ); + maBookSettings.mnShowObjectMode = rAttribs.getToken( XML_showObjects, XML_all ); + maBookSettings.mnUpdateLinksMode = rAttribs.getToken( XML_updateLinks, XML_userSet ); + maBookSettings.mnDefaultThemeVer = rAttribs.getInteger( XML_defaultThemeVersion, -1 ); + maBookSettings.mbSaveExtLinkValues = rAttribs.getBool( XML_saveExternalLinkValues, true ); + setDateMode( rAttribs.getBool( XML_date1904, false ) ); +} + +void WorkbookSettings::importCalcPr( const AttributeList& rAttribs ) +{ + maCalcSettings.mfIterateDelta = rAttribs.getDouble( XML_iterateDelta, 0.0001 ); + maCalcSettings.mnCalcId = rAttribs.getInteger( XML_calcId, -1 ); + maCalcSettings.mnRefMode = rAttribs.getToken( XML_refMode, XML_A1 ); + maCalcSettings.mnCalcMode = rAttribs.getToken( XML_calcMode, XML_auto ); + maCalcSettings.mnIterateCount = rAttribs.getInteger( XML_iterateCount, 100 ); + maCalcSettings.mnProcCount = rAttribs.getInteger( XML_concurrentManualCount, -1 ); + maCalcSettings.mbCalcOnSave = rAttribs.getBool( XML_calcOnSave, true ); + maCalcSettings.mbCalcCompleted = rAttribs.getBool( XML_calcCompleted, true ); + maCalcSettings.mbFullPrecision = rAttribs.getBool( XML_fullPrecision, true ); + maCalcSettings.mbIterate = rAttribs.getBool( XML_iterate, false ); + maCalcSettings.mbConcurrent = rAttribs.getBool( XML_concurrentCalc, true ); +} + +void WorkbookSettings::importFileSharing( RecordInputStream& rStrm ) +{ + maFileSharing.mbRecommendReadOnly = rStrm.readuInt16() != 0; + rStrm >> maFileSharing.mnPasswordHash >> maFileSharing.maUserName; +} + +void WorkbookSettings::importWorkbookPr( RecordInputStream& rStrm ) +{ + sal_uInt32 nFlags; + rStrm >> nFlags >> maBookSettings.mnDefaultThemeVer >> maBookSettings.maCodeName; + maBookSettings.setBinObjectMode( extractValue< sal_uInt16 >( nFlags, 13, 2 ) ); + // set flag means: strip external link values + maBookSettings.mbSaveExtLinkValues = !getFlag( nFlags, OOBIN_WORKBOOKPR_STRIPEXT ); + setDateMode( getFlag( nFlags, OOBIN_WORKBOOKPR_DATE1904 ) ); +} + +void WorkbookSettings::importCalcPr( RecordInputStream& rStrm ) +{ + sal_Int32 nCalcMode, nProcCount; + sal_uInt16 nFlags; + rStrm >> maCalcSettings.mnCalcId >> nCalcMode >> maCalcSettings.mnIterateCount >> maCalcSettings.mfIterateDelta >> nProcCount >> nFlags; + + static const sal_Int32 spnCalcModes[] = { XML_manual, XML_auto, XML_autoNoTable }; + maCalcSettings.mnRefMode = getFlagValue( nFlags, OOBIN_CALCPR_A1, XML_A1, XML_R1C1 ); + maCalcSettings.mnCalcMode = STATIC_ARRAY_SELECT( spnCalcModes, nCalcMode, XML_auto ); + maCalcSettings.mnProcCount = getFlagValue< sal_Int32 >( nFlags, OOBIN_CALCPR_MANUALPROC, nProcCount, -1 ); + maCalcSettings.mbCalcOnSave = getFlag( nFlags, OOBIN_CALCPR_CALCONSAVE ); + maCalcSettings.mbCalcCompleted = getFlag( nFlags, OOBIN_CALCPR_CALCCOMPLETED ); + maCalcSettings.mbFullPrecision = getFlag( nFlags, OOBIN_CALCPR_FULLPRECISION ); + maCalcSettings.mbIterate = getFlag( nFlags, OOBIN_CALCPR_ITERATE ); + maCalcSettings.mbConcurrent = getFlag( nFlags, OOBIN_CALCPR_CONCURRENT ); +} + +void WorkbookSettings::setSaveExtLinkValues( bool bSaveExtLinks ) +{ + maBookSettings.mbSaveExtLinkValues = bSaveExtLinks; +} + +void WorkbookSettings::importBookBool( BiffInputStream& rStrm ) +{ + // value of 0 means save external values, value of 1 means strip external values + maBookSettings.mbSaveExtLinkValues = rStrm.readuInt16() == 0; +} + +void WorkbookSettings::importCalcCount( BiffInputStream& rStrm ) +{ + maCalcSettings.mnIterateCount = rStrm.readuInt16(); +} + +void WorkbookSettings::importCalcMode( BiffInputStream& rStrm ) +{ + sal_Int16 nCalcMode = rStrm.readInt16() + 1; + static const sal_Int32 spnCalcModes[] = { XML_autoNoTable, XML_manual, XML_auto }; + maCalcSettings.mnCalcMode = STATIC_ARRAY_SELECT( spnCalcModes, nCalcMode, XML_auto ); +} + +void WorkbookSettings::importCodeName( BiffInputStream& rStrm ) +{ + maBookSettings.maCodeName = rStrm.readUniString(); +} + +void WorkbookSettings::importDateMode( BiffInputStream& rStrm ) +{ + setDateMode( rStrm.readuInt16() != 0 ); +} + +void WorkbookSettings::importDelta( BiffInputStream& rStrm ) +{ + rStrm >> maCalcSettings.mfIterateDelta; +} + +void WorkbookSettings::importFileSharing( BiffInputStream& rStrm ) +{ + maFileSharing.mbRecommendReadOnly = rStrm.readuInt16() != 0; + rStrm >> maFileSharing.mnPasswordHash; + if( getBiff() == BIFF8 ) + { + sal_uInt16 nStrLen = rStrm.readuInt16(); + // there is no string flags field if string is empty + if( nStrLen > 0 ) + maFileSharing.maUserName = rStrm.readUniStringBody( nStrLen ); + } + else + { + maFileSharing.maUserName = rStrm.readByteStringUC( false, getTextEncoding() ); + } +} + +void WorkbookSettings::importHideObj( BiffInputStream& rStrm ) +{ + maBookSettings.setBinObjectMode( rStrm.readuInt16() ); +} + +void WorkbookSettings::importIteration( BiffInputStream& rStrm ) +{ + maCalcSettings.mbIterate = rStrm.readuInt16() != 0; +} + +void WorkbookSettings::importPrecision( BiffInputStream& rStrm ) +{ + maCalcSettings.mbFullPrecision = rStrm.readuInt16() != 0; +} + +void WorkbookSettings::importRefMode( BiffInputStream& rStrm ) +{ + maCalcSettings.mnRefMode = (rStrm.readuInt16() == 0) ? XML_R1C1 : XML_A1; +} + +void WorkbookSettings::importSaveRecalc( BiffInputStream& rStrm ) +{ + maCalcSettings.mbCalcOnSave = rStrm.readuInt16() != 0; +} + +void WorkbookSettings::importUncalced( BiffInputStream& ) +{ + // existence of this record indicates incomplete recalc + maCalcSettings.mbCalcCompleted = false; +} + +void WorkbookSettings::importUsesElfs( BiffInputStream& rStrm ) +{ + maCalcSettings.mbUseNlr = rStrm.readuInt16() != 0; +} + +void WorkbookSettings::finalizeImport() +{ + // default settings + PropertySet aPropSet( getDocument() ); + switch( getFilterType() ) + { + case FILTER_OOX: + case FILTER_BIFF: + aPropSet.setProperty( PROP_IgnoreCase, true ); // always in Excel + aPropSet.setProperty( PROP_RegularExpressions, false ); // not supported in Excel + break; + case FILTER_UNKNOWN: + break; + } + + // write protection + if( maFileSharing.mbRecommendReadOnly || (maFileSharing.mnPasswordHash != 0) ) try + { + getBaseFilter().getMediaDescriptor()[ CREATE_OUSTRING( "ReadOnly" ) ] <<= true; + + Reference< XPropertySet > xDocumentSettings( getDocumentFactory()->createInstance( + CREATE_OUSTRING( "com.sun.star.document.Settings" ) ), UNO_QUERY_THROW ); + PropertySet aSettingsProp( xDocumentSettings ); + if( maFileSharing.mbRecommendReadOnly ) + aSettingsProp.setProperty( PROP_LoadReadonly, true ); +// if( maFileSharing.mnPasswordHash != 0 ) +// aSettingsProp.setProperty( PROP_ModifyPasswordHash, static_cast< sal_Int32 >( maFileSharing.mnPasswordHash ) ); + } + catch( Exception& ) + { + } + + // calculation settings + Date aNullDate = getNullDate(); + + aPropSet.setProperty( PROP_NullDate, aNullDate ); + aPropSet.setProperty( PROP_IsIterationEnabled, maCalcSettings.mbIterate ); + aPropSet.setProperty( PROP_IterationCount, maCalcSettings.mnIterateCount ); + aPropSet.setProperty( PROP_IterationEpsilon, maCalcSettings.mfIterateDelta ); + aPropSet.setProperty( PROP_CalcAsShown, !maCalcSettings.mbFullPrecision ); + aPropSet.setProperty( PROP_LookUpLabels, maCalcSettings.mbUseNlr ); + + Reference< XNumberFormatsSupplier > xNumFmtsSupp( getDocument(), UNO_QUERY ); + if( xNumFmtsSupp.is() ) + { + PropertySet aNumFmtProp( xNumFmtsSupp->getNumberFormatSettings() ); + aNumFmtProp.setProperty( PROP_NullDate, aNullDate ); + } + + Reference< XCalculatable > xCalculatable( getDocument(), UNO_QUERY ); + if( xCalculatable.is() ) + xCalculatable->enableAutomaticCalculation( (maCalcSettings.mnCalcMode == XML_auto) || (maCalcSettings.mnCalcMode == XML_autoNoTable) ); + + // VBA code name + aPropSet.setProperty( PROP_CodeName, maBookSettings.maCodeName ); +} + +sal_Int16 WorkbookSettings::getApiShowObjectMode() const +{ + switch( maBookSettings.mnShowObjectMode ) + { + case XML_all: return API_SHOWMODE_SHOW; + case XML_none: return API_SHOWMODE_HIDE; + // #i80528# placeholders not supported anymore, but this is handled internally in Calc + case XML_placeholders: return API_SHOWMODE_PLACEHOLDER; + } + return API_SHOWMODE_SHOW; +} + +Date WorkbookSettings::getNullDate() const +{ + static const Date saDate1900( 30, 12, 1899 ), saDate1904( 1, 1, 1904 ); + return maBookSettings.mbDateMode1904 ? saDate1904 : saDate1900; +} + +void WorkbookSettings::setDateMode( bool bDateMode1904 ) +{ + maBookSettings.mbDateMode1904 = bDateMode1904; + getUnitConverter().finalizeNullDate( getNullDate() ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/worksheetbuffer.cxx b/oox/source/xls/worksheetbuffer.cxx new file mode 100644 index 000000000000..6f8d50ff38f6 --- /dev/null +++ b/oox/source/xls/worksheetbuffer.cxx @@ -0,0 +1,261 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/worksheetbuffer.hxx" +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/sheet/XExternalSheetName.hpp> +#include <com/sun/star/sheet/XSheetLinkable.hpp> +#include "properties.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/excelhandlers.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::container::XNamed; +using ::com::sun::star::sheet::XSpreadsheetDocument; +using ::com::sun::star::sheet::XSpreadsheets; + +namespace oox { +namespace xls { + +// ============================================================================ + +SheetInfoModel::SheetInfoModel() : + mnBiffHandle( -1 ), + mnSheetId( -1 ), + mnState( XML_visible ) +{ +} + +// ============================================================================ + +WorksheetBuffer::WorksheetBuffer( const WorkbookHelper& rHelper ) : + WorkbookHelper( rHelper ) +{ +} + +/*static*/ OUString WorksheetBuffer::getBaseFileName( const OUString& rUrl ) +{ + sal_Int32 nFileNamePos = ::std::max< sal_Int32 >( rUrl.lastIndexOf( '/' ) + 1, 0 ); + sal_Int32 nExtPos = rUrl.lastIndexOf( '.' ); + if( nExtPos <= nFileNamePos ) nExtPos = rUrl.getLength(); + return rUrl.copy( nFileNamePos, nExtPos - nFileNamePos ); +} + +void WorksheetBuffer::initializeSingleSheet() +{ + OSL_ENSURE( maSheetInfos.empty(), "WorksheetBuffer::initializeSingleSheet - invalid call" ); + SheetInfoModel aModel; + aModel.maName = getBaseFileName( getBaseFilter().getFileUrl() ); + insertSheet( aModel ); +} + +void WorksheetBuffer::importSheet( const AttributeList& rAttribs ) +{ + SheetInfoModel aModel; + aModel.maRelId = rAttribs.getString( R_TOKEN( id ), OUString() ); + aModel.maName = rAttribs.getXString( XML_name, OUString() ); + aModel.mnSheetId = rAttribs.getInteger( XML_sheetId, -1 ); + aModel.mnState = rAttribs.getToken( XML_state, XML_visible ); + insertSheet( aModel ); +} + +void WorksheetBuffer::importSheet( RecordInputStream& rStrm ) +{ + sal_Int32 nState; + SheetInfoModel aModel; + rStrm >> nState >> aModel.mnSheetId >> aModel.maRelId >> aModel.maName; + static const sal_Int32 spnStates[] = { XML_visible, XML_hidden, XML_veryHidden }; + aModel.mnState = STATIC_ARRAY_SELECT( spnStates, nState, XML_visible ); + insertSheet( aModel ); +} + +void WorksheetBuffer::importSheet( BiffInputStream& rStrm ) +{ + SheetInfoModel aModel; + if( getBiff() >= BIFF5 ) + { + rStrm.enableDecoder( false ); + aModel.mnBiffHandle = rStrm.readuInt32(); + rStrm.enableDecoder( true ); + sal_uInt16 nState = rStrm.readuInt16(); + static const sal_Int32 spnStates[] = { XML_visible, XML_hidden, XML_veryHidden }; + aModel.mnState = STATIC_ARRAY_SELECT( spnStates, nState, XML_visible ); + } + aModel.maName = (getBiff() == BIFF8) ? + rStrm.readUniStringBody( rStrm.readuInt8() ) : + rStrm.readByteStringUC( false, getTextEncoding() ); + insertSheet( aModel ); +} + +sal_Int16 WorksheetBuffer::insertEmptySheet( const OUString& rPreferredName, bool bVisible ) +{ + return createSheet( rPreferredName, SAL_MAX_INT32, bVisible ).first; +} + +sal_Int32 WorksheetBuffer::getWorksheetCount() const +{ + return static_cast< sal_Int32 >( maSheetInfos.size() ); +} + +OUString WorksheetBuffer::getWorksheetRelId( sal_Int32 nWorksheet ) const +{ + const SheetInfo* pSheetInfo = maSheetInfos.get( nWorksheet ).get(); + return pSheetInfo ? pSheetInfo->maRelId : OUString(); +} + +sal_Int64 WorksheetBuffer::getBiffRecordHandle( sal_Int32 nWorksheet ) const +{ + const SheetInfo* pSheetInfo = maSheetInfos.get( nWorksheet ).get(); + return pSheetInfo ? pSheetInfo->mnBiffHandle : -1; +} + +sal_Int16 WorksheetBuffer::getCalcSheetIndex( sal_Int32 nWorksheet ) const +{ + const SheetInfo* pSheetInfo = maSheetInfos.get( nWorksheet ).get(); + return pSheetInfo ? pSheetInfo->mnCalcSheet : -1; +} + +OUString WorksheetBuffer::getCalcSheetName( sal_Int32 nWorksheet ) const +{ + const SheetInfo* pSheetInfo = maSheetInfos.get( nWorksheet ).get(); + return pSheetInfo ? pSheetInfo->maCalcName : OUString(); +} + +sal_Int16 WorksheetBuffer::getCalcSheetIndex( const OUString& rWorksheetName ) const +{ + const SheetInfo* pSheetInfo = maSheetInfosByName.get( rWorksheetName ).get(); + return pSheetInfo ? pSheetInfo->mnCalcSheet : -1; +} + +OUString WorksheetBuffer::getCalcSheetName( const OUString& rWorksheetName ) const +{ + if( const SheetInfo* pSheetInfo = maSheetInfosByName.get( rWorksheetName ).get() ) + { + bool bIsQuoted = pSheetInfo->maName != rWorksheetName; + return bIsQuoted ? pSheetInfo->maCalcQuotedName : pSheetInfo->maCalcName; + } + return OUString(); +} + +// private -------------------------------------------------------------------- + +namespace { + +OUString lclQuoteName( const OUString& rName ) +{ + OUStringBuffer aBuffer( rName ); + // duplicate all quote characters + for( sal_Int32 nPos = aBuffer.getLength() - 1; nPos >= 0; --nPos ) + if( aBuffer.charAt( nPos ) == '\'' ) + aBuffer.insert( nPos, sal_Unicode( '\'' ) ); + // add outer quotes and return + return aBuffer.insert( 0, sal_Unicode( '\'' ) ).append( sal_Unicode( '\'' ) ).makeStringAndClear(); +} + +} // namespace + +WorksheetBuffer::SheetInfo::SheetInfo( const SheetInfoModel& rModel, sal_Int16 nCalcSheet, const OUString& rCalcName ) : + SheetInfoModel( rModel ), + maCalcName( rCalcName ), + maCalcQuotedName( lclQuoteName( rCalcName ) ), + mnCalcSheet( nCalcSheet ) +{ +} + +WorksheetBuffer::IndexNamePair WorksheetBuffer::createSheet( const OUString& rPreferredName, sal_Int32 nSheetPos, bool bVisible ) +{ + try + { + Reference< XSpreadsheets > xSheets( getDocument()->getSheets(), UNO_QUERY_THROW ); + Reference< XIndexAccess > xSheetsIA( xSheets, UNO_QUERY_THROW ); + Reference< XNameAccess > xSheetsNA( xSheets, UNO_QUERY_THROW ); + sal_Int16 nCalcSheet = -1; + OUString aSheetName = (rPreferredName.getLength() == 0) ? CREATE_OUSTRING( "Sheet" ) : rPreferredName; + PropertySet aPropSet; + if( nSheetPos < xSheetsIA->getCount() ) + { + nCalcSheet = static_cast< sal_Int16 >( nSheetPos ); + // existing sheet - try to rename + Reference< XNamed > xSheetName( xSheetsIA->getByIndex( nSheetPos ), UNO_QUERY_THROW ); + if( xSheetName->getName() != aSheetName ) + { + aSheetName = ContainerHelper::getUnusedName( xSheetsNA, aSheetName, ' ' ); + xSheetName->setName( aSheetName ); + } + aPropSet.set( xSheetName ); + } + else + { + nCalcSheet = static_cast< sal_Int16 >( xSheetsIA->getCount() ); + // new sheet - insert with unused name + aSheetName = ContainerHelper::getUnusedName( xSheetsNA, aSheetName, ' ' ); + xSheets->insertNewByName( aSheetName, nCalcSheet ); + aPropSet.set( xSheetsIA->getByIndex( nCalcSheet ) ); + } + + // sheet properties + aPropSet.setProperty( PROP_IsVisible, bVisible ); + + // return final sheet index if sheet exists + return IndexNamePair( nCalcSheet, aSheetName ); + } + catch( Exception& ) + { + OSL_ENSURE( false, "WorksheetBuffer::createSheet - cannot insert or rename worksheet" ); + } + return IndexNamePair( -1, OUString() ); +} + +void WorksheetBuffer::insertSheet( const SheetInfoModel& rModel ) +{ + sal_Int32 nWorksheet = static_cast< sal_Int32 >( maSheetInfos.size() ); + IndexNamePair aIndexName = createSheet( rModel.maName, nWorksheet, rModel.mnState == XML_visible ); + ::boost::shared_ptr< SheetInfo > xSheetInfo( new SheetInfo( rModel, aIndexName.first, aIndexName.second ) ); + maSheetInfos.push_back( xSheetInfo ); + maSheetInfosByName[ rModel.maName ] = xSheetInfo; + maSheetInfosByName[ lclQuoteName( rModel.maName ) ] = xSheetInfo; +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/worksheetfragment.cxx b/oox/source/xls/worksheetfragment.cxx new file mode 100644 index 000000000000..bee58fdbaff3 --- /dev/null +++ b/oox/source/xls/worksheetfragment.cxx @@ -0,0 +1,1202 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/worksheetfragment.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/core/relations.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/autofiltercontext.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/commentsfragment.hxx" +#include "oox/xls/condformatcontext.hxx" +#include "oox/xls/drawingfragment.hxx" +#include "oox/xls/externallinkbuffer.hxx" +#include "oox/xls/pagesettings.hxx" +#include "oox/xls/pivottablebuffer.hxx" +#include "oox/xls/pivottablefragment.hxx" +#include "oox/xls/querytablefragment.hxx" +#include "oox/xls/scenariobuffer.hxx" +#include "oox/xls/scenariocontext.hxx" +#include "oox/xls/sheetdatacontext.hxx" +#include "oox/xls/tablefragment.hxx" +#include "oox/xls/viewsettings.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/xls/worksheetsettings.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::oox::core::ContextHandlerRef; +using ::oox::core::RecordInfo; +using ::oox::core::Relations; +using ::oox::core::RelationsRef; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt16 BIFF_COLINFO_HIDDEN = 0x0001; +const sal_uInt16 BIFF_COLINFO_SHOWPHONETIC = 0x0008; +const sal_uInt16 BIFF_COLINFO_COLLAPSED = 0x1000; + +const sal_uInt16 BIFF_DEFROW_CUSTOMHEIGHT = 0x0001; +const sal_uInt16 BIFF_DEFROW_HIDDEN = 0x0002; +const sal_uInt16 BIFF_DEFROW_THICKTOP = 0x0004; +const sal_uInt16 BIFF_DEFROW_THICKBOTTOM = 0x0008; +const sal_uInt16 BIFF2_DEFROW_DEFHEIGHT = 0x8000; +const sal_uInt16 BIFF2_DEFROW_MASK = 0x7FFF; + +const sal_uInt32 BIFF_DATAVAL_STRINGLIST = 0x00000080; +const sal_uInt32 BIFF_DATAVAL_ALLOWBLANK = 0x00000100; +const sal_uInt32 BIFF_DATAVAL_NODROPDOWN = 0x00000200; +const sal_uInt32 BIFF_DATAVAL_SHOWINPUT = 0x00040000; +const sal_uInt32 BIFF_DATAVAL_SHOWERROR = 0x00080000; + +const sal_uInt32 BIFF_SHRFEATHEAD_SHEETPROT = 2; + +const sal_Int32 OOBIN_OLEOBJECT_CONTENT = 1; +const sal_Int32 OOBIN_OLEOBJECT_ICON = 4; +const sal_Int32 OOBIN_OLEOBJECT_ALWAYS = 1; +const sal_Int32 OOBIN_OLEOBJECT_ONCALL = 3; +const sal_uInt16 OOBIN_OLEOBJECT_LINKED = 0x0001; +const sal_uInt16 OOBIN_OLEOBJECT_AUTOLOAD = 0x0002; + +} // namespace + +// ============================================================================ + +OoxDataValidationsContext::OoxDataValidationsContext( OoxWorksheetFragmentBase& rFragment ) : + OoxWorksheetContextBase( rFragment ) +{ +} + +ContextHandlerRef OoxDataValidationsContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( dataValidations ): + if( nElement == XLS_TOKEN( dataValidation ) ) + { + importDataValidation( rAttribs ); + return this; + } + break; + case XLS_TOKEN( dataValidation ): + switch( nElement ) + { + case XLS_TOKEN( formula1 ): + case XLS_TOKEN( formula2 ): + return this; // collect formulas in onEndElement() + } + break; + } + return 0; +} + +namespace { + +ApiTokenSequence lclImportDataValFormula( FormulaParser& rParser, const OUString& rFormula, const CellAddress& rBaseAddress ) +{ + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( rBaseAddress ); + rParser.importFormula( aContext, rFormula ); + return aContext.getTokens(); +} + +} // namespace + +void OoxDataValidationsContext::onEndElement( const OUString& rChars ) +{ + if( mxValModel.get() ) switch( getCurrentElement() ) + { + case XLS_TOKEN( formula1 ): + mxValModel->maTokens1 = lclImportDataValFormula( + getFormulaParser(), rChars, mxValModel->maRanges.getBaseAddress() ); + // process string list of a list validation (convert to list of string tokens) + if( mxValModel->mnType == XML_list ) + getFormulaParser().convertStringToStringList( mxValModel->maTokens1, ',', true ); + break; + case XLS_TOKEN( formula2 ): + mxValModel->maTokens2 = lclImportDataValFormula( + getFormulaParser(), rChars, mxValModel->maRanges.getBaseAddress() ); + break; + case XLS_TOKEN( dataValidation ): + setValidation( *mxValModel ); + mxValModel.reset(); + break; + } +} + + +ContextHandlerRef OoxDataValidationsContext::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + if( nRecId == OOBIN_ID_DATAVALIDATION ) + importDataValidation( rStrm ); + return 0; +} + +void OoxDataValidationsContext::importDataValidation( const AttributeList& rAttribs ) +{ + mxValModel.reset( new ValidationModel ); + getAddressConverter().convertToCellRangeList( mxValModel->maRanges, rAttribs.getString( XML_sqref, OUString() ), getSheetIndex(), true ); + mxValModel->maInputTitle = rAttribs.getXString( XML_promptTitle, OUString() ); + mxValModel->maInputMessage = rAttribs.getXString( XML_prompt, OUString() ); + mxValModel->maErrorTitle = rAttribs.getXString( XML_errorTitle, OUString() ); + mxValModel->maErrorMessage = rAttribs.getXString( XML_error, OUString() ); + mxValModel->mnType = rAttribs.getToken( XML_type, XML_none ); + mxValModel->mnOperator = rAttribs.getToken( XML_operator, XML_between ); + mxValModel->mnErrorStyle = rAttribs.getToken( XML_errorStyle, XML_stop ); + mxValModel->mbShowInputMsg = rAttribs.getBool( XML_showInputMessage, false ); + mxValModel->mbShowErrorMsg = rAttribs.getBool( XML_showErrorMessage, false ); + /* The attribute showDropDown@dataValidation is in fact a "suppress + dropdown" flag, as it was in the BIFF format! ECMA specification + and attribute name are plain wrong! */ + mxValModel->mbNoDropDown = rAttribs.getBool( XML_showDropDown, false ); + mxValModel->mbAllowBlank = rAttribs.getBool( XML_allowBlank, false ); +} + +void OoxDataValidationsContext::importDataValidation( RecordInputStream& rStrm ) +{ + ValidationModel aModel; + + sal_uInt32 nFlags; + BinRangeList aRanges; + rStrm >> nFlags >> aRanges >> aModel.maErrorTitle >> aModel.maErrorMessage >> aModel.maInputTitle >> aModel.maInputMessage; + + // equal flags in BIFF and OOBIN + aModel.setBinType( extractValue< sal_uInt8 >( nFlags, 0, 4 ) ); + aModel.setBinOperator( extractValue< sal_uInt8 >( nFlags, 20, 4 ) ); + aModel.setBinErrorStyle( extractValue< sal_uInt8 >( nFlags, 4, 3 ) ); + aModel.mbAllowBlank = getFlag( nFlags, BIFF_DATAVAL_ALLOWBLANK ); + aModel.mbNoDropDown = getFlag( nFlags, BIFF_DATAVAL_NODROPDOWN ); + aModel.mbShowInputMsg = getFlag( nFlags, BIFF_DATAVAL_SHOWINPUT ); + aModel.mbShowErrorMsg = getFlag( nFlags, BIFF_DATAVAL_SHOWERROR ); + + // cell range list + getAddressConverter().convertToCellRangeList( aModel.maRanges, aRanges, getSheetIndex(), true ); + + // condition formula(s) + FormulaParser& rParser = getFormulaParser(); + TokensFormulaContext aContext( true, false ); + aContext.setBaseAddress( aModel.maRanges.getBaseAddress() ); + rParser.importFormula( aContext, rStrm ); + aModel.maTokens1 = aContext.getTokens(); + rParser.importFormula( aContext, rStrm ); + aModel.maTokens2 = aContext.getTokens(); + // process string list of a list validation (convert to list of string tokens) + if( (aModel.mnType == XML_list) && getFlag( nFlags, BIFF_DATAVAL_STRINGLIST ) ) + rParser.convertStringToStringList( aModel.maTokens1, ',', true ); + + // set validation data + setValidation( aModel ); +} + +// ============================================================================ + +OoxWorksheetFragment::OoxWorksheetFragment( const WorkbookHelper& rHelper, + const OUString& rFragmentPath, ISegmentProgressBarRef xProgressBar, WorksheetType eSheetType, sal_Int16 nSheet ) : + OoxWorksheetFragmentBase( rHelper, rFragmentPath, xProgressBar, eSheetType, nSheet ) +{ + // import data tables related to this worksheet + RelationsRef xTableRels = getRelations().getRelationsFromType( CREATE_OFFICEDOC_RELATIONSTYPE( "table" ) ); + for( Relations::const_iterator aIt = xTableRels->begin(), aEnd = xTableRels->end(); aIt != aEnd; ++aIt ) + importOoxFragment( new OoxTableFragment( *this, getFragmentPathFromRelation( aIt->second ) ) ); + + // import comments related to this worksheet + OUString aCommentsFragmentPath = getFragmentPathFromFirstType( CREATE_OFFICEDOC_RELATIONSTYPE( "comments" ) ); + if( aCommentsFragmentPath.getLength() > 0 ) + importOoxFragment( new OoxCommentsFragment( *this, aCommentsFragmentPath ) ); +} + +// oox.core.ContextHandler2Helper interface ----------------------------------- + +ContextHandlerRef OoxWorksheetFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: switch( getSheetType() ) + { + case SHEETTYPE_WORKSHEET: return (nElement == XLS_TOKEN( worksheet )) ? this : 0; + case SHEETTYPE_CHARTSHEET: return 0; + case SHEETTYPE_MACROSHEET: return (nElement == XM_TOKEN( macrosheet )) ? this : 0; + case SHEETTYPE_DIALOGSHEET: return (nElement == XM_TOKEN( dialogsheet )) ? this : 0; + case SHEETTYPE_MODULESHEET: return 0; + case SHEETTYPE_EMPTYSHEET: return 0; + } + break; + + case XLS_TOKEN( worksheet ): + case XM_TOKEN( macrosheet ): + switch( nElement ) + { + case XLS_TOKEN( sheetData ): return new OoxSheetDataContext( *this ); + case XLS_TOKEN( autoFilter ): return new OoxAutoFilterContext( *this ); + case XLS_TOKEN( conditionalFormatting ): return new OoxCondFormatContext( *this ); + case XLS_TOKEN( dataValidations ): return new OoxDataValidationsContext( *this ); + + case XLS_TOKEN( sheetViews ): + case XLS_TOKEN( cols ): + case XLS_TOKEN( mergeCells ): + case XLS_TOKEN( hyperlinks ): + case XLS_TOKEN( rowBreaks ): + case XLS_TOKEN( colBreaks ): + case XLS_TOKEN( oleObjects ): + case XLS_TOKEN( controls ): return this; + + case XLS_TOKEN( sheetPr ): getWorksheetSettings().importSheetPr( rAttribs ); return this; + case XLS_TOKEN( dimension ): importDimension( rAttribs ); break; + case XLS_TOKEN( sheetFormatPr ): importSheetFormatPr( rAttribs ); break; + case XLS_TOKEN( sheetProtection ): getWorksheetSettings().importSheetProtection( rAttribs ); break; + case XLS_TOKEN( phoneticPr ): getWorksheetSettings().importPhoneticPr( rAttribs ); break; + case XLS_TOKEN( printOptions ): getPageSettings().importPrintOptions( rAttribs ); break; + case XLS_TOKEN( pageMargins ): getPageSettings().importPageMargins( rAttribs ); break; + case XLS_TOKEN( pageSetup ): getPageSettings().importPageSetup( getRelations(), rAttribs ); break; + case XLS_TOKEN( headerFooter ): getPageSettings().importHeaderFooter( rAttribs ); return this; + case XLS_TOKEN( picture ): getPageSettings().importPicture( getRelations(), rAttribs ); break; + case XLS_TOKEN( drawing ): importDrawing( rAttribs ); break; + case XLS_TOKEN( legacyDrawing ): importLegacyDrawing( rAttribs ); break; + case XLS_TOKEN( scenarios ): + return new OoxScenariosContext( *this ); + } + break; + + case XLS_TOKEN( sheetPr ): + switch( nElement ) + { + case XLS_TOKEN( tabColor ): getWorksheetSettings().importTabColor( rAttribs ); break; + case XLS_TOKEN( outlinePr ): getWorksheetSettings().importOutlinePr( rAttribs ); break; + case XLS_TOKEN( pageSetUpPr ): importPageSetUpPr( rAttribs ); break; + } + break; + + case XLS_TOKEN( sheetViews ): + switch( nElement ) + { + case XLS_TOKEN( sheetView ): getSheetViewSettings().importSheetView( rAttribs ); return this; + } + break; + case XLS_TOKEN( sheetView ): + switch( nElement ) + { + case XLS_TOKEN( pane ): getSheetViewSettings().importPane( rAttribs ); break; + case XLS_TOKEN( selection ): getSheetViewSettings().importSelection( rAttribs ); break; + } + break; + + case XLS_TOKEN( cols ): + if( nElement == XLS_TOKEN( col ) ) importCol( rAttribs ); + break; + case XLS_TOKEN( mergeCells ): + if( nElement == XLS_TOKEN( mergeCell ) ) importMergeCell( rAttribs ); + break; + case XLS_TOKEN( hyperlinks ): + if( nElement == XLS_TOKEN( hyperlink ) ) importHyperlink( rAttribs ); + break; + case XLS_TOKEN( rowBreaks ): + if( nElement == XLS_TOKEN( brk ) ) importBrk( rAttribs, true ); + break; + case XLS_TOKEN( colBreaks ): + if( nElement == XLS_TOKEN( brk ) ) importBrk( rAttribs, false ); + break; + + case XLS_TOKEN( headerFooter ): + switch( nElement ) + { + case XLS_TOKEN( firstHeader ): + case XLS_TOKEN( firstFooter ): + case XLS_TOKEN( oddHeader ): + case XLS_TOKEN( oddFooter ): + case XLS_TOKEN( evenHeader ): + case XLS_TOKEN( evenFooter ): return this; // collect h/f contents in onEndElement() + } + break; + + case XLS_TOKEN( oleObjects ): + if( nElement == XLS_TOKEN( oleObject ) ) importOleObject( rAttribs ); + break; + case XLS_TOKEN( controls ): + if( nElement == XLS_TOKEN( control ) ) importControl( rAttribs ); + break; + } + return 0; +} + +void OoxWorksheetFragment::onEndElement( const OUString& rChars ) +{ + switch( getCurrentElement() ) + { + case XLS_TOKEN( firstHeader ): + case XLS_TOKEN( firstFooter ): + case XLS_TOKEN( oddHeader ): + case XLS_TOKEN( oddFooter ): + case XLS_TOKEN( evenHeader ): + case XLS_TOKEN( evenFooter ): + getPageSettings().importHeaderFooterCharacters( rChars, getCurrentElement() ); + break; + } +} + +ContextHandlerRef OoxWorksheetFragment::onCreateRecordContext( sal_Int32 nRecId, RecordInputStream& rStrm ) +{ + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + if( nRecId == OOBIN_ID_WORKSHEET ) return this; + break; + + case OOBIN_ID_WORKSHEET: + switch( nRecId ) + { + case OOBIN_ID_SHEETDATA: return new OoxSheetDataContext( *this ); + case OOBIN_ID_CONDFORMATTING: return new OoxCondFormatContext( *this ); + case OOBIN_ID_DATAVALIDATIONS: return new OoxDataValidationsContext( *this ); + + case OOBIN_ID_SHEETVIEWS: + case OOBIN_ID_COLS: + case OOBIN_ID_MERGECELLS: + case OOBIN_ID_ROWBREAKS: + case OOBIN_ID_COLBREAKS: + case OOBIN_ID_OLEOBJECTS: + case OOBIN_ID_CONTROLS: return this; + + case OOBIN_ID_SHEETPR: getWorksheetSettings().importSheetPr( rStrm ); break; + case OOBIN_ID_DIMENSION: importDimension( rStrm ); break; + case OOBIN_ID_SHEETFORMATPR: importSheetFormatPr( rStrm ); break; + case OOBIN_ID_HYPERLINK: importHyperlink( rStrm ); break; + case OOBIN_ID_PAGEMARGINS: getPageSettings().importPageMargins( rStrm ); break; + case OOBIN_ID_PAGESETUP: getPageSettings().importPageSetup( getRelations(), rStrm ); break; + case OOBIN_ID_PRINTOPTIONS: getPageSettings().importPrintOptions( rStrm ); break; + case OOBIN_ID_HEADERFOOTER: getPageSettings().importHeaderFooter( rStrm ); break; + case OOBIN_ID_PICTURE: getPageSettings().importPicture( getRelations(), rStrm ); break; + case OOBIN_ID_SHEETPROTECTION: getWorksheetSettings().importSheetProtection( rStrm ); break; + case OOBIN_ID_PHONETICPR: getWorksheetSettings().importPhoneticPr( rStrm ); break; + case OOBIN_ID_DRAWING: importDrawing( rStrm ); break; + case OOBIN_ID_LEGACYDRAWING: importLegacyDrawing( rStrm ); break; + case OOBIN_ID_SCENARIOS: + return new OoxScenariosContext( *this ); + } + break; + + case OOBIN_ID_SHEETVIEWS: + switch( nRecId ) + { + case OOBIN_ID_SHEETVIEW: getSheetViewSettings().importSheetView( rStrm ); return this; + } + break; + case OOBIN_ID_SHEETVIEW: + switch( nRecId ) + { + case OOBIN_ID_PANE: getSheetViewSettings().importPane( rStrm ); break; + case OOBIN_ID_SELECTION: getSheetViewSettings().importSelection( rStrm ); break; + } + break; + + case OOBIN_ID_COLS: + if( nRecId == OOBIN_ID_COL ) importCol( rStrm ); + break; + case OOBIN_ID_MERGECELLS: + if( nRecId == OOBIN_ID_MERGECELL ) importMergeCell( rStrm ); + break; + case OOBIN_ID_ROWBREAKS: + if( nRecId == OOBIN_ID_BRK ) importBrk( rStrm, true ); + break; + case OOBIN_ID_COLBREAKS: + if( nRecId == OOBIN_ID_BRK ) importBrk( rStrm, false ); + break; + case OOBIN_ID_OLEOBJECTS: + if( nRecId == OOBIN_ID_OLEOBJECT ) importOleObject( rStrm ); + break; + case OOBIN_ID_CONTROLS: + if( nRecId == OOBIN_ID_CONTROL ) importControl( rStrm ); + break; + } + return 0; +} + +// oox.core.FragmentHandler2 interface ---------------------------------------- + +const RecordInfo* OoxWorksheetFragment::getRecordInfos() const +{ + static const RecordInfo spRecInfos[] = + { + { OOBIN_ID_CFRULE, OOBIN_ID_CFRULE + 1 }, + { OOBIN_ID_COLBREAKS, OOBIN_ID_COLBREAKS + 1 }, + { OOBIN_ID_COLORSCALE, OOBIN_ID_COLORSCALE + 1 }, + { OOBIN_ID_COLS, OOBIN_ID_COLS + 1 }, + { OOBIN_ID_CONDFORMATTING, OOBIN_ID_CONDFORMATTING + 1 }, + { OOBIN_ID_CONTROLS, OOBIN_ID_CONTROLS + 2 }, + { OOBIN_ID_CUSTOMSHEETVIEW, OOBIN_ID_CUSTOMSHEETVIEW + 1 }, + { OOBIN_ID_CUSTOMSHEETVIEWS, OOBIN_ID_CUSTOMSHEETVIEWS + 3 }, + { OOBIN_ID_DATABAR, OOBIN_ID_DATABAR + 1 }, + { OOBIN_ID_DATAVALIDATIONS, OOBIN_ID_DATAVALIDATIONS + 1 }, + { OOBIN_ID_HEADERFOOTER, OOBIN_ID_HEADERFOOTER + 1 }, + { OOBIN_ID_ICONSET, OOBIN_ID_ICONSET + 1 }, + { OOBIN_ID_MERGECELLS, OOBIN_ID_MERGECELLS + 1 }, + { OOBIN_ID_OLEOBJECTS, OOBIN_ID_OLEOBJECTS + 2 }, + { OOBIN_ID_ROW, -1 }, + { OOBIN_ID_ROWBREAKS, OOBIN_ID_ROWBREAKS + 1 }, + { OOBIN_ID_SCENARIO, OOBIN_ID_SCENARIO + 1 }, + { OOBIN_ID_SCENARIOS, OOBIN_ID_SCENARIOS + 1 }, + { OOBIN_ID_SHEETDATA, OOBIN_ID_SHEETDATA + 1 }, + { OOBIN_ID_SHEETVIEW, OOBIN_ID_SHEETVIEW + 1 }, + { OOBIN_ID_SHEETVIEWS, OOBIN_ID_SHEETVIEWS + 1 }, + { OOBIN_ID_TABLEPARTS, OOBIN_ID_TABLEPARTS + 2 }, + { OOBIN_ID_WORKSHEET, OOBIN_ID_WORKSHEET + 1 }, + { -1, -1 } + }; + return spRecInfos; +} + +void OoxWorksheetFragment::initializeImport() +{ + // initial processing in base class WorksheetHelper + initializeWorksheetImport(); + + // import query table fragments related to this worksheet + RelationsRef xQueryRels = getRelations().getRelationsFromType( CREATE_OFFICEDOC_RELATIONSTYPE( "queryTable" ) ); + for( Relations::const_iterator aIt = xQueryRels->begin(), aEnd = xQueryRels->end(); aIt != aEnd; ++aIt ) + importOoxFragment( new OoxQueryTableFragment( *this, getFragmentPathFromRelation( aIt->second ) ) ); + + // import pivot table fragments related to this worksheet + RelationsRef xPivotRels = getRelations().getRelationsFromType( CREATE_OFFICEDOC_RELATIONSTYPE( "pivotTable" ) ); + for( Relations::const_iterator aIt = xPivotRels->begin(), aEnd = xPivotRels->end(); aIt != aEnd; ++aIt ) + importOoxFragment( new OoxPivotTableFragment( *this, getFragmentPathFromRelation( aIt->second ) ) ); +} + +void OoxWorksheetFragment::finalizeImport() +{ + // final processing in base class WorksheetHelper + finalizeWorksheetImport(); +} + +// private -------------------------------------------------------------------- + +void OoxWorksheetFragment::importPageSetUpPr( const AttributeList& rAttribs ) +{ + // for whatever reason, this flag is still stored separated from the page settings + getPageSettings().setFitToPagesMode( rAttribs.getBool( XML_fitToPage, false ) ); +} + +void OoxWorksheetFragment::importDimension( const AttributeList& rAttribs ) +{ + CellRangeAddress aRange; + getAddressConverter().convertToCellRangeUnchecked( aRange, rAttribs.getString( XML_ref, OUString() ), getSheetIndex() ); + /* OOXML stores the used area, if existing, or "A1" if the sheet is empty. + In case of "A1", the dimension at the WorksheetHelper object will not + be set. If the cell A1 exists, the used area will be updated while + importing the cell. */ + if( (aRange.EndColumn > 0) || (aRange.EndRow > 0) ) + extendUsedArea( aRange ); +} + +void OoxWorksheetFragment::importSheetFormatPr( const AttributeList& rAttribs ) +{ + // default column settings + setBaseColumnWidth( rAttribs.getInteger( XML_baseColWidth, 8 ) ); + setDefaultColumnWidth( rAttribs.getDouble( XML_defaultColWidth, 0.0 ) ); + // default row settings + setDefaultRowSettings( + rAttribs.getDouble( XML_defaultRowHeight, 0.0 ), + rAttribs.getBool( XML_customHeight, false ), + rAttribs.getBool( XML_zeroHeight, false ), + rAttribs.getBool( XML_thickTop, false ), + rAttribs.getBool( XML_thickBottom, false ) ); +} + +void OoxWorksheetFragment::importCol( const AttributeList& rAttribs ) +{ + ColumnModel aModel; + aModel.mnFirstCol = rAttribs.getInteger( XML_min, -1 ); + aModel.mnLastCol = rAttribs.getInteger( XML_max, -1 ); + aModel.mfWidth = rAttribs.getDouble( XML_width, 0.0 ); + aModel.mnXfId = rAttribs.getInteger( XML_style, -1 ); + aModel.mnLevel = rAttribs.getInteger( XML_outlineLevel, 0 ); + aModel.mbShowPhonetic = rAttribs.getBool( XML_phonetic, false ); + aModel.mbHidden = rAttribs.getBool( XML_hidden, false ); + aModel.mbCollapsed = rAttribs.getBool( XML_collapsed, false ); + // set column properties in the current sheet + setColumnModel( aModel ); +} + +void OoxWorksheetFragment::importMergeCell( const AttributeList& rAttribs ) +{ + CellRangeAddress aRange; + if( getAddressConverter().convertToCellRange( aRange, rAttribs.getString( XML_ref, OUString() ), getSheetIndex(), true, true ) ) + setMergedRange( aRange ); +} + +void OoxWorksheetFragment::importHyperlink( const AttributeList& rAttribs ) +{ + HyperlinkModel aModel; + if( getAddressConverter().convertToCellRange( aModel.maRange, rAttribs.getString( XML_ref, OUString() ), getSheetIndex(), true, true ) ) + { + aModel.maTarget = getRelations().getExternalTargetFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ); + aModel.maLocation = rAttribs.getXString( XML_location, OUString() ); + aModel.maDisplay = rAttribs.getXString( XML_display, OUString() ); + aModel.maTooltip = rAttribs.getXString( XML_tooltip, OUString() ); + setHyperlink( aModel ); + } +} + +void OoxWorksheetFragment::importBrk( const AttributeList& rAttribs, bool bRowBreak ) +{ + PageBreakModel aModel; + aModel.mnColRow = rAttribs.getInteger( XML_id, 0 ); + aModel.mnMin = rAttribs.getInteger( XML_id, 0 ); + aModel.mnMax = rAttribs.getInteger( XML_id, 0 ); + aModel.mbManual = rAttribs.getBool( XML_man, false ); + setPageBreak( aModel, bRowBreak ); +} + +void OoxWorksheetFragment::importDrawing( const AttributeList& rAttribs ) +{ + setDrawingPath( getFragmentPathFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ) ); +} + +void OoxWorksheetFragment::importLegacyDrawing( const AttributeList& rAttribs ) +{ + setVmlDrawingPath( getFragmentPathFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ) ); +} + +void OoxWorksheetFragment::importOleObject( const AttributeList& rAttribs ) +{ + ::oox::vml::OleObjectInfo aInfo; + aInfo.setShapeId( rAttribs.getInteger( XML_shapeId, 0 ) ); + OSL_ENSURE( rAttribs.hasAttribute( XML_link ) != rAttribs.hasAttribute( R_TOKEN( id ) ), + "OoxWorksheetFragment::importOleObject - OLE object must be either linked or embedded" ); + aInfo.mbLinked = rAttribs.hasAttribute( XML_link ); + if( aInfo.mbLinked ) + aInfo.maTargetLink = getFormulaParser().importOleTargetLink( rAttribs.getString( XML_link, OUString() ) ); + else if( rAttribs.hasAttribute( R_TOKEN( id ) ) ) + importEmbeddedOleData( aInfo.maEmbeddedData, rAttribs.getString( R_TOKEN( id ), OUString() ) ); + aInfo.maProgId = rAttribs.getString( XML_progId, OUString() ); + aInfo.mbShowAsIcon = rAttribs.getToken( XML_dvAspect, XML_DVASPECT_CONTENT ) == XML_DVASPECT_ICON; + aInfo.mbAutoUpdate = rAttribs.getToken( XML_oleUpdate, XML_OLEUPDATE_ONCALL ) == XML_OLEUPDATE_ALWAYS; + aInfo.mbAutoLoad = rAttribs.getBool( XML_autoLoad, false ); + getVmlDrawing().registerOleObject( aInfo ); +} + +void OoxWorksheetFragment::importControl( const AttributeList& rAttribs ) +{ + ::oox::vml::ControlInfo aInfo; + aInfo.setShapeId( rAttribs.getInteger( XML_shapeId, 0 ) ); + aInfo.maFragmentPath = getFragmentPathFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) ); + aInfo.maName = rAttribs.getString( XML_name, OUString() ); + getVmlDrawing().registerControl( aInfo ); +} + +void OoxWorksheetFragment::importDimension( RecordInputStream& rStrm ) +{ + BinRange aBinRange; + aBinRange.read( rStrm ); + CellRangeAddress aRange; + getAddressConverter().convertToCellRangeUnchecked( aRange, aBinRange, getSheetIndex() ); + /* BIFF12 stores the used area, if existing, or "A1" if the sheet is + empty. In case of "A1", the dimension at the WorksheetHelper object + will not be set. If the cell A1 exists, the used area will be updated + while importing the cell. */ + if( (aRange.EndColumn > 0) || (aRange.EndRow > 0) ) + extendUsedArea( aRange ); +} + +void OoxWorksheetFragment::importSheetFormatPr( RecordInputStream& rStrm ) +{ + sal_Int32 nDefaultWidth; + sal_uInt16 nBaseWidth, nDefaultHeight, nFlags; + rStrm >> nDefaultWidth >> nBaseWidth >> nDefaultHeight >> nFlags; + + // base column with + setBaseColumnWidth( nBaseWidth ); + // default width is stored as 1/256th of a character in OOBIN, convert to entire character + setDefaultColumnWidth( static_cast< double >( nDefaultWidth ) / 256.0 ); + // row height is in twips in OOBIN, convert to points; equal flags in BIFF and OOBIN + setDefaultRowSettings( + nDefaultHeight / 20.0, + getFlag( nFlags, BIFF_DEFROW_CUSTOMHEIGHT ), + getFlag( nFlags, BIFF_DEFROW_HIDDEN ), + getFlag( nFlags, BIFF_DEFROW_THICKTOP ), + getFlag( nFlags, BIFF_DEFROW_THICKBOTTOM ) ); +} + +void OoxWorksheetFragment::importCol( RecordInputStream& rStrm ) +{ + ColumnModel aModel; + + sal_Int32 nWidth; + sal_uInt16 nFlags; + rStrm >> aModel.mnFirstCol >> aModel.mnLastCol >> nWidth >> aModel.mnXfId >> nFlags; + + // column indexes are 0-based in OOBIN, but ColumnModel expects 1-based + ++aModel.mnFirstCol; + ++aModel.mnLastCol; + // width is stored as 1/256th of a character in OOBIN, convert to entire character + aModel.mfWidth = static_cast< double >( nWidth ) / 256.0; + // equal flags in BIFF and OOBIN + aModel.mnLevel = extractValue< sal_Int32 >( nFlags, 8, 3 ); + aModel.mbShowPhonetic = getFlag( nFlags, BIFF_COLINFO_SHOWPHONETIC ); + aModel.mbHidden = getFlag( nFlags, BIFF_COLINFO_HIDDEN ); + aModel.mbCollapsed = getFlag( nFlags, BIFF_COLINFO_COLLAPSED ); + // set column properties in the current sheet + setColumnModel( aModel ); +} + +void OoxWorksheetFragment::importMergeCell( RecordInputStream& rStrm ) +{ + BinRange aBinRange; + rStrm >> aBinRange; + CellRangeAddress aRange; + if( getAddressConverter().convertToCellRange( aRange, aBinRange, getSheetIndex(), true, true ) ) + setMergedRange( aRange ); +} + +void OoxWorksheetFragment::importHyperlink( RecordInputStream& rStrm ) +{ + BinRange aBinRange; + rStrm >> aBinRange; + HyperlinkModel aModel; + if( getAddressConverter().convertToCellRange( aModel.maRange, aBinRange, getSheetIndex(), true, true ) ) + { + aModel.maTarget = getRelations().getExternalTargetFromRelId( rStrm.readString() ); + rStrm >> aModel.maLocation >> aModel.maTooltip >> aModel.maDisplay; + setHyperlink( aModel ); + } +} + +void OoxWorksheetFragment::importBrk( RecordInputStream& rStrm, bool bRowBreak ) +{ + PageBreakModel aModel; + sal_Int32 nManual; + rStrm >> aModel.mnColRow >> aModel.mnMin >> aModel.mnMax >> nManual; + aModel.mbManual = nManual != 0; + setPageBreak( aModel, bRowBreak ); +} + +void OoxWorksheetFragment::importDrawing( RecordInputStream& rStrm ) +{ + setDrawingPath( getFragmentPathFromRelId( rStrm.readString() ) ); +} + +void OoxWorksheetFragment::importLegacyDrawing( RecordInputStream& rStrm ) +{ + setVmlDrawingPath( getFragmentPathFromRelId( rStrm.readString() ) ); +} + +void OoxWorksheetFragment::importOleObject( RecordInputStream& rStrm ) +{ + ::oox::vml::OleObjectInfo aInfo; + sal_Int32 nAspect, nUpdateMode, nShapeId; + sal_uInt16 nFlags; + rStrm >> nAspect >> nUpdateMode >> nShapeId >> nFlags >> aInfo.maProgId; + aInfo.mbLinked = getFlag( nFlags, OOBIN_OLEOBJECT_LINKED ); + if( aInfo.mbLinked ) + aInfo.maTargetLink = getFormulaParser().importOleTargetLink( rStrm ); + else + importEmbeddedOleData( aInfo.maEmbeddedData, rStrm.readString() ); + aInfo.setShapeId( nShapeId ); + aInfo.mbShowAsIcon = nAspect == OOBIN_OLEOBJECT_ICON; + aInfo.mbAutoUpdate = nUpdateMode == OOBIN_OLEOBJECT_ALWAYS; + aInfo.mbAutoLoad = getFlag( nFlags, OOBIN_OLEOBJECT_AUTOLOAD ); + getVmlDrawing().registerOleObject( aInfo ); +} + +void OoxWorksheetFragment::importControl( RecordInputStream& rStrm ) +{ + ::oox::vml::ControlInfo aInfo; + aInfo.setShapeId( rStrm.readInt32() ); + aInfo.maFragmentPath = getFragmentPathFromRelId( rStrm.readString() ); + rStrm >> aInfo.maName; + getVmlDrawing().registerControl( aInfo ); +} + +void OoxWorksheetFragment::importEmbeddedOleData( StreamDataSequence& orEmbeddedData, const OUString& rRelId ) +{ + OUString aFragmentPath = getFragmentPathFromRelId( rRelId ); + if( aFragmentPath.getLength() > 0 ) + getBaseFilter().importBinaryData( orEmbeddedData, aFragmentPath ); +} + +// ============================================================================ + +BiffWorksheetFragment::BiffWorksheetFragment( const BiffWorkbookFragmentBase& rParent, ISegmentProgressBarRef xProgressBar, WorksheetType eSheetType, sal_Int16 nSheet ) : + BiffWorksheetFragmentBase( rParent, xProgressBar, eSheetType, nSheet ) +{ +} + +BiffWorksheetFragment::~BiffWorksheetFragment() +{ +} + +bool BiffWorksheetFragment::importFragment() +{ + // initial processing in base class WorksheetHelper + initializeWorksheetImport(); + + // create a SheetDataContext object that implements cell import + BiffSheetDataContext aSheetData( *this ); + + WorkbookSettings& rWorkbookSett = getWorkbookSettings(); + WorksheetSettings& rWorksheetSett = getWorksheetSettings(); + SheetViewSettings& rSheetViewSett = getSheetViewSettings(); + CondFormatBuffer& rCondFormats = getCondFormats(); + PageSettings& rPageSett = getPageSettings(); + + // process all record in this sheet fragment + while( mrStrm.startNextRecord() && (mrStrm.getRecId() != BIFF_ID_EOF) ) + { + if( isBofRecord() ) + { + // skip unknown embedded fragments (BOF/EOF blocks) + skipFragment(); + } + else + { + // cache base stream position to detect if record is already processed + sal_Int64 nStrmPos = mrStrm.tellBase(); + sal_uInt16 nRecId = mrStrm.getRecId(); + + switch( nRecId ) + { + // records in all BIFF versions + case BIFF_ID_BOTTOMMARGIN: rPageSett.importBottomMargin( mrStrm ); break; + case BIFF_ID_CALCCOUNT: rWorkbookSett.importCalcCount( mrStrm ); break; + case BIFF_ID_CALCMODE: rWorkbookSett.importCalcMode( mrStrm ); break; + case BIFF_ID_DEFCOLWIDTH: importDefColWidth(); break; + case BIFF_ID_DELTA: rWorkbookSett.importDelta( mrStrm ); break; + case BIFF2_ID_DIMENSION: importDimension(); break; + case BIFF3_ID_DIMENSION: importDimension(); break; + case BIFF_ID_FOOTER: rPageSett.importFooter( mrStrm ); break; + case BIFF_ID_HEADER: rPageSett.importHeader( mrStrm ); break; + case BIFF_ID_HORPAGEBREAKS: importPageBreaks( true ); break; + case BIFF_ID_ITERATION: rWorkbookSett.importIteration( mrStrm ); break; + case BIFF_ID_LEFTMARGIN: rPageSett.importLeftMargin( mrStrm ); break; + case BIFF_ID_PANE: rSheetViewSett.importPane( mrStrm ); break; + case BIFF_ID_PASSWORD: rWorksheetSett.importPassword( mrStrm ); break; + case BIFF_ID_PRINTGRIDLINES: rPageSett.importPrintGridLines( mrStrm ); break; + case BIFF_ID_PRINTHEADERS: rPageSett.importPrintHeaders( mrStrm ); break; + case BIFF_ID_PROTECT: rWorksheetSett.importProtect( mrStrm ); break; + case BIFF_ID_REFMODE: rWorkbookSett.importRefMode( mrStrm ); break; + case BIFF_ID_RIGHTMARGIN: rPageSett.importRightMargin( mrStrm ); break; + case BIFF_ID_SELECTION: rSheetViewSett.importSelection( mrStrm ); break; + case BIFF_ID_TOPMARGIN: rPageSett.importTopMargin( mrStrm ); break; + case BIFF_ID_VERPAGEBREAKS: importPageBreaks( false ); break; + + // BIFF specific records + default: switch( getBiff() ) + { + case BIFF2: switch( nRecId ) + { + case BIFF_ID_COLUMNDEFAULT: importColumnDefault(); break; + case BIFF_ID_COLWIDTH: importColWidth(); break; + case BIFF2_ID_DEFROWHEIGHT: importDefRowHeight(); break; + case BIFF2_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF3: switch( nRecId ) + { + case BIFF_ID_COLINFO: importColInfo(); break; + case BIFF_ID_DEFCOLWIDTH: importDefColWidth(); break; + case BIFF3_ID_DEFROWHEIGHT: importDefRowHeight(); break; + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_SAVERECALC: rWorkbookSett.importSaveRecalc( mrStrm ); break; + case BIFF_ID_SHEETPR: rWorksheetSett.importSheetPr( mrStrm ); break; + case BIFF_ID_UNCALCED: rWorkbookSett.importUncalced( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + + } + break; + + case BIFF4: switch( nRecId ) + { + case BIFF_ID_COLINFO: importColInfo(); break; + case BIFF3_ID_DEFROWHEIGHT: importDefRowHeight(); break; + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_PAGESETUP: rPageSett.importPageSetup( mrStrm ); break; + case BIFF_ID_SAVERECALC: rWorkbookSett.importSaveRecalc( mrStrm ); break; + case BIFF_ID_SHEETPR: rWorksheetSett.importSheetPr( mrStrm ); break; + case BIFF_ID_STANDARDWIDTH: importStandardWidth(); break; + case BIFF_ID_UNCALCED: rWorkbookSett.importUncalced( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF5: switch( nRecId ) + { + case BIFF_ID_COLINFO: importColInfo(); break; + case BIFF3_ID_DEFROWHEIGHT: importDefRowHeight(); break; + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_MERGEDCELLS: importMergedCells(); break; // #i62300# also in BIFF5 + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_PAGESETUP: rPageSett.importPageSetup( mrStrm ); break; + case BIFF_ID_PTDEFINITION: importPTDefinition(); break; + case BIFF_ID_SAVERECALC: rWorkbookSett.importSaveRecalc( mrStrm ); break; + case BIFF_ID_SCENPROTECT: rWorksheetSett.importScenProtect( mrStrm ); break; + case BIFF_ID_SCL: rSheetViewSett.importScl( mrStrm ); break; + case BIFF_ID_SHEETPR: rWorksheetSett.importSheetPr( mrStrm ); break; + case BIFF_ID_STANDARDWIDTH: importStandardWidth(); break; + case BIFF_ID_UNCALCED: rWorkbookSett.importUncalced( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF8: switch( nRecId ) + { + case BIFF_ID_CFHEADER: rCondFormats.importCfHeader( mrStrm ); break; + case BIFF_ID_CODENAME: rWorksheetSett.importCodeName( mrStrm ); break; + case BIFF_ID_COLINFO: importColInfo(); break; + case BIFF_ID_DATAVALIDATION: importDataValidation(); break; + case BIFF_ID_DATAVALIDATIONS: importDataValidations(); break; + case BIFF3_ID_DEFROWHEIGHT: importDefRowHeight(); break; + case BIFF_ID_HCENTER: rPageSett.importHorCenter( mrStrm ); break; + case BIFF_ID_HYPERLINK: importHyperlink(); break; + case BIFF_ID_LABELRANGES: importLabelRanges(); break; + case BIFF_ID_MERGEDCELLS: importMergedCells(); break; + case BIFF_ID_OBJECTPROTECT: rWorksheetSett.importObjectProtect( mrStrm ); break; + case BIFF_ID_PAGESETUP: rPageSett.importPageSetup( mrStrm ); break; + case BIFF_ID_PHONETICPR: rWorksheetSett.importPhoneticPr( mrStrm ); break; + case BIFF_ID_PICTURE: rPageSett.importPicture( mrStrm ); break; + case BIFF_ID_PTDEFINITION: importPTDefinition(); break; + case BIFF_ID_SAVERECALC: rWorkbookSett.importSaveRecalc( mrStrm ); break; + case BIFF_ID_SCENARIOS: importScenarios(); break; + case BIFF_ID_SCENPROTECT: rWorksheetSett.importScenProtect( mrStrm ); break; + case BIFF_ID_SCL: rSheetViewSett.importScl( mrStrm ); break; + case BIFF_ID_SHEETPR: rWorksheetSett.importSheetPr( mrStrm ); break; + case BIFF_ID_SHAREDFEATHEAD: importSharedFeatHead(); break; + case BIFF_ID_STANDARDWIDTH: importStandardWidth(); break; + case BIFF_ID_UNCALCED: rWorkbookSett.importUncalced( mrStrm ); break; + case BIFF_ID_VCENTER: rPageSett.importVerCenter( mrStrm ); break; + case BIFF3_ID_WINDOW2: rSheetViewSett.importWindow2( mrStrm ); break; + } + break; + + case BIFF_UNKNOWN: break; + } + } + + // record not processed, try cell records + if( mrStrm.tellBase() == nStrmPos ) + aSheetData.importRecord(); + // record still not processed, try pivot table records + if( mxPTContext.get() && (mrStrm.tellBase() == nStrmPos) ) + mxPTContext->importRecord(); + } + } + + // final processing in base class WorksheetHelper + finalizeWorksheetImport(); + return mrStrm.getRecId() == BIFF_ID_EOF; +} + +// private -------------------------------------------------------------------- + +void BiffWorksheetFragment::importColInfo() +{ + sal_uInt16 nFirstCol, nLastCol, nWidth, nXfId, nFlags; + mrStrm >> nFirstCol >> nLastCol >> nWidth >> nXfId >> nFlags; + + ColumnModel aModel; + // column indexes are 0-based in BIFF, but ColumnModel expects 1-based + aModel.mnFirstCol = static_cast< sal_Int32 >( nFirstCol ) + 1; + aModel.mnLastCol = static_cast< sal_Int32 >( nLastCol ) + 1; + // width is stored as 1/256th of a character in BIFF, convert to entire character + aModel.mfWidth = static_cast< double >( nWidth ) / 256.0; + aModel.mnXfId = nXfId; + aModel.mnLevel = extractValue< sal_Int32 >( nFlags, 8, 3 ); + aModel.mbShowPhonetic = getFlag( nFlags, BIFF_COLINFO_SHOWPHONETIC ); + aModel.mbHidden = getFlag( nFlags, BIFF_COLINFO_HIDDEN ); + aModel.mbCollapsed = getFlag( nFlags, BIFF_COLINFO_COLLAPSED ); + // set column properties in the current sheet + setColumnModel( aModel ); +} + +void BiffWorksheetFragment::importColumnDefault() +{ + sal_uInt16 nFirstCol, nLastCol, nXfId; + mrStrm >> nFirstCol >> nLastCol >> nXfId; + setDefaultColumnFormat( nFirstCol, nLastCol, nXfId ); +} + +void BiffWorksheetFragment::importColWidth() +{ + sal_uInt8 nFirstCol, nLastCol; + sal_uInt16 nWidth; + mrStrm >> nFirstCol >> nLastCol >> nWidth; + + ColumnModel aModel; + // column indexes are 0-based in BIFF, but ColumnModel expects 1-based + aModel.mnFirstCol = static_cast< sal_Int32 >( nFirstCol ) + 1; + aModel.mnLastCol = static_cast< sal_Int32 >( nLastCol ) + 1; + // width is stored as 1/256th of a character in BIFF, convert to entire character + aModel.mfWidth = static_cast< double >( nWidth ) / 256.0; + // set column properties in the current sheet + setColumnModel( aModel ); +} + +void BiffWorksheetFragment::importDefColWidth() +{ + /* Stored as entire number of characters without padding pixels, which + will be added in setBaseColumnWidth(). Call has no effect, if a + width has already been set from the STANDARDWIDTH record. */ + setBaseColumnWidth( mrStrm.readuInt16() ); +} + +void BiffWorksheetFragment::importDefRowHeight() +{ + sal_uInt16 nFlags = BIFF_DEFROW_CUSTOMHEIGHT, nHeight; + if( getBiff() != BIFF2 ) + mrStrm >> nFlags; + mrStrm >> nHeight; + if( getBiff() == BIFF2 ) + nHeight &= BIFF2_DEFROW_MASK; + // row height is in twips in BIFF, convert to points + setDefaultRowSettings( + nHeight / 20.0, + getFlag( nFlags, BIFF_DEFROW_CUSTOMHEIGHT ), + getFlag( nFlags, BIFF_DEFROW_HIDDEN ), + getFlag( nFlags, BIFF_DEFROW_THICKTOP ), + getFlag( nFlags, BIFF_DEFROW_THICKBOTTOM ) ); +} + +void BiffWorksheetFragment::importDataValidations() +{ + sal_Int32 nObjId; + mrStrm.skip( 10 ); + mrStrm >> nObjId; + //! TODO: invalidate object id in drawing object manager +} + +namespace { + +OUString lclReadDataValMessage( BiffInputStream& rStrm ) +{ + // empty strings are single NUL characters (string length is 1) + OUString aMessage = rStrm.readUniString( true ); + if( (aMessage.getLength() == 1) && (aMessage[ 0 ] == 0) ) + aMessage = OUString(); + return aMessage; +} + +ApiTokenSequence lclReadDataValFormula( BiffInputStream& rStrm, FormulaParser& rParser ) +{ + sal_uInt16 nFmlaSize = rStrm.readuInt16(); + rStrm.skip( 2 ); + // enable NUL characters, string list is single tStr token with NUL separators + TokensFormulaContext aContext( true, false, true ); + rParser.importFormula( aContext, rStrm, &nFmlaSize ); + return aContext.getTokens(); +} + +} // namespace + +void BiffWorksheetFragment::importDataValidation() +{ + ValidationModel aModel; + + // flags + sal_uInt32 nFlags; + mrStrm >> nFlags; + aModel.setBinType( extractValue< sal_uInt8 >( nFlags, 0, 4 ) ); + aModel.setBinOperator( extractValue< sal_uInt8 >( nFlags, 20, 4 ) ); + aModel.setBinErrorStyle( extractValue< sal_uInt8 >( nFlags, 4, 3 ) ); + aModel.mbAllowBlank = getFlag( nFlags, BIFF_DATAVAL_ALLOWBLANK ); + aModel.mbNoDropDown = getFlag( nFlags, BIFF_DATAVAL_NODROPDOWN ); + aModel.mbShowInputMsg = getFlag( nFlags, BIFF_DATAVAL_SHOWINPUT ); + aModel.mbShowErrorMsg = getFlag( nFlags, BIFF_DATAVAL_SHOWERROR ); + + // message strings + aModel.maInputTitle = lclReadDataValMessage( mrStrm ); + aModel.maErrorTitle = lclReadDataValMessage( mrStrm ); + aModel.maInputMessage = lclReadDataValMessage( mrStrm ); + aModel.maErrorMessage = lclReadDataValMessage( mrStrm ); + + // condition formula(s) + FormulaParser& rParser = getFormulaParser(); + aModel.maTokens1 = lclReadDataValFormula( mrStrm, rParser ); + aModel.maTokens2 = lclReadDataValFormula( mrStrm, rParser ); + // process string list of a list validation (convert to list of string tokens) + if( (aModel.mnType == XML_list) && getFlag( nFlags, BIFF_DATAVAL_STRINGLIST ) ) + rParser.convertStringToStringList( aModel.maTokens1, '\0', true ); + + // cell range list + BinRangeList aRanges; + mrStrm >> aRanges; + getAddressConverter().convertToCellRangeList( aModel.maRanges, aRanges, getSheetIndex(), true ); + + // set validation data + setValidation( aModel ); +} + +void BiffWorksheetFragment::importDimension() +{ + // 32-bit row indexes in BIFF8 + bool bInt32Rows = (mrStrm.getRecId() == BIFF3_ID_DIMENSION) && (getBiff() == BIFF8); + BinRange aBinRange; + aBinRange.read( mrStrm, true, bInt32Rows ); + /* BIFF stores the used area with end column and end row increased by 1 + (first unused column and row). */ + if( (aBinRange.maFirst.mnCol < aBinRange.maLast.mnCol) && (aBinRange.maFirst.mnRow < aBinRange.maLast.mnRow) ) + { + // reduce range to used area + --aBinRange.maLast.mnCol; + --aBinRange.maLast.mnRow; + CellRangeAddress aRange; + getAddressConverter().convertToCellRangeUnchecked( aRange, aBinRange, getSheetIndex() ); + extendUsedArea( aRange ); + } +} + +void BiffWorksheetFragment::importHyperlink() +{ + HyperlinkModel aModel; + + // read cell range for the hyperlink + BinRange aBiffRange; + mrStrm >> aBiffRange; + // #i80006# Excel silently ignores invalid hi-byte of column index (TODO: everywhere?) + aBiffRange.maFirst.mnCol &= 0xFF; + aBiffRange.maLast.mnCol &= 0xFF; + if( !getAddressConverter().convertToCellRange( aModel.maRange, aBiffRange, getSheetIndex(), true, true ) ) + return; + + // try to read the StdHlink data + if( !::oox::ole::OleHelper::importStdHlink( aModel, mrStrm, true ) ) + return; + + // try to read the optional following SCREENTIP record + if( (mrStrm.getNextRecId() == BIFF_ID_SCREENTIP) && mrStrm.startNextRecord() ) + { + mrStrm.skip( 2 ); // repeated record id + // the cell range, again + mrStrm >> aBiffRange; + CellRangeAddress aRange; + if( getAddressConverter().convertToCellRange( aRange, aBiffRange, getSheetIndex(), true, true ) && + (aRange.StartColumn == aModel.maRange.StartColumn) && + (aRange.StartRow == aModel.maRange.StartRow) && + (aRange.EndColumn == aModel.maRange.EndColumn) && + (aRange.EndRow == aModel.maRange.EndRow) ) + { + /* This time, we have no string length, no flag field, and a + null-terminated 16-bit character array. */ + aModel.maTooltip = mrStrm.readNulUnicodeArray(); + } + } + + // store the hyperlink settings + setHyperlink( aModel ); +} + +void BiffWorksheetFragment::importLabelRanges() +{ + BinRangeList aBiffRowRanges, aBiffColRanges; + mrStrm >> aBiffRowRanges >> aBiffColRanges; + ApiCellRangeList aColRanges, aRowRanges; + getAddressConverter().convertToCellRangeList( aColRanges, aBiffColRanges, getSheetIndex(), true ); + getAddressConverter().convertToCellRangeList( aRowRanges, aBiffRowRanges, getSheetIndex(), true ); + setLabelRanges( aColRanges, aRowRanges ); +} + +void BiffWorksheetFragment::importMergedCells() +{ + BinRangeList aBiffRanges; + mrStrm >> aBiffRanges; + ApiCellRangeList aRanges; + getAddressConverter().convertToCellRangeList( aRanges, aBiffRanges, getSheetIndex(), true ); + for( ApiCellRangeList::const_iterator aIt = aRanges.begin(), aEnd = aRanges.end(); aIt != aEnd; ++aIt ) + setMergedRange( *aIt ); +} + +void BiffWorksheetFragment::importPageBreaks( bool bRowBreak ) +{ + PageBreakModel aModel; + aModel.mbManual = true; // only manual breaks stored in BIFF + bool bBiff8 = getBiff() == BIFF8; // skip start/end columns or rows in BIFF8 + + sal_uInt16 nCount; + mrStrm >> nCount; + for( sal_uInt16 nIndex = 0; !mrStrm.isEof() && (nIndex < nCount); ++nIndex ) + { + aModel.mnColRow = mrStrm.readuInt16(); + setPageBreak( aModel, bRowBreak ); + if( bBiff8 ) + mrStrm.skip( 4 ); + } +} + +void BiffWorksheetFragment::importPTDefinition() +{ + mxPTContext.reset( new BiffPivotTableContext( *this, getPivotTables().createPivotTable() ) ); + mxPTContext->importRecord(); +} + +void BiffWorksheetFragment::importScenarios() +{ + getScenarios().createSheetScenarios( getSheetIndex() ).importScenarios( mrStrm ); +} + +void BiffWorksheetFragment::importSharedFeatHead() +{ + mrStrm.skip( 12 ); + sal_uInt16 nType = mrStrm.readuInt16(); + mrStrm.skip( 5 ); + switch( nType ) + { + case BIFF_SHRFEATHEAD_SHEETPROT: + if( mrStrm.getRemaining() >= 4 ) + getWorksheetSettings().importSheetProtection( mrStrm ); + break; + } +} + +void BiffWorksheetFragment::importStandardWidth() +{ + sal_uInt16 nWidth; + mrStrm >> nWidth; + // width is stored as 1/256th of a character in BIFF, convert to entire character + double fWidth = static_cast< double >( nWidth ) / 256.0; + // set as default width, will override the width from DEFCOLWIDTH record + setDefaultColumnWidth( fWidth ); +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/worksheethelper.cxx b/oox/source/xls/worksheethelper.cxx new file mode 100644 index 000000000000..d2d42f2be369 --- /dev/null +++ b/oox/source/xls/worksheethelper.cxx @@ -0,0 +1,2295 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/worksheethelper.hxx" +#include <algorithm> +#include <utility> +#include <list> +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/sheet/TableValidationVisibility.hpp> +#include <com/sun/star/sheet/ValidationType.hpp> +#include <com/sun/star/sheet/ValidationAlertStyle.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSheetCellRangeContainer.hpp> +#include <com/sun/star/sheet/XSheetCondition.hpp> +#include <com/sun/star/sheet/XCellAddressable.hpp> +#include <com/sun/star/sheet/XCellRangeAddressable.hpp> +#include <com/sun/star/sheet/XFormulaTokens.hpp> +#include <com/sun/star/sheet/XMultiFormulaTokens.hpp> +#include <com/sun/star/sheet/XSheetOutline.hpp> +#include <com/sun/star/sheet/XMultipleOperation.hpp> +#include <com/sun/star/sheet/XLabelRanges.hpp> +#include <com/sun/star/table/XColumnRowRange.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/util/NumberFormat.hpp> +#include <com/sun/star/util/XMergeable.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include "properties.hxx" +#include "tokens.hxx" +#include "oox/helper/containerhelper.hxx" +#include "oox/helper/propertyset.hxx" +#include "oox/core/filterbase.hxx" +#include "oox/xls/addressconverter.hxx" +#include "oox/xls/commentsbuffer.hxx" +#include "oox/xls/condformatbuffer.hxx" +#include "oox/xls/drawingfragment.hxx" +#include "oox/xls/formulaparser.hxx" +#include "oox/xls/pagesettings.hxx" +#include "oox/xls/sharedformulabuffer.hxx" +#include "oox/xls/sharedstringsbuffer.hxx" +#include "oox/xls/stylesbuffer.hxx" +#include "oox/xls/unitconverter.hxx" +#include "oox/xls/viewsettings.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/xls/worksheetbuffer.hxx" +#include "oox/xls/worksheetsettings.hxx" + +using ::rtl::OUString; +using ::rtl::OUStringBuffer; +using ::com::sun::star::awt::Point; +using ::com::sun::star::awt::Rectangle; +using ::com::sun::star::awt::Size; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::drawing::XDrawPage; +using ::com::sun::star::drawing::XDrawPageSupplier; +using ::com::sun::star::lang::Locale; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::sheet::ConditionOperator; +using ::com::sun::star::sheet::ValidationType; +using ::com::sun::star::sheet::ValidationAlertStyle; +using ::com::sun::star::sheet::XCellAddressable; +using ::com::sun::star::sheet::XCellRangeAddressable; +using ::com::sun::star::sheet::XFormulaTokens; +using ::com::sun::star::sheet::XLabelRanges; +using ::com::sun::star::sheet::XMultiFormulaTokens; +using ::com::sun::star::sheet::XMultipleOperation; +using ::com::sun::star::sheet::XSheetCellRangeContainer; +using ::com::sun::star::sheet::XSheetCellRanges; +using ::com::sun::star::sheet::XSheetCondition; +using ::com::sun::star::sheet::XSheetOutline; +using ::com::sun::star::sheet::XSpreadsheet; +using ::com::sun::star::table::BorderLine; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::table::XCell; +using ::com::sun::star::table::XCellRange; +using ::com::sun::star::table::XColumnRowRange; +using ::com::sun::star::table::XTableColumns; +using ::com::sun::star::table::XTableRows; +using ::com::sun::star::text::XText; +using ::com::sun::star::text::XTextContent; +using ::com::sun::star::text::XTextRange; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +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::util::DateTime; +using ::com::sun::star::util::XMergeable; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::com::sun::star::util::XNumberFormatTypes; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +void lclUpdateProgressBar( ISegmentProgressBarRef xProgressBar, const CellRangeAddress& rUsedArea, sal_Int32 nRow ) +{ + if( xProgressBar.get() && (rUsedArea.StartRow <= nRow) && (nRow <= rUsedArea.EndRow) ) + { + double fPosition = static_cast< double >( nRow - rUsedArea.StartRow + 1 ) / (rUsedArea.EndRow - rUsedArea.StartRow + 1); + if( xProgressBar->getPosition() < fPosition ) + xProgressBar->setPosition( fPosition ); + } +} + +void lclUpdateProgressBar( ISegmentProgressBarRef xProgressBar, double fPosition ) +{ + if( xProgressBar.get() ) + xProgressBar->setPosition( fPosition ); +} + +// ---------------------------------------------------------------------------- + +struct ValueRange +{ + sal_Int32 mnFirst; + sal_Int32 mnLast; + + inline explicit ValueRange( sal_Int32 nValue ) : mnFirst( nValue ), mnLast( nValue ) {} + inline explicit ValueRange( sal_Int32 nFirst, sal_Int32 nLast ) : mnFirst( nFirst ), mnLast( nLast ) {} +}; + +typedef ::std::vector< ValueRange > ValueRangeVector; + +// ---------------------------------------------------------------------------- + +struct ValueRangeComp +{ + inline bool operator()( const ValueRange& rRange, sal_Int32 nValue ) const { return rRange.mnLast < nValue; } +}; + +typedef ::std::vector< ValueRange > ValueRangeVector; + +// ---------------------------------------------------------------------------- + +class ValueRangeSet +{ +public: + inline explicit ValueRangeSet() {} + + void insert( sal_Int32 nValue ); + void intersect( ValueRangeVector& orRanges, sal_Int32 nFirst, sal_Int32 nLast ) const; + +private: + ValueRangeVector maData; +}; + +void ValueRangeSet::insert( sal_Int32 nValue ) +{ + // find the first range that contains nValue or that follows nValue + ValueRangeVector::iterator aBeg = maData.begin(); + ValueRangeVector::iterator aEnd = maData.end(); + ValueRangeVector::iterator aNext = ::std::lower_bound( aBeg, aEnd, nValue, ValueRangeComp() ); + + // nothing to do if found range contains nValue + if( (aNext == aEnd) || (nValue < aNext->mnFirst) ) + { + ValueRangeVector::iterator aPrev = (aNext == aBeg) ? aEnd : (aNext - 1); + bool bJoinPrev = (aPrev != aEnd) && (aPrev->mnLast + 1 == nValue); + bool bJoinNext = (aNext != aEnd) && (aNext->mnFirst - 1 == nValue); + if( bJoinPrev && bJoinNext ) + { + aPrev->mnLast = aNext->mnLast; + maData.erase( aNext ); + } + else if( bJoinPrev ) + ++aPrev->mnLast; + else if( bJoinNext ) + --aNext->mnFirst; + else + maData.insert( aNext, ValueRange( nValue ) ); + } +} + +void ValueRangeSet::intersect( ValueRangeVector& orRanges, sal_Int32 nFirst, sal_Int32 nLast ) const +{ + orRanges.clear(); + // find the range that contains nFirst or the first range that follows nFirst + ValueRangeVector::const_iterator aIt = ::std::lower_bound( maData.begin(), maData.end(), nFirst, ValueRangeComp() ); + for( ValueRangeVector::const_iterator aEnd = maData.end(); (aIt != aEnd) && (aIt->mnFirst <= nLast); ++aIt ) + orRanges.push_back( ValueRange( ::std::max( aIt->mnFirst, nFirst ), ::std::min( aIt->mnLast, nLast ) ) ); +} + +} // namespace + +// ============================================================================ +// ============================================================================ + +void CellModel::reset() +{ + mxCell.clear(); + maValueStr = maFormulaRef = OUString(); + mnCellType = mnFormulaType = XML_TOKEN_INVALID; + mnSharedId = mnXfId = mnNumFmtId = -1; + mbHasValueStr = mbShowPhonetic = false; +} + +// ---------------------------------------------------------------------------- + +DataTableModel::DataTableModel() : + mb2dTable( false ), + mbRowTable( false ), + mbRef1Deleted( false ), + mbRef2Deleted( false ) +{ +} + +// ---------------------------------------------------------------------------- + +ColumnModel::ColumnModel() : + mnFirstCol( -1 ), + mnLastCol( -1 ), + mfWidth( 0.0 ), + mnXfId( -1 ), + mnLevel( 0 ), + mbShowPhonetic( false ), + mbHidden( false ), + mbCollapsed( false ) +{ +} + +bool ColumnModel::tryExpand( const ColumnModel& rModel ) +{ + bool bExpandable = + (mnFirstCol <= rModel.mnFirstCol) && + (rModel.mnFirstCol <= mnLastCol + 1) && + (mfWidth == rModel.mfWidth) && + // ignore mnXfId, cell formatting is always set directly + (mnLevel == rModel.mnLevel) && + (mbHidden == rModel.mbHidden) && + (mbCollapsed == rModel.mbCollapsed); + + if( bExpandable ) + mnLastCol = rModel.mnLastCol; + return bExpandable; +} + +// ---------------------------------------------------------------------------- + +RowModel::RowModel() : + mnFirstRow( -1 ), + mnLastRow( -1 ), + mfHeight( 0.0 ), + mnXfId( -1 ), + mnLevel( 0 ), + mbCustomHeight( false ), + mbCustomFormat( false ), + mbShowPhonetic( false ), + mbHidden( false ), + mbCollapsed( false ), + mbThickTop( false ), + mbThickBottom( false ) +{ +} + +bool RowModel::tryExpand( const RowModel& rModel ) +{ + bool bExpandable = + (mnFirstRow <= rModel.mnFirstRow) && + (rModel.mnFirstRow <= mnLastRow + 1) && + (mfHeight == rModel.mfHeight) && + // ignore mnXfId, mbCustomFormat, mbShowPhonetic - cell formatting is always set directly + (mnLevel == rModel.mnLevel) && + (mbCustomHeight == rModel.mbCustomHeight) && + (mbHidden == rModel.mbHidden) && + (mbCollapsed == rModel.mbCollapsed); + + if( bExpandable ) + mnLastRow = rModel.mnLastRow; + return bExpandable; +} + +// ---------------------------------------------------------------------------- + +PageBreakModel::PageBreakModel() : + mnColRow( 0 ), + mbManual( false ) +{ +} + +// ---------------------------------------------------------------------------- + +HyperlinkModel::HyperlinkModel() +{ +} + +// ---------------------------------------------------------------------------- + +ValidationModel::ValidationModel() : + mnType( XML_none ), + mnOperator( XML_between ), + mnErrorStyle( XML_stop ), + mbShowInputMsg( false ), + mbShowErrorMsg( false ), + mbNoDropDown( false ), + mbAllowBlank( false ) +{ +} + +void ValidationModel::setBinType( sal_uInt8 nType ) +{ + static const sal_Int32 spnTypeIds[] = { + XML_none, XML_whole, XML_decimal, XML_list, XML_date, XML_time, XML_textLength, XML_custom }; + mnType = STATIC_ARRAY_SELECT( spnTypeIds, nType, XML_none ); +} + +void ValidationModel::setBinOperator( sal_uInt8 nOperator ) +{ + static const sal_Int32 spnOperators[] = { + XML_between, XML_notBetween, XML_equal, XML_notEqual, + XML_greaterThan, XML_lessThan, XML_greaterThanOrEqual, XML_lessThanOrEqual }; + mnOperator = STATIC_ARRAY_SELECT( spnOperators, nOperator, XML_TOKEN_INVALID ); +} + +void ValidationModel::setBinErrorStyle( sal_uInt8 nErrorStyle ) +{ + static const sal_Int32 spnErrorStyles[] = { XML_stop, XML_warning, XML_information }; + mnErrorStyle = STATIC_ARRAY_SELECT( spnErrorStyles, nErrorStyle, XML_stop ); +} + +// ============================================================================ +// ============================================================================ + +class WorksheetData : public WorkbookHelper +{ +public: + explicit WorksheetData( + const WorkbookHelper& rHelper, + ISegmentProgressBarRef xProgressBar, + WorksheetType eSheetType, + sal_Int16 nSheet ); + + /** Returns true, if this helper refers to an existing Calc sheet. */ + inline bool isValidSheet() const { return mxSheet.is(); } + + /** Returns a cell formula simulating the passed boolean value. */ + const OUString& getBooleanFormula( bool bValue ) const; + + /** Returns the type of this sheet. */ + inline WorksheetType getSheetType() const { return meSheetType; } + /** Returns the index of the current sheet. */ + inline sal_Int16 getSheetIndex() const { return maUsedArea.Sheet; } + /** Returns the XSpreadsheet interface of the current sheet. */ + inline const ::com::sun::star::uno::Reference< ::com::sun::star::sheet::XSpreadsheet >& + getSheet() const { return mxSheet; } + + /** Returns the XCell interface for the passed cell address. */ + Reference< XCell > getCell( const CellAddress& rAddress ) const; + /** Returns the XCellRange interface for the passed cell range address. */ + Reference< XCellRange > getCellRange( const CellRangeAddress& rRange ) const; + /** Returns the XSheetCellRanges interface for the passed cell range addresses. */ + Reference< XSheetCellRanges > getCellRangeList( const ApiCellRangeList& rRanges ) const; + + /** Returns the XCellRange interface for a column. */ + Reference< XCellRange > getColumn( sal_Int32 nCol ) const; + /** Returns the XCellRange interface for a row. */ + Reference< XCellRange > getRow( sal_Int32 nRow ) const; + + /** Returns the XTableColumns interface for a range of columns. */ + Reference< XTableColumns > getColumns( sal_Int32 nFirstCol, sal_Int32 nLastCol ) const; + /** Returns the XTableRows interface for a range of rows. */ + Reference< XTableRows > getRows( sal_Int32 nFirstRow, sal_Int32 nLastRow ) const; + + /** Returns the XDrawPage interface of the draw page of the current sheet. */ + Reference< XDrawPage > getDrawPage() const; + /** Returns the size of the entire drawing page in 1/100 mm. */ + Size getDrawPageSize() const; + + /** Returns the absolute position of the top-left corner of the cell in 1/100 mm. */ + Point getCellPosition( sal_Int32 nCol, sal_Int32 nRow ) const; + /** Returns the size of the cell in 1/100 mm. */ + Size getCellSize( sal_Int32 nCol, sal_Int32 nRow ) const; + + /** Returns the address of the cell that contains the passed point in 1/100 mm. */ + CellAddress getCellAddressFromPosition( const Point& rPosition, const Size& rDrawPageSize ) const; + /** Returns the cell range address that contains the passed rectangle in 1/100 mm. */ + CellRangeAddress getCellRangeFromRectangle( const Rectangle& rRect ) const; + + /** Returns the worksheet settings object. */ + inline WorksheetSettings& getWorksheetSettings() { return maSheetSett; } + /** Returns the buffer containing all shared formulas in this sheet. */ + inline SharedFormulaBuffer& getSharedFormulas() { return maSharedFmlas; } + /** Returns the conditional formattings in this sheet. */ + inline CondFormatBuffer& getCondFormats() { return maCondFormats; } + /** Returns the buffer for all cell comments in this sheet. */ + inline CommentsBuffer& getComments() { return maComments; } + /** Returns the page/print settings for this sheet. */ + inline PageSettings& getPageSettings() { return maPageSett; } + /** Returns the view settings for this sheet. */ + inline SheetViewSettings& getSheetViewSettings() { return maSheetViewSett; } + /** Returns the VML drawing page for this sheet (OOX only!). */ + inline VmlDrawing& getVmlDrawing() { return *mxVmlDrawing; } + + /** Changes the current sheet type. */ + inline void setSheetType( WorksheetType eSheetType ) { meSheetType = eSheetType; } + /** Stores the cell format at the passed address. */ + void setCellFormat( const CellModel& rModel ); + /** Merges the cells in the passed cell range. */ + void setMergedRange( const CellRangeAddress& rRange ); + /** Sets a column or row page break described in the passed struct. */ + void setPageBreak( const PageBreakModel& rModel, bool bRowBreak ); + /** Inserts the hyperlink URL into the spreadsheet. */ + void setHyperlink( const HyperlinkModel& rModel ); + /** Inserts the data validation settings into the spreadsheet. */ + void setValidation( const ValidationModel& rModel ); + /** Sets the path to the DrawingML fragment of this sheet. */ + void setDrawingPath( const OUString& rDrawingPath ); + /** Sets the path to the legacy VML drawing fragment of this sheet. */ + void setVmlDrawingPath( const OUString& rVmlDrawingPath ); + + /** Extends the used area of this sheet by the passed cell position. */ + void extendUsedArea( const CellAddress& rAddress ); + /** Extends the used area of this sheet by the passed cell range. */ + void extendUsedArea( const CellRangeAddress& rRange ); + /** Extends the shape bounding box by the position and size of the passed rectangle. */ + void extendShapeBoundingBox( const Rectangle& rShapeRect ); + + /** Sets base width for all columns (without padding pixels). This value + is only used, if base width has not been set with setDefaultColumnWidth(). */ + void setBaseColumnWidth( sal_Int32 nWidth ); + /** Sets default width for all columns. This function overrides the base + width set with the setBaseColumnWidth() function. */ + void setDefaultColumnWidth( double fWidth ); + /** Sets column settings for a specific column range. + @descr Column default formatting is converted directly, other settings + are cached and converted in the finalizeImport() call. */ + void setColumnModel( const ColumnModel& rModel ); + + /** Sets default height and hidden state for all unused rows in the sheet. */ + void setDefaultRowSettings( double fHeight, bool bCustomHeight, bool bHidden, bool bThickTop, bool bThickBottom ); + /** Sets row settings for a specific row. + @descr Row default formatting is converted directly, other settings + are cached and converted in the finalizeImport() call. */ + void setRowModel( const RowModel& rModel ); + + /** Converts column default cell formatting. */ + void convertColumnFormat( sal_Int32 nFirstCol, sal_Int32 nLastCol, sal_Int32 nXfId ) const; + /** Converts row default cell formatting. */ + void convertRowFormat( sal_Int32 nFirstRow, sal_Int32 nLastRow, sal_Int32 nXfId ) const; + + /** Initial conversion before importing the worksheet. */ + void initializeWorksheetImport(); + /** Final conversion after importing the worksheet. */ + void finalizeWorksheetImport(); + +private: + typedef ::std::vector< sal_Int32 > OutlineLevelVec; + typedef ::std::map< sal_Int32, ColumnModel > ColumnModelMap; + typedef ::std::map< sal_Int32, RowModel > RowModelMap; + typedef ::std::list< HyperlinkModel > HyperlinkModelList; + typedef ::std::list< ValidationModel > ValidationModelList; + + struct XfIdRowRange + { + sal_Int32 mnFirstRow; /// Index of first row. + sal_Int32 mnLastRow; /// Index of last row. + sal_Int32 mnXfId; /// XF identifier for the row range. + + explicit XfIdRowRange(); + bool intersects( const CellRangeAddress& rRange ) const; + void set( sal_Int32 nFirstRow, sal_Int32 nLastRow, sal_Int32 nXfId ); + bool tryExpand( sal_Int32 nFirstRow, sal_Int32 nLastRow, sal_Int32 nXfId ); + }; + + struct XfIdRange + { + CellRangeAddress maRange; /// The formatted cell range. + sal_Int32 mnXfId; /// XF identifier for the range. + sal_Int32 mnNumFmtId; /// Number format id overriding the XF. + + void set( const CellModel& rModel ); + bool tryExpand( const CellModel& rModel ); + bool tryMerge( const XfIdRange& rXfIdRange ); + }; + + struct MergedRange + { + CellRangeAddress maRange; /// The formatted cell range. + sal_Int32 mnHorAlign; /// Horizontal alignment in the range. + + explicit MergedRange( const CellRangeAddress& rRange ); + explicit MergedRange( const CellAddress& rAddress, sal_Int32 nHorAlign ); + bool tryExpand( const CellAddress& rAddress, sal_Int32 nHorAlign ); + }; + + typedef ::std::pair< sal_Int32, sal_Int32 > RowColKey; + typedef ::std::map< RowColKey, XfIdRange > XfIdRangeMap; + typedef ::std::list< MergedRange > MergedRangeList; + + /** Writes all cell formatting attributes to the passed row range. */ + void writeXfIdRowRangeProperties( const XfIdRowRange& rXfIdRowRange ) const; + /** Writes all cell formatting attributes to the passed cell range. */ + void writeXfIdRangeProperties( const XfIdRange& rXfIdRange ) const; + /** Tries to merge the ranges last inserted in maXfIdRanges with existing ranges. */ + void mergeXfIdRanges(); + /** Finalizes the remaining ranges in maXfIdRanges. */ + void finalizeXfIdRanges(); + + /** Inserts all imported hyperlinks into their cell ranges. */ + void finalizeHyperlinkRanges() const; + /** Generates the final URL for the passed hyperlink. */ + OUString getHyperlinkUrl( const HyperlinkModel& rHyperlink ) const; + /** Inserts a hyperlinks into the specified cell. */ + void insertHyperlink( const CellAddress& rAddress, const OUString& rUrl ) const; + + /** Inserts all imported data validations into their cell ranges. */ + void finalizeValidationRanges() const; + + /** Merges all cached merged ranges and updates right/bottom cell borders. */ + void finalizeMergedRanges(); + /** Merges the passed merged range and updates right/bottom cell borders. */ + void finalizeMergedRange( const CellRangeAddress& rRange ); + + /** Imports the drawing layer of the sheet (DrawingML part). */ + void finalizeDrawing(); + /** Imports the drawing layer of the sheet (VML part). */ + void finalizeVmlDrawing(); + /** Extends the used cell area with the area used by drawing objects. */ + void finalizeUsedArea(); + + /** Converts column properties for all columns in the sheet. */ + void convertColumns(); + /** Converts column properties. */ + void convertColumns( OutlineLevelVec& orColLevels, sal_Int32 nFirstCol, sal_Int32 nLastCol, const ColumnModel& rModel ); + + /** Converts row properties for all rows in the sheet. */ + void convertRows(); + /** Converts row properties. */ + void convertRows( OutlineLevelVec& orRowLevels, sal_Int32 nFirstRow, sal_Int32 nLastRow, const RowModel& rModel, double fDefHeight = -1.0 ); + + /** Converts outline grouping for the passed column or row. */ + void convertOutlines( OutlineLevelVec& orLevels, sal_Int32 nColRow, sal_Int32 nLevel, bool bCollapsed, bool bRows ); + /** Groups columns or rows for the given range. */ + void groupColumnsOrRows( sal_Int32 nFirstColRow, sal_Int32 nLastColRow, bool bCollapsed, bool bRows ); + +private: + typedef ::std::auto_ptr< VmlDrawing > VmlDrawingPtr; + + const OUString maTrueFormula; /// Replacement formula for TRUE boolean cells. + const OUString maFalseFormula; /// Replacement formula for FALSE boolean cells. + const OUString maSheetCellRanges; /// Service name for a SheetCellRanges object. + const OUString maUrlTextField; /// Service name for a URL text field. + const CellAddress& mrMaxApiPos; /// Reference to maximum Calc cell address from address converter. + CellRangeAddress maUsedArea; /// Used area of the sheet, and sheet index of the sheet. + ColumnModel maDefColModel; /// Default column formatting. + ColumnModelMap maColModels; /// Columns sorted by first column index. + RowModel maDefRowModel; /// Default row formatting. + RowModelMap maRowModels; /// Rows sorted by row index. + HyperlinkModelList maHyperlinks; /// Cell ranges containing hyperlinks. + ValidationModelList maValidations; /// Cell ranges containing data validation settings. + XfIdRowRange maXfIdRowRange; /// Cached XF identifier for a range of rows. + XfIdRangeMap maXfIdRanges; /// Collected XF identifiers for cell ranges. + MergedRangeList maMergedRanges; /// Merged cell ranges. + MergedRangeList maCenterFillRanges; /// Merged cell ranges from 'center across' or 'fill' alignment. + ValueRangeSet maManualRowHeights; /// Rows that need manual height independent from own settings. + WorksheetSettings maSheetSett; /// Global settings for this sheet. + SharedFormulaBuffer maSharedFmlas; /// Buffer for shared formulas in this sheet. + CondFormatBuffer maCondFormats; /// Buffer for conditional formattings. + CommentsBuffer maComments; /// Buffer for all cell comments in this sheet. + PageSettings maPageSett; /// Page/print settings for this sheet. + SheetViewSettings maSheetViewSett; /// View settings for this sheet. + VmlDrawingPtr mxVmlDrawing; /// Collection of all VML shapes. + OUString maDrawingPath; /// Path to DrawingML fragment. + OUString maVmlDrawingPath; /// Path to legacy VML drawing fragment. + Rectangle maShapeBoundingBox; /// Bounding box for all shapes from all drawings. + ISegmentProgressBarRef mxProgressBar; /// Sheet progress bar. + ISegmentProgressBarRef mxRowProgress; /// Progress bar for row/cell processing. + ISegmentProgressBarRef mxFinalProgress; /// Progress bar for finalization. + WorksheetType meSheetType; /// Type of this sheet. + Reference< XSpreadsheet > mxSheet; /// Reference to the current sheet. + bool mbHasDefWidth; /// True = default column width is set from defaultColWidth attribute. +}; + +// ---------------------------------------------------------------------------- + +WorksheetData::WorksheetData( const WorkbookHelper& rHelper, ISegmentProgressBarRef xProgressBar, WorksheetType eSheetType, sal_Int16 nSheet ) : + WorkbookHelper( rHelper ), + maTrueFormula( CREATE_OUSTRING( "=TRUE()" ) ), + maFalseFormula( CREATE_OUSTRING( "=FALSE()" ) ), + maSheetCellRanges( CREATE_OUSTRING( "com.sun.star.sheet.SheetCellRanges" ) ), + maUrlTextField( CREATE_OUSTRING( "com.sun.star.text.TextField.URL" ) ), + mrMaxApiPos( rHelper.getAddressConverter().getMaxApiAddress() ), + maUsedArea( nSheet, SAL_MAX_INT32, SAL_MAX_INT32, -1, -1 ), + maSheetSett( *this ), + maSharedFmlas( *this ), + maCondFormats( *this ), + maComments( *this ), + maPageSett( *this ), + maSheetViewSett( *this ), + mxProgressBar( xProgressBar ), + meSheetType( eSheetType ), + mbHasDefWidth( false ) +{ + mxSheet = getSheetFromDoc( nSheet ); + if( !mxSheet.is() ) + maUsedArea.Sheet = -1; + + // default column settings (width and hidden state may be updated later) + maDefColModel.mfWidth = 8.5; + maDefColModel.mnXfId = -1; + maDefColModel.mnLevel = 0; + maDefColModel.mbHidden = false; + maDefColModel.mbCollapsed = false; + + // default row settings (height and hidden state may be updated later) + maDefRowModel.mfHeight = 0.0; + maDefRowModel.mnXfId = -1; + maDefRowModel.mnLevel = 0; + maDefRowModel.mbCustomHeight = false; + maDefRowModel.mbCustomFormat = false; + maDefRowModel.mbShowPhonetic = false; + maDefRowModel.mbHidden = false; + maDefRowModel.mbCollapsed = false; + + // buffers + if( getFilterType() == FILTER_OOX ) + mxVmlDrawing.reset( new VmlDrawing( *this ) ); + + // prepare progress bars + if( mxProgressBar.get() ) + { + mxRowProgress = mxProgressBar->createSegment( 0.5 ); + mxFinalProgress = mxProgressBar->createSegment( 0.5 ); + } +} + +const OUString& WorksheetData::getBooleanFormula( bool bValue ) const +{ + return bValue ? maTrueFormula : maFalseFormula; +} + +Reference< XCell > WorksheetData::getCell( const CellAddress& rAddress ) const +{ + Reference< XCell > xCell; + if( mxSheet.is() ) try + { + xCell = mxSheet->getCellByPosition( rAddress.Column, rAddress.Row ); + } + catch( Exception& ) + { + } + return xCell; +} + +Reference< XCellRange > WorksheetData::getCellRange( const CellRangeAddress& rRange ) const +{ + Reference< XCellRange > xRange; + if( mxSheet.is() ) try + { + xRange = mxSheet->getCellRangeByPosition( rRange.StartColumn, rRange.StartRow, rRange.EndColumn, rRange.EndRow ); + } + catch( Exception& ) + { + } + return xRange; +} + +Reference< XSheetCellRanges > WorksheetData::getCellRangeList( const ApiCellRangeList& rRanges ) const +{ + Reference< XSheetCellRanges > xRanges; + if( mxSheet.is() && !rRanges.empty() ) try + { + xRanges.set( getDocumentFactory()->createInstance( maSheetCellRanges ), UNO_QUERY_THROW ); + Reference< XSheetCellRangeContainer > xRangeCont( xRanges, UNO_QUERY_THROW ); + xRangeCont->addRangeAddresses( ContainerHelper::vectorToSequence( rRanges ), sal_False ); + } + catch( Exception& ) + { + } + return xRanges; +} + +Reference< XCellRange > WorksheetData::getColumn( sal_Int32 nCol ) const +{ + Reference< XCellRange > xColumn; + try + { + Reference< XColumnRowRange > xColRowRange( mxSheet, UNO_QUERY_THROW ); + Reference< XTableColumns > xColumns( xColRowRange->getColumns(), UNO_SET_THROW ); + xColumn.set( xColumns->getByIndex( nCol ), UNO_QUERY ); + } + catch( Exception& ) + { + } + return xColumn; +} + +Reference< XCellRange > WorksheetData::getRow( sal_Int32 nRow ) const +{ + Reference< XCellRange > xRow; + try + { + Reference< XColumnRowRange > xColRowRange( mxSheet, UNO_QUERY_THROW ); + Reference< XTableRows > xRows( xColRowRange->getRows(), UNO_SET_THROW ); + xRow.set( xRows->getByIndex( nRow ), UNO_QUERY ); + } + catch( Exception& ) + { + } + return xRow; +} + +Reference< XTableColumns > WorksheetData::getColumns( sal_Int32 nFirstCol, sal_Int32 nLastCol ) const +{ + Reference< XTableColumns > xColumns; + nLastCol = ::std::min( nLastCol, mrMaxApiPos.Column ); + if( (0 <= nFirstCol) && (nFirstCol <= nLastCol) ) + { + Reference< XColumnRowRange > xRange( getCellRange( CellRangeAddress( getSheetIndex(), nFirstCol, 0, nLastCol, 0 ) ), UNO_QUERY ); + if( xRange.is() ) + xColumns = xRange->getColumns(); + } + return xColumns; +} + +Reference< XTableRows > WorksheetData::getRows( sal_Int32 nFirstRow, sal_Int32 nLastRow ) const +{ + Reference< XTableRows > xRows; + nLastRow = ::std::min( nLastRow, mrMaxApiPos.Row ); + if( (0 <= nFirstRow) && (nFirstRow <= nLastRow) ) + { + Reference< XColumnRowRange > xRange( getCellRange( CellRangeAddress( getSheetIndex(), 0, nFirstRow, 0, nLastRow ) ), UNO_QUERY ); + if( xRange.is() ) + xRows = xRange->getRows(); + } + return xRows; +} + +Reference< XDrawPage > WorksheetData::getDrawPage() const +{ + Reference< XDrawPage > xDrawPage; + try + { + xDrawPage = Reference< XDrawPageSupplier >( mxSheet, UNO_QUERY_THROW )->getDrawPage(); + } + catch( Exception& ) + { + } + return xDrawPage; +} + +Size WorksheetData::getDrawPageSize() const +{ + Size aSize; + PropertySet aRangeProp( getCellRange( CellRangeAddress( getSheetIndex(), 0, 0, mrMaxApiPos.Column, mrMaxApiPos.Row ) ) ); + aRangeProp.getProperty( aSize, PROP_Size ); + return aSize; +} + +Point WorksheetData::getCellPosition( sal_Int32 nCol, sal_Int32 nRow ) const +{ + Point aPoint; + PropertySet aCellProp( getCell( CellAddress( getSheetIndex(), nCol, nRow ) ) ); + aCellProp.getProperty( aPoint, PROP_Position ); + return aPoint; +} + +Size WorksheetData::getCellSize( sal_Int32 nCol, sal_Int32 nRow ) const +{ + Size aSize; + PropertySet aCellProp( getCell( CellAddress( getSheetIndex(), nCol, nRow ) ) ); + aCellProp.getProperty( aSize, PROP_Size ); + return aSize; +} + +namespace { + +inline sal_Int32 lclGetMidAddr( sal_Int32 nBegAddr, sal_Int32 nEndAddr, sal_Int32 nBegPos, sal_Int32 nEndPos, sal_Int32 nSearchPos ) +{ + // use sal_Int64 to prevent integer overflow + return nBegAddr + 1 + static_cast< sal_Int32 >( static_cast< sal_Int64 >( nEndAddr - nBegAddr - 2 ) * (nSearchPos - nBegPos) / (nEndPos - nBegPos) ); +} + +bool lclPrepareInterval( sal_Int32 nBegAddr, sal_Int32& rnMidAddr, sal_Int32 nEndAddr, + sal_Int32 nBegPos, sal_Int32 nEndPos, sal_Int32 nSearchPos ) +{ + // searched position before nBegPos -> use nBegAddr + if( nSearchPos <= nBegPos ) + { + rnMidAddr = nBegAddr; + return false; + } + + // searched position after nEndPos, or begin next to end -> use nEndAddr + if( (nSearchPos >= nEndPos) || (nBegAddr + 1 >= nEndAddr) ) + { + rnMidAddr = nEndAddr; + return false; + } + + /* Otherwise find mid address according to position. lclGetMidAddr() will + return an address between nBegAddr and nEndAddr. */ + rnMidAddr = lclGetMidAddr( nBegAddr, nEndAddr, nBegPos, nEndPos, nSearchPos ); + return true; +} + +bool lclUpdateInterval( sal_Int32& rnBegAddr, sal_Int32& rnMidAddr, sal_Int32& rnEndAddr, + sal_Int32& rnBegPos, sal_Int32 nMidPos, sal_Int32& rnEndPos, sal_Int32 nSearchPos ) +{ + // nSearchPos < nMidPos: use the interval [begin,mid] in the next iteration + if( nSearchPos < nMidPos ) + { + // if rnBegAddr is next to rnMidAddr, the latter is the column/row in question + if( rnBegAddr + 1 >= rnMidAddr ) + return false; + // otherwise, set interval end to mid + rnEndPos = nMidPos; + rnEndAddr = rnMidAddr; + rnMidAddr = lclGetMidAddr( rnBegAddr, rnEndAddr, rnBegPos, rnEndPos, nSearchPos ); + return true; + } + + // nSearchPos > nMidPos: use the interval [mid,end] in the next iteration + if( nSearchPos > nMidPos ) + { + // if rnMidAddr is next to rnEndAddr, the latter is the column/row in question + if( rnMidAddr + 1 >= rnEndAddr ) + { + rnMidAddr = rnEndAddr; + return false; + } + // otherwise, set interval start to mid + rnBegPos = nMidPos; + rnBegAddr = rnMidAddr; + rnMidAddr = lclGetMidAddr( rnBegAddr, rnEndAddr, rnBegPos, rnEndPos, nSearchPos ); + return true; + } + + // nSearchPos == nMidPos: rnMidAddr is the column/row in question, do not loop anymore + return false; +} + +} // namespace + +CellAddress WorksheetData::getCellAddressFromPosition( const Point& rPosition, const Size& rDrawPageSize ) const +{ + // starting cell address and its position in drawing layer (top-left edge) + sal_Int32 nBegCol = 0; + sal_Int32 nBegRow = 0; + Point aBegPos( 0, 0 ); + + // end cell address and its position in drawing layer (bottom-right edge) + sal_Int32 nEndCol = mrMaxApiPos.Column + 1; + sal_Int32 nEndRow = mrMaxApiPos.Row + 1; + Point aEndPos( rDrawPageSize.Width, rDrawPageSize.Height ); + + // starting point for interval search + sal_Int32 nMidCol, nMidRow; + bool bLoopCols = lclPrepareInterval( nBegCol, nMidCol, nEndCol, aBegPos.X, aEndPos.X, rPosition.X ); + bool bLoopRows = lclPrepareInterval( nBegRow, nMidRow, nEndRow, aBegPos.Y, aEndPos.Y, rPosition.Y ); + Point aMidPos = getCellPosition( nMidCol, nMidRow ); + + /* The loop will find the column/row index of the cell right of/below + the cell containing the passed point, unless the point is located at + the top or left border of the containing cell. */ + while( bLoopCols || bLoopRows ) + { + bLoopCols = bLoopCols && lclUpdateInterval( nBegCol, nMidCol, nEndCol, aBegPos.X, aMidPos.X, aEndPos.X, rPosition.X ); + bLoopRows = bLoopRows && lclUpdateInterval( nBegRow, nMidRow, nEndRow, aBegPos.Y, aMidPos.Y, aEndPos.Y, rPosition.Y ); + aMidPos = getCellPosition( nMidCol, nMidRow ); + } + + /* The cell left of/above the current search position contains the passed + point, unless the point is located on the top/left border of the cell, + or the last column/row of the sheet has been reached. */ + if( aMidPos.X > rPosition.X ) --nMidCol; + if( aMidPos.Y > rPosition.Y ) --nMidRow; + return CellAddress( getSheetIndex(), nMidCol, nMidRow ); +} + +CellRangeAddress WorksheetData::getCellRangeFromRectangle( const Rectangle& rRect ) const +{ + Size aPageSize = getDrawPageSize(); + CellAddress aStartAddr = getCellAddressFromPosition( Point( rRect.X, rRect.Y ), aPageSize ); + Point aBotRight( rRect.X + rRect.Width, rRect.Y + rRect.Height ); + CellAddress aEndAddr = getCellAddressFromPosition( aBotRight, aPageSize ); + bool bMultiCols = aStartAddr.Column < aEndAddr.Column; + bool bMultiRows = aStartAddr.Row < aEndAddr.Row; + if( bMultiCols || bMultiRows ) + { + /* Reduce end position of the cell range to previous column or row, if + the rectangle ends exactly between two columns or rows. */ + Point aEndPos = getCellPosition( aEndAddr.Column, aEndAddr.Row ); + if( bMultiCols && (aBotRight.X <= aEndPos.X) ) + --aEndAddr.Column; + if( bMultiRows && (aBotRight.Y <= aEndPos.Y) ) + --aEndAddr.Row; + } + return CellRangeAddress( getSheetIndex(), aStartAddr.Column, aStartAddr.Row, aEndAddr.Column, aEndAddr.Row ); +} + +void WorksheetData::setCellFormat( const CellModel& rModel ) +{ + if( rModel.mxCell.is() && ((rModel.mnXfId >= 0) || (rModel.mnNumFmtId >= 0)) ) + { + // try to merge existing ranges and to write some formatting properties + if( !maXfIdRanges.empty() ) + { + // get row index of last inserted cell + sal_Int32 nLastRow = maXfIdRanges.rbegin()->second.maRange.StartRow; + // row changed - try to merge ranges of last row with existing ranges + if( rModel.maAddress.Row != nLastRow ) + { + mergeXfIdRanges(); + // write format properties of all ranges above last row and remove them + XfIdRangeMap::iterator aIt = maXfIdRanges.begin(), aEnd = maXfIdRanges.end(); + while( aIt != aEnd ) + { + // check that range cannot be merged with current row, and that range is not in cached row range + if( (aIt->second.maRange.EndRow < nLastRow) && !maXfIdRowRange.intersects( aIt->second.maRange ) ) + { + writeXfIdRangeProperties( aIt->second ); + maXfIdRanges.erase( aIt++ ); + } + else + ++aIt; + } + } + } + + // try to expand last existing range, or create new range entry + if( maXfIdRanges.empty() || !maXfIdRanges.rbegin()->second.tryExpand( rModel ) ) + maXfIdRanges[ RowColKey( rModel.maAddress.Row, rModel.maAddress.Column ) ].set( rModel ); + + // update merged ranges for 'center across selection' and 'fill' + if( const Xf* pXf = getStyles().getCellXf( rModel.mnXfId ).get() ) + { + sal_Int32 nHorAlign = pXf->getAlignment().getModel().mnHorAlign; + if( (nHorAlign == XML_centerContinuous) || (nHorAlign == XML_fill) ) + { + /* start new merged range, if cell is not empty (#108781#), + or try to expand last range with empty cell */ + if( rModel.mnCellType != XML_TOKEN_INVALID ) + maCenterFillRanges.push_back( MergedRange( rModel.maAddress, nHorAlign ) ); + else if( !maCenterFillRanges.empty() ) + maCenterFillRanges.rbegin()->tryExpand( rModel.maAddress, nHorAlign ); + } + } + } +} + +void WorksheetData::setMergedRange( const CellRangeAddress& rRange ) +{ + maMergedRanges.push_back( MergedRange( rRange ) ); +} + +void WorksheetData::setPageBreak( const PageBreakModel& rModel, bool bRowBreak ) +{ + if( rModel.mbManual && (rModel.mnColRow > 0) ) + { + PropertySet aPropSet( bRowBreak ? getRow( rModel.mnColRow ) : getColumn( rModel.mnColRow ) ); + aPropSet.setProperty( PROP_IsStartOfNewPage, true ); + } +} + +void WorksheetData::setHyperlink( const HyperlinkModel& rModel ) +{ + maHyperlinks.push_back( rModel ); +} + +void WorksheetData::setValidation( const ValidationModel& rModel ) +{ + maValidations.push_back( rModel ); +} + +void WorksheetData::setDrawingPath( const OUString& rDrawingPath ) +{ + maDrawingPath = rDrawingPath; +} + +void WorksheetData::setVmlDrawingPath( const OUString& rVmlDrawingPath ) +{ + maVmlDrawingPath = rVmlDrawingPath; +} + +void WorksheetData::extendUsedArea( const CellAddress& rAddress ) +{ + maUsedArea.StartColumn = ::std::min( maUsedArea.StartColumn, rAddress.Column ); + maUsedArea.StartRow = ::std::min( maUsedArea.StartRow, rAddress.Row ); + maUsedArea.EndColumn = ::std::max( maUsedArea.EndColumn, rAddress.Column ); + maUsedArea.EndRow = ::std::max( maUsedArea.EndRow, rAddress.Row ); +} + +void WorksheetData::extendUsedArea( const CellRangeAddress& rRange ) +{ + extendUsedArea( CellAddress( rRange.Sheet, rRange.StartColumn, rRange.StartRow ) ); + extendUsedArea( CellAddress( rRange.Sheet, rRange.EndColumn, rRange.EndRow ) ); +} + +void WorksheetData::extendShapeBoundingBox( const Rectangle& rShapeRect ) +{ + // scale EMUs to 1/100 mm + const UnitConverter& rUnitConv = getUnitConverter(); + Rectangle aShapeRectHmm( + rUnitConv.scaleToMm100( rShapeRect.X, UNIT_EMU ), + rUnitConv.scaleToMm100( rShapeRect.Y, UNIT_EMU ), + rUnitConv.scaleToMm100( rShapeRect.Width, UNIT_EMU ), + rUnitConv.scaleToMm100( rShapeRect.Height, UNIT_EMU ) ); + + if( (maShapeBoundingBox.Width == 0) && (maShapeBoundingBox.Height == 0) ) + { + // width and height of maShapeBoundingBox are assumed to be zero on first cell + maShapeBoundingBox = aShapeRectHmm; + } + else + { + sal_Int32 nEndX = ::std::max( maShapeBoundingBox.X + maShapeBoundingBox.Width, aShapeRectHmm.X + aShapeRectHmm.Width ); + sal_Int32 nEndY = ::std::max( maShapeBoundingBox.Y + maShapeBoundingBox.Height, aShapeRectHmm.Y + aShapeRectHmm.Height ); + maShapeBoundingBox.X = ::std::min( maShapeBoundingBox.X, aShapeRectHmm.X ); + maShapeBoundingBox.Y = ::std::min( maShapeBoundingBox.Y, aShapeRectHmm.Y ); + maShapeBoundingBox.Width = nEndX - maShapeBoundingBox.X; + maShapeBoundingBox.Height = nEndY - maShapeBoundingBox.Y; + } +} + +void WorksheetData::setBaseColumnWidth( sal_Int32 nWidth ) +{ + // do not modify width, if setDefaultColumnWidth() has been used + if( !mbHasDefWidth && (nWidth > 0) ) + { + // #i3006# add 5 pixels padding to the width + const UnitConverter& rUnitConv = getUnitConverter(); + maDefColModel.mfWidth = rUnitConv.scaleFromMm100( + rUnitConv.scaleToMm100( nWidth, UNIT_DIGIT ) + rUnitConv.scaleToMm100( 5, UNIT_SCREENX ), UNIT_DIGIT ); + } +} + +void WorksheetData::setDefaultColumnWidth( double fWidth ) +{ + // overrides a width set with setBaseColumnWidth() + if( fWidth > 0.0 ) + { + maDefColModel.mfWidth = fWidth; + mbHasDefWidth = true; + } +} + +void WorksheetData::setColumnModel( const ColumnModel& rModel ) +{ + // convert 1-based OOX column indexes to 0-based API column indexes + sal_Int32 nFirstCol = rModel.mnFirstCol - 1; + sal_Int32 nLastCol = rModel.mnLastCol - 1; + if( (0 <= nFirstCol) && (nFirstCol <= mrMaxApiPos.Column) ) + { + // set column formatting directly, nLastCol is checked inside the function + convertColumnFormat( nFirstCol, nLastCol, rModel.mnXfId ); + // expand last entry or add new entry + if( maColModels.empty() || !maColModels.rbegin()->second.tryExpand( rModel ) ) + maColModels[ nFirstCol ] = rModel; + } +} + +void WorksheetData::setDefaultRowSettings( double fHeight, bool bCustomHeight, bool bHidden, bool bThickTop, bool bThickBottom ) +{ + maDefRowModel.mfHeight = fHeight; + maDefRowModel.mbCustomHeight = bCustomHeight; + maDefRowModel.mbHidden = bHidden; + maDefRowModel.mbThickTop = bThickTop; + maDefRowModel.mbThickBottom = bThickBottom; +} + +void WorksheetData::setRowModel( const RowModel& rModel ) +{ + // convert 1-based OOX row indexes to 0-based API row indexes + sal_Int32 nFirstRow = rModel.mnFirstRow - 1; + sal_Int32 nLastRow = rModel.mnLastRow - 1; + if( (0 <= nFirstRow) && (nFirstRow <= mrMaxApiPos.Row) ) + { + // set row formatting + if( rModel.mbCustomFormat ) + { + // try to expand cached row range, if formatting is equal + if( (maXfIdRowRange.mnLastRow < 0) || !maXfIdRowRange.tryExpand( nFirstRow, nLastRow, rModel.mnXfId ) ) + { + writeXfIdRowRangeProperties( maXfIdRowRange ); + maXfIdRowRange.set( nFirstRow, nLastRow, rModel.mnXfId ); + } + } + else if( maXfIdRowRange.mnLastRow >= 0 ) + { + // finish last cached row range + writeXfIdRowRangeProperties( maXfIdRowRange ); + maXfIdRowRange.set( -1, -1, -1 ); + } + + // expand last entry or add new entry + if( maRowModels.empty() || !maRowModels.rbegin()->second.tryExpand( rModel ) ) + maRowModels[ nFirstRow ] = rModel; + } + lclUpdateProgressBar( mxRowProgress, maUsedArea, nLastRow ); +} + +void WorksheetData::convertColumnFormat( sal_Int32 nFirstCol, sal_Int32 nLastCol, sal_Int32 nXfId ) const +{ + CellRangeAddress aRange( getSheetIndex(), nFirstCol, 0, nLastCol, mrMaxApiPos.Row ); + if( getAddressConverter().validateCellRange( aRange, true, false ) ) + { + PropertySet aPropSet( getCellRange( aRange ) ); + getStyles().writeCellXfToPropertySet( aPropSet, nXfId ); + } +} + +void WorksheetData::convertRowFormat( sal_Int32 nFirstRow, sal_Int32 nLastRow, sal_Int32 nXfId ) const +{ + CellRangeAddress aRange( getSheetIndex(), 0, nFirstRow, mrMaxApiPos.Column, nLastRow ); + if( getAddressConverter().validateCellRange( aRange, true, false ) ) + { + PropertySet aPropSet( getCellRange( aRange ) ); + getStyles().writeCellXfToPropertySet( aPropSet, nXfId ); + } +} + +void WorksheetData::initializeWorksheetImport() +{ + // set default cell style for unused cells + PropertySet aPropSet( mxSheet ); + aPropSet.setProperty( PROP_CellStyle, getStyles().getDefaultStyleName() ); + + /* Remember current sheet index in global data, needed by some global + objects, e.g. the chart converter. */ + setCurrentSheetIndex( getSheetIndex() ); +} + +void WorksheetData::finalizeWorksheetImport() +{ + lclUpdateProgressBar( mxRowProgress, 1.0 ); + finalizeXfIdRanges(); + lclUpdateProgressBar( mxFinalProgress, 0.25 ); + finalizeHyperlinkRanges(); + finalizeValidationRanges(); + finalizeMergedRanges(); + maSheetSett.finalizeImport(); + maCondFormats.finalizeImport(); + maPageSett.finalizeImport(); + maSheetViewSett.finalizeImport(); + maSheetSett.finalizeImport(); + + lclUpdateProgressBar( mxFinalProgress, 0.5 ); + convertColumns(); + convertRows(); + lclUpdateProgressBar( mxFinalProgress, 0.75 ); + finalizeDrawing(); + finalizeVmlDrawing(); + maComments.finalizeImport(); // after VML drawing + finalizeUsedArea(); // after DML and VML drawing + lclUpdateProgressBar( mxFinalProgress, 1.0 ); + + // reset current sheet index in global data + setCurrentSheetIndex( -1 ); +} + +// private -------------------------------------------------------------------- + +WorksheetData::XfIdRowRange::XfIdRowRange() : + mnFirstRow( -1 ), + mnLastRow( -1 ), + mnXfId( -1 ) +{ +} + +bool WorksheetData::XfIdRowRange::intersects( const CellRangeAddress& rRange ) const +{ + return (rRange.StartRow <= mnLastRow) && (mnFirstRow <= rRange.EndRow); +} + +void WorksheetData::XfIdRowRange::set( sal_Int32 nFirstRow, sal_Int32 nLastRow, sal_Int32 nXfId ) +{ + mnFirstRow = nFirstRow; + mnLastRow = nLastRow; + mnXfId = nXfId; +} + +bool WorksheetData::XfIdRowRange::tryExpand( sal_Int32 nFirstRow, sal_Int32 nLastRow, sal_Int32 nXfId ) +{ + if( mnXfId == nXfId ) + { + if( mnLastRow + 1 == nFirstRow ) + { + mnLastRow = nLastRow; + return true; + } + if( mnFirstRow == nLastRow + 1 ) + { + mnFirstRow = nFirstRow; + return true; + } + } + return false; +} + +void WorksheetData::XfIdRange::set( const CellModel& rModel ) +{ + maRange.Sheet = rModel.maAddress.Sheet; + maRange.StartColumn = maRange.EndColumn = rModel.maAddress.Column; + maRange.StartRow = maRange.EndRow = rModel.maAddress.Row; + mnXfId = rModel.mnXfId; + mnNumFmtId = rModel.mnNumFmtId; +} + +bool WorksheetData::XfIdRange::tryExpand( const CellModel& rModel ) +{ + if( (mnXfId == rModel.mnXfId) && (mnNumFmtId == rModel.mnNumFmtId) && + (maRange.StartRow == rModel.maAddress.Row) && + (maRange.EndRow == rModel.maAddress.Row) && + (maRange.EndColumn + 1 == rModel.maAddress.Column) ) + { + ++maRange.EndColumn; + return true; + } + return false; +} + +bool WorksheetData::XfIdRange::tryMerge( const XfIdRange& rXfIdRange ) +{ + if( (mnXfId == rXfIdRange.mnXfId) && + (mnNumFmtId == rXfIdRange.mnNumFmtId) && + (maRange.EndRow + 1 == rXfIdRange.maRange.StartRow) && + (maRange.StartColumn == rXfIdRange.maRange.StartColumn) && + (maRange.EndColumn == rXfIdRange.maRange.EndColumn) ) + { + maRange.EndRow = rXfIdRange.maRange.EndRow; + return true; + } + return false; +} + + +WorksheetData::MergedRange::MergedRange( const CellRangeAddress& rRange ) : + maRange( rRange ), + mnHorAlign( XML_TOKEN_INVALID ) +{ +} + +WorksheetData::MergedRange::MergedRange( const CellAddress& rAddress, sal_Int32 nHorAlign ) : + maRange( rAddress.Sheet, rAddress.Column, rAddress.Row, rAddress.Column, rAddress.Row ), + mnHorAlign( nHorAlign ) +{ +} + +bool WorksheetData::MergedRange::tryExpand( const CellAddress& rAddress, sal_Int32 nHorAlign ) +{ + if( (mnHorAlign == nHorAlign) && (maRange.StartRow == rAddress.Row) && + (maRange.EndRow == rAddress.Row) && (maRange.EndColumn + 1 == rAddress.Column) ) + { + ++maRange.EndColumn; + return true; + } + return false; +} + +void WorksheetData::writeXfIdRowRangeProperties( const XfIdRowRange& rXfIdRowRange ) const +{ + if( (rXfIdRowRange.mnLastRow >= 0) && (rXfIdRowRange.mnXfId >= 0) ) + convertRowFormat( rXfIdRowRange.mnFirstRow, rXfIdRowRange.mnLastRow, rXfIdRowRange.mnXfId ); +} + +void WorksheetData::writeXfIdRangeProperties( const XfIdRange& rXfIdRange ) const +{ + StylesBuffer& rStyles = getStyles(); + PropertyMap aPropMap; + if( rXfIdRange.mnXfId >= 0 ) + rStyles.writeCellXfToPropertyMap( aPropMap, rXfIdRange.mnXfId ); + if( rXfIdRange.mnNumFmtId >= 0 ) + rStyles.writeNumFmtToPropertyMap( aPropMap, rXfIdRange.mnNumFmtId ); + PropertySet aPropSet( getCellRange( rXfIdRange.maRange ) ); + aPropSet.setProperties( aPropMap ); +} + +void WorksheetData::mergeXfIdRanges() +{ + if( !maXfIdRanges.empty() ) + { + // get row index of last range + sal_Int32 nLastRow = maXfIdRanges.rbegin()->second.maRange.StartRow; + // process all ranges located in the same row of the last range + XfIdRangeMap::iterator aMergeIt = maXfIdRanges.end(); + while( (aMergeIt != maXfIdRanges.begin()) && ((--aMergeIt)->second.maRange.StartRow == nLastRow) ) + { + const XfIdRange& rMergeXfIdRange = aMergeIt->second; + // try to find a range that can be merged with rMergeRange + bool bFound = false; + for( XfIdRangeMap::iterator aIt = maXfIdRanges.begin(); !bFound && (aIt != aMergeIt); ++aIt ) + if( (bFound = aIt->second.tryMerge( rMergeXfIdRange )) == true ) + maXfIdRanges.erase( aMergeIt++ ); + } + } +} + +void WorksheetData::finalizeXfIdRanges() +{ + // write default formatting of remaining row range + writeXfIdRowRangeProperties( maXfIdRowRange ); + // try to merge remaining inserted ranges + mergeXfIdRanges(); + // write all formatting + for( XfIdRangeMap::const_iterator aIt = maXfIdRanges.begin(), aEnd = maXfIdRanges.end(); aIt != aEnd; ++aIt ) + writeXfIdRangeProperties( aIt->second ); +} + +void WorksheetData::finalizeHyperlinkRanges() const +{ + for( HyperlinkModelList::const_iterator aIt = maHyperlinks.begin(), aEnd = maHyperlinks.end(); aIt != aEnd; ++aIt ) + { + OUString aUrl = getHyperlinkUrl( *aIt ); + // try to insert URL into each cell of the range + if( aUrl.getLength() > 0 ) + for( CellAddress aAddress( getSheetIndex(), aIt->maRange.StartColumn, aIt->maRange.StartRow ); aAddress.Row <= aIt->maRange.EndRow; ++aAddress.Row ) + for( aAddress.Column = aIt->maRange.StartColumn; aAddress.Column <= aIt->maRange.EndColumn; ++aAddress.Column ) + insertHyperlink( aAddress, aUrl ); + } +} + +OUString WorksheetData::getHyperlinkUrl( const HyperlinkModel& rHyperlink ) const +{ + OUStringBuffer aUrlBuffer; + if( rHyperlink.maTarget.getLength() > 0 ) + aUrlBuffer.append( getBaseFilter().getAbsoluteUrl( rHyperlink.maTarget ) ); + if( rHyperlink.maLocation.getLength() > 0 ) + aUrlBuffer.append( sal_Unicode( '#' ) ).append( rHyperlink.maLocation ); + OUString aUrl = aUrlBuffer.makeStringAndClear(); + + // convert '#SheetName!A1' to '#SheetName.A1' + if( (aUrl.getLength() > 0) && (aUrl[ 0 ] == '#') ) + { + sal_Int32 nSepPos = aUrl.lastIndexOf( '!' ); + if( nSepPos > 0 ) + { + // replace the exclamation mark with a period + aUrl = aUrl.replaceAt( nSepPos, 1, OUString( sal_Unicode( '.' ) ) ); + // #i66592# convert sheet names that have been renamed on import + OUString aSheetName = aUrl.copy( 1, nSepPos - 1 ); + OUString aCalcName = getWorksheets().getCalcSheetName( aSheetName ); + if( aCalcName.getLength() > 0 ) + aUrl = aUrl.replaceAt( 1, nSepPos - 1, aCalcName ); + } + } + + return aUrl; +} + +void WorksheetData::insertHyperlink( const CellAddress& rAddress, const OUString& rUrl ) const +{ + Reference< XCell > xCell = getCell( rAddress ); + if( xCell.is() ) switch( xCell->getType() ) + { + // #i54261# restrict creation of URL field to text cells + case ::com::sun::star::table::CellContentType_TEXT: + { + Reference< XText > xText( xCell, UNO_QUERY ); + if( xText.is() ) + { + // create a URL field object and set its properties + Reference< XTextContent > xUrlField( getDocumentFactory()->createInstance( maUrlTextField ), UNO_QUERY ); + OSL_ENSURE( xUrlField.is(), "WorksheetData::insertHyperlink - cannot create text field" ); + if( xUrlField.is() ) + { + // properties of the URL field + PropertySet aPropSet( xUrlField ); + aPropSet.setProperty( PROP_URL, rUrl ); + aPropSet.setProperty( PROP_Representation, xText->getString() ); + try + { + // insert the field into the cell + xText->setString( OUString() ); + Reference< XTextRange > xRange( xText->createTextCursor(), UNO_QUERY_THROW ); + xText->insertTextContent( xRange, xUrlField, sal_False ); + } + catch( const Exception& ) + { + OSL_ENSURE( false, "WorksheetData::insertHyperlink - cannot insert text field" ); + } + } + } + } + break; + + // fix for #i31050# disabled, HYPERLINK is not able to return numeric value (#i91351#) +#if 0 + // #i31050# replace number with HYPERLINK function + case ::com::sun::star::table::CellContentType_VALUE: + { + Reference< XFormulaTokens > xTokens( xCell, UNO_QUERY ); + OSL_ENSURE( xTokens.is(), "WorksheetHelper::insertHyperlink - missing formula interface" ); + if( xTokens.is() ) + { + SimpleFormulaContext aContext( xTokens, false, false ); + getFormulaParser().convertNumberToHyperlink( aContext, rUrl, xCell->getValue() ); + } + } + break; +#endif + + default:; + } +} + +void WorksheetData::finalizeValidationRanges() const +{ + for( ValidationModelList::const_iterator aIt = maValidations.begin(), aEnd = maValidations.end(); aIt != aEnd; ++aIt ) + { + PropertySet aPropSet( getCellRangeList( aIt->maRanges ) ); + + Reference< XPropertySet > xValidation; + if( aPropSet.getProperty( xValidation, PROP_Validation ) && xValidation.is() ) + { + PropertySet aValProps( xValidation ); + namespace csss = ::com::sun::star::sheet; + + // convert validation type to API enum + ValidationType eType = csss::ValidationType_ANY; + switch( aIt->mnType ) + { + case XML_custom: eType = csss::ValidationType_CUSTOM; break; + case XML_date: eType = csss::ValidationType_DATE; break; + case XML_decimal: eType = csss::ValidationType_DECIMAL; break; + case XML_list: eType = csss::ValidationType_LIST; break; + case XML_none: eType = csss::ValidationType_ANY; break; + case XML_textLength: eType = csss::ValidationType_TEXT_LEN; break; + case XML_time: eType = csss::ValidationType_TIME; break; + case XML_whole: eType = csss::ValidationType_WHOLE; break; + default: OSL_ENSURE( false, "WorksheetData::finalizeValidationRanges - unknown validation type" ); + } + aValProps.setProperty( PROP_Type, eType ); + + // convert error alert style to API enum + ValidationAlertStyle eAlertStyle = csss::ValidationAlertStyle_STOP; + switch( aIt->mnErrorStyle ) + { + case XML_information: eAlertStyle = csss::ValidationAlertStyle_INFO; break; + case XML_stop: eAlertStyle = csss::ValidationAlertStyle_STOP; break; + case XML_warning: eAlertStyle = csss::ValidationAlertStyle_WARNING; break; + default: OSL_ENSURE( false, "WorksheetData::finalizeValidationRanges - unknown error style" ); + } + aValProps.setProperty( PROP_ErrorAlertStyle, eAlertStyle ); + + // convert dropdown style to API visibility constants + sal_Int16 nVisibility = aIt->mbNoDropDown ? csss::TableValidationVisibility::INVISIBLE : csss::TableValidationVisibility::UNSORTED; + aValProps.setProperty( PROP_ShowList, nVisibility ); + + // messages + aValProps.setProperty( PROP_ShowInputMessage, aIt->mbShowInputMsg ); + aValProps.setProperty( PROP_InputTitle, aIt->maInputTitle ); + aValProps.setProperty( PROP_InputMessage, aIt->maInputMessage ); + aValProps.setProperty( PROP_ShowErrorMessage, aIt->mbShowErrorMsg ); + aValProps.setProperty( PROP_ErrorTitle, aIt->maErrorTitle ); + aValProps.setProperty( PROP_ErrorMessage, aIt->maErrorMessage ); + + // allow blank cells + aValProps.setProperty( PROP_IgnoreBlankCells, aIt->mbAllowBlank ); + + try + { + // condition operator + Reference< XSheetCondition > xSheetCond( xValidation, UNO_QUERY_THROW ); + xSheetCond->setOperator( CondFormatBuffer::convertToApiOperator( aIt->mnOperator ) ); + + // condition formulas + Reference< XMultiFormulaTokens > xTokens( xValidation, UNO_QUERY_THROW ); + xTokens->setTokens( 0, aIt->maTokens1 ); + xTokens->setTokens( 1, aIt->maTokens2 ); + } + catch( Exception& ) + { + } + + // write back validation settings to cell range(s) + aPropSet.setProperty( PROP_Validation, xValidation ); + } + } +} + +void WorksheetData::finalizeMergedRanges() +{ + MergedRangeList::const_iterator aIt, aEnd; + for( aIt = maMergedRanges.begin(), aEnd = maMergedRanges.end(); aIt != aEnd; ++aIt ) + finalizeMergedRange( aIt->maRange ); + for( aIt = maCenterFillRanges.begin(), aEnd = maCenterFillRanges.end(); aIt != aEnd; ++aIt ) + finalizeMergedRange( aIt->maRange ); +} + +void WorksheetData::finalizeMergedRange( const CellRangeAddress& rRange ) +{ + bool bMultiCol = rRange.StartColumn < rRange.EndColumn; + bool bMultiRow = rRange.StartRow < rRange.EndRow; + + if( bMultiCol || bMultiRow ) try + { + // merge the cell range + Reference< XMergeable > xMerge( getCellRange( rRange ), UNO_QUERY_THROW ); + xMerge->merge( sal_True ); + + // if merging this range worked (no overlapping merged ranges), update cell borders + Reference< XCell > xTopLeft( getCell( CellAddress( getSheetIndex(), rRange.StartColumn, rRange.StartRow ) ), UNO_SET_THROW ); + PropertySet aTopLeftProp( xTopLeft ); + + // copy right border of top-right cell to right border of top-left cell + if( bMultiCol ) + { + PropertySet aTopRightProp( getCell( CellAddress( getSheetIndex(), rRange.EndColumn, rRange.StartRow ) ) ); + BorderLine aLine; + if( aTopRightProp.getProperty( aLine, PROP_RightBorder ) ) + aTopLeftProp.setProperty( PROP_RightBorder, aLine ); + } + + // copy bottom border of bottom-left cell to bottom border of top-left cell + if( bMultiRow ) + { + PropertySet aBottomLeftProp( getCell( CellAddress( getSheetIndex(), rRange.StartColumn, rRange.EndRow ) ) ); + BorderLine aLine; + if( aBottomLeftProp.getProperty( aLine, PROP_BottomBorder ) ) + aTopLeftProp.setProperty( PROP_BottomBorder, aLine ); + } + + // #i93609# merged range in a single row: test if manual row height is needed + if( !bMultiRow ) + { + bool bTextWrap = aTopLeftProp.getBoolProperty( PROP_IsTextWrapped ); + if( !bTextWrap && (xTopLeft->getType() == ::com::sun::star::table::CellContentType_TEXT) ) + { + Reference< XText > xText( xTopLeft, UNO_QUERY ); + bTextWrap = xText.is() && (xText->getString().indexOf( '\x0A' ) >= 0); + } + if( bTextWrap ) + maManualRowHeights.insert( rRange.StartRow ); + } + } + catch( Exception& ) + { + } +} + +void WorksheetData::finalizeDrawing() +{ + OSL_ENSURE( (getFilterType() == FILTER_OOX) || (maDrawingPath.getLength() == 0), + "WorksheetData::finalizeDrawing - unexpected DrawingML path" ); + if( (getFilterType() == FILTER_OOX) && (maDrawingPath.getLength() > 0) ) + importOoxFragment( new OoxDrawingFragment( *this, maDrawingPath ) ); +} + +void WorksheetData::finalizeVmlDrawing() +{ + OSL_ENSURE( (getFilterType() == FILTER_OOX) || (maVmlDrawingPath.getLength() == 0), + "WorksheetData::finalizeVmlDrawing - unexpected VML path" ); + if( (getFilterType() == FILTER_OOX) && (maVmlDrawingPath.getLength() > 0) ) + importOoxFragment( new OoxVmlDrawingFragment( *this, maVmlDrawingPath ) ); +} + +void WorksheetData::finalizeUsedArea() +{ + /* Extend used area of the sheet by cells covered with drawing objects. + Needed if the imported document is inserted as "OLE object from file" + and thus does not provide an OLE size property by itself. */ + if( (maShapeBoundingBox.Width > 0) || (maShapeBoundingBox.Height > 0) ) + extendUsedArea( getCellRangeFromRectangle( maShapeBoundingBox ) ); + + // if no used area is set, default to A1 + if( maUsedArea.StartColumn > maUsedArea.EndColumn ) + maUsedArea.StartColumn = maUsedArea.EndColumn = 0; + if( maUsedArea.StartRow > maUsedArea.EndRow ) + maUsedArea.StartRow = maUsedArea.EndRow = 0; + + /* Register the used area of this sheet in global view settings. The + global view settings will set the visible area if this document is an + embedded OLE object. */ + getViewSettings().setSheetUsedArea( maUsedArea ); +} + +void WorksheetData::convertColumns() +{ + sal_Int32 nNextCol = 0; + sal_Int32 nMaxCol = mrMaxApiPos.Column; + // stores first grouped column index for each level + OutlineLevelVec aColLevels; + + for( ColumnModelMap::const_iterator aIt = maColModels.begin(), aEnd = maColModels.end(); aIt != aEnd; ++aIt ) + { + // convert 1-based OOX column indexes to 0-based API column indexes + sal_Int32 nFirstCol = ::std::max( aIt->second.mnFirstCol - 1, nNextCol ); + sal_Int32 nLastCol = ::std::min( aIt->second.mnLastCol - 1, nMaxCol ); + + // process gap between two column models, use default column model + if( nNextCol < nFirstCol ) + convertColumns( aColLevels, nNextCol, nFirstCol - 1, maDefColModel ); + // process the column model + convertColumns( aColLevels, nFirstCol, nLastCol, aIt->second ); + + // cache next column to be processed + nNextCol = nLastCol + 1; + } + + // remaining default columns to end of sheet + convertColumns( aColLevels, nNextCol, nMaxCol, maDefColModel ); + // close remaining column outlines spanning to end of sheet + convertOutlines( aColLevels, nMaxCol + 1, 0, false, false ); +} + +void WorksheetData::convertColumns( OutlineLevelVec& orColLevels, + sal_Int32 nFirstCol, sal_Int32 nLastCol, const ColumnModel& rModel ) +{ + PropertySet aPropSet( getColumns( nFirstCol, nLastCol ) ); + + // column width: convert 'number of characters' to column width in 1/100 mm + sal_Int32 nWidth = getUnitConverter().scaleToMm100( rModel.mfWidth, UNIT_DIGIT ); + // macro sheets have double width + if( meSheetType == SHEETTYPE_MACROSHEET ) + nWidth *= 2; + if( nWidth > 0 ) + aPropSet.setProperty( PROP_Width, nWidth ); + + // hidden columns: TODO: #108683# hide columns later? + if( rModel.mbHidden ) + aPropSet.setProperty( PROP_IsVisible, false ); + + // outline settings for this column range + convertOutlines( orColLevels, nFirstCol, rModel.mnLevel, rModel.mbCollapsed, false ); +} + +void WorksheetData::convertRows() +{ + sal_Int32 nNextRow = 0; + sal_Int32 nMaxRow = mrMaxApiPos.Row; + // stores first grouped row index for each level + OutlineLevelVec aRowLevels; + + for( RowModelMap::const_iterator aIt = maRowModels.begin(), aEnd = maRowModels.end(); aIt != aEnd; ++aIt ) + { + // convert 1-based OOX row indexes to 0-based API row indexes + sal_Int32 nFirstRow = ::std::max( aIt->second.mnFirstRow - 1, nNextRow ); + sal_Int32 nLastRow = ::std::min( aIt->second.mnLastRow - 1, nMaxRow ); + + // process gap between two row models, use default row model + if( nNextRow < nFirstRow ) + convertRows( aRowLevels, nNextRow, nFirstRow - 1, maDefRowModel ); + // process the row model + convertRows( aRowLevels, nFirstRow, nLastRow, aIt->second, maDefRowModel.mfHeight ); + + // cache next row to be processed + nNextRow = nLastRow + 1; + } + + // remaining default rows to end of sheet + convertRows( aRowLevels, nNextRow, nMaxRow, maDefRowModel ); + // close remaining row outlines spanning to end of sheet + convertOutlines( aRowLevels, nMaxRow + 1, 0, false, true ); +} + +void WorksheetData::convertRows( OutlineLevelVec& orRowLevels, + sal_Int32 nFirstRow, sal_Int32 nLastRow, const RowModel& rModel, double fDefHeight ) +{ + // row height: convert points to row height in 1/100 mm + double fHeight = (rModel.mfHeight >= 0.0) ? rModel.mfHeight : fDefHeight; + sal_Int32 nHeight = getUnitConverter().scaleToMm100( fHeight, UNIT_POINT ); + if( nHeight > 0 ) + { + ValueRangeVector aManualRows; + if( rModel.mbCustomHeight ) + aManualRows.push_back( ValueRange( nFirstRow, nLastRow ) ); + else + maManualRowHeights.intersect( aManualRows, nFirstRow, nLastRow ); + for( ValueRangeVector::const_iterator aIt = aManualRows.begin(), aEnd = aManualRows.end(); aIt != aEnd; ++aIt ) + { + PropertySet aPropSet( getRows( aIt->mnFirst, aIt->mnLast ) ); + aPropSet.setProperty( PROP_Height, nHeight ); + } + } + + // hidden rows: TODO: #108683# hide rows later? + if( rModel.mbHidden ) + { + PropertySet aPropSet( getRows( nFirstRow, nLastRow ) ); + aPropSet.setProperty( PROP_IsVisible, false ); + } + + // outline settings for this row range + convertOutlines( orRowLevels, nFirstRow, rModel.mnLevel, rModel.mbCollapsed, true ); +} + +void WorksheetData::convertOutlines( OutlineLevelVec& orLevels, + sal_Int32 nColRow, sal_Int32 nLevel, bool bCollapsed, bool bRows ) +{ + /* It is ensured from caller functions, that this function is called + without any gaps between the processed column or row ranges. */ + + OSL_ENSURE( nLevel >= 0, "WorksheetData::convertOutlines - negative outline level" ); + nLevel = ::std::max< sal_Int32 >( nLevel, 0 ); + + sal_Int32 nSize = orLevels.size(); + if( nSize < nLevel ) + { + // Outline level increased. Push the begin column position. + for( sal_Int32 nIndex = nSize; nIndex < nLevel; ++nIndex ) + orLevels.push_back( nColRow ); + } + else if( nLevel < nSize ) + { + // Outline level decreased. Pop them all out. + for( sal_Int32 nIndex = nLevel; nIndex < nSize; ++nIndex ) + { + sal_Int32 nFirstInLevel = orLevels.back(); + orLevels.pop_back(); + groupColumnsOrRows( nFirstInLevel, nColRow - 1, bCollapsed, bRows ); + bCollapsed = false; // collapse only once + } + } +} + +void WorksheetData::groupColumnsOrRows( sal_Int32 nFirstColRow, sal_Int32 nLastColRow, bool bCollapse, bool bRows ) +{ + try + { + Reference< XSheetOutline > xOutline( mxSheet, UNO_QUERY_THROW ); + if( bRows ) + { + CellRangeAddress aRange( getSheetIndex(), 0, nFirstColRow, 0, nLastColRow ); + xOutline->group( aRange, ::com::sun::star::table::TableOrientation_ROWS ); + if( bCollapse ) + xOutline->hideDetail( aRange ); + } + else + { + CellRangeAddress aRange( getSheetIndex(), nFirstColRow, 0, nLastColRow, 0 ); + xOutline->group( aRange, ::com::sun::star::table::TableOrientation_COLUMNS ); + if( bCollapse ) + xOutline->hideDetail( aRange ); + } + } + catch( Exception& ) + { + } +} + +// ============================================================================ +// ============================================================================ + +WorksheetHelper::WorksheetHelper( WorksheetData& rSheetData ) : + WorkbookHelper( rSheetData ), + mrSheetData( rSheetData ) +{ +} + +WorksheetType WorksheetHelper::getSheetType() const +{ + return mrSheetData.getSheetType(); +} + +sal_Int16 WorksheetHelper::getSheetIndex() const +{ + return mrSheetData.getSheetIndex(); +} + +const Reference< XSpreadsheet >& WorksheetHelper::getSheet() const +{ + return mrSheetData.getSheet(); +} + +Reference< XCell > WorksheetHelper::getCell( const CellAddress& rAddress ) const +{ + return mrSheetData.getCell( rAddress ); +} + +Reference< XCell > WorksheetHelper::getCell( const OUString& rAddressStr, CellAddress* opAddress ) const +{ + CellAddress aAddress; + if( getAddressConverter().convertToCellAddress( aAddress, rAddressStr, mrSheetData.getSheetIndex(), true ) ) + { + if( opAddress ) *opAddress = aAddress; + return mrSheetData.getCell( aAddress ); + } + return Reference< XCell >(); +} + +Reference< XCell > WorksheetHelper::getCell( const BinAddress& rBinAddress, CellAddress* opAddress ) const +{ + CellAddress aAddress; + if( getAddressConverter().convertToCellAddress( aAddress, rBinAddress, mrSheetData.getSheetIndex(), true ) ) + { + if( opAddress ) *opAddress = aAddress; + return mrSheetData.getCell( aAddress ); + } + return Reference< XCell >(); +} + +Reference< XCellRange > WorksheetHelper::getCellRange( const CellRangeAddress& rRange ) const +{ + return mrSheetData.getCellRange( rRange ); +} + +Reference< XCellRange > WorksheetHelper::getCellRange( const OUString& rRangeStr, CellRangeAddress* opRange ) const +{ + CellRangeAddress aRange; + if( getAddressConverter().convertToCellRange( aRange, rRangeStr, mrSheetData.getSheetIndex(), true, true ) ) + { + if( opRange ) *opRange = aRange; + return mrSheetData.getCellRange( aRange ); + } + return Reference< XCellRange >(); +} + +Reference< XCellRange > WorksheetHelper::getCellRange( const BinRange& rBinRange, CellRangeAddress* opRange ) const +{ + CellRangeAddress aRange; + if( getAddressConverter().convertToCellRange( aRange, rBinRange, mrSheetData.getSheetIndex(), true, true ) ) + { + if( opRange ) *opRange = aRange; + return mrSheetData.getCellRange( aRange ); + } + return Reference< XCellRange >(); +} + +Reference< XSheetCellRanges > WorksheetHelper::getCellRangeList( const ApiCellRangeList& rRanges ) const +{ + return mrSheetData.getCellRangeList( rRanges ); +} + +Reference< XSheetCellRanges > WorksheetHelper::getCellRangeList( + const OUString& rRangesStr, ApiCellRangeList* opRanges ) const +{ + ApiCellRangeList aRanges; + getAddressConverter().convertToCellRangeList( aRanges, rRangesStr, mrSheetData.getSheetIndex(), true ); + if( opRanges ) *opRanges = aRanges; + return mrSheetData.getCellRangeList( aRanges ); +} + +Reference< XSheetCellRanges > WorksheetHelper::getCellRangeList( + const BinRangeList& rBinRanges, ApiCellRangeList* opRanges ) const +{ + ApiCellRangeList aRanges; + getAddressConverter().convertToCellRangeList( aRanges, rBinRanges, mrSheetData.getSheetIndex(), true ); + if( opRanges ) *opRanges = aRanges; + return mrSheetData.getCellRangeList( aRanges ); +} + +CellAddress WorksheetHelper::getCellAddress( const Reference< XCell >& rxCell ) +{ + CellAddress aAddress; + Reference< XCellAddressable > xAddressable( rxCell, UNO_QUERY ); + OSL_ENSURE( xAddressable.is(), "WorksheetHelper::getCellAddress - cell reference not addressable" ); + if( xAddressable.is() ) + aAddress = xAddressable->getCellAddress(); + return aAddress; +} + +CellRangeAddress WorksheetHelper::getRangeAddress( const Reference< XCellRange >& rxRange ) +{ + CellRangeAddress aRange; + Reference< XCellRangeAddressable > xAddressable( rxRange, UNO_QUERY ); + OSL_ENSURE( xAddressable.is(), "WorksheetHelper::getRangeAddress - cell range reference not addressable" ); + if( xAddressable.is() ) + aRange = xAddressable->getRangeAddress(); + return aRange; +} + +Reference< XCellRange > WorksheetHelper::getColumn( sal_Int32 nCol ) const +{ + return mrSheetData.getColumn( nCol ); +} + +Reference< XCellRange > WorksheetHelper::getRow( sal_Int32 nRow ) const +{ + return mrSheetData.getRow( nRow ); +} + +Reference< XTableColumns > WorksheetHelper::getColumns( sal_Int32 nFirstCol, sal_Int32 nLastCol ) const +{ + return mrSheetData.getColumns( nFirstCol, nLastCol ); +} + +Reference< XTableRows > WorksheetHelper::getRows( sal_Int32 nFirstRow, sal_Int32 nLastRow ) const +{ + return mrSheetData.getRows( nFirstRow, nLastRow ); +} + +Reference< XDrawPage > WorksheetHelper::getDrawPage() const +{ + return mrSheetData.getDrawPage(); +} + +Point WorksheetHelper::getCellPosition( sal_Int32 nCol, sal_Int32 nRow ) const +{ + return mrSheetData.getCellPosition( nCol, nRow ); +} + +Size WorksheetHelper::getCellSize( sal_Int32 nCol, sal_Int32 nRow ) const +{ + return mrSheetData.getCellSize( nCol, nRow ); +} + +Size WorksheetHelper::getDrawPageSize() const +{ + return mrSheetData.getDrawPageSize(); +} + +WorksheetSettings& WorksheetHelper::getWorksheetSettings() const +{ + return mrSheetData.getWorksheetSettings(); +} + +SharedFormulaBuffer& WorksheetHelper::getSharedFormulas() const +{ + return mrSheetData.getSharedFormulas(); +} + +CondFormatBuffer& WorksheetHelper::getCondFormats() const +{ + return mrSheetData.getCondFormats(); +} + +CommentsBuffer& WorksheetHelper::getComments() const +{ + return mrSheetData.getComments(); +} + +PageSettings& WorksheetHelper::getPageSettings() const +{ + return mrSheetData.getPageSettings(); +} + +SheetViewSettings& WorksheetHelper::getSheetViewSettings() const +{ + return mrSheetData.getSheetViewSettings(); +} + +VmlDrawing& WorksheetHelper::getVmlDrawing() const +{ + return mrSheetData.getVmlDrawing(); +} + +void WorksheetHelper::setStringCell( const Reference< XCell >& rxCell, const OUString& rText ) const +{ + OSL_ENSURE( rxCell.is(), "WorksheetHelper::setStringCell - missing cell interface" ); + Reference< XText > xText( rxCell, UNO_QUERY ); + if( xText.is() ) + xText->setString( rText ); +} + +void WorksheetHelper::setSharedStringCell( const Reference< XCell >& rxCell, sal_Int32 nStringId, sal_Int32 nXfId ) const +{ + OSL_ENSURE( rxCell.is(), "WorksheetHelper::setSharedStringCell - missing cell interface" ); + getSharedStrings().convertString( Reference< XText >( rxCell, UNO_QUERY ), nStringId, nXfId ); +} + +void WorksheetHelper::setDateTimeCell( const Reference< XCell >& rxCell, const DateTime& rDateTime ) const +{ + OSL_ENSURE( rxCell.is(), "WorksheetHelper::setDateTimeCell - missing cell interface" ); + // write serial date/time value into the cell + double fSerial = getUnitConverter().calcSerialFromDateTime( rDateTime ); + rxCell->setValue( fSerial ); + // set appropriate number format + using namespace ::com::sun::star::util::NumberFormat; + sal_Int16 nStdFmt = (fSerial < 1.0) ? TIME : (((rDateTime.Hours > 0) || (rDateTime.Minutes > 0) || (rDateTime.Seconds > 0)) ? DATETIME : DATE); + setStandardNumFmt( rxCell, nStdFmt ); +} + +void WorksheetHelper::setBooleanCell( const Reference< XCell >& rxCell, bool bValue ) const +{ + OSL_ENSURE( rxCell.is(), "WorksheetHelper::setBooleanCell - missing cell interface" ); + rxCell->setFormula( mrSheetData.getBooleanFormula( bValue ) ); +} + +void WorksheetHelper::setErrorCell( const Reference< XCell >& rxCell, const OUString& rErrorCode ) const +{ + setErrorCell( rxCell, getUnitConverter().calcBiffErrorCode( rErrorCode ) ); +} + +void WorksheetHelper::setErrorCell( const Reference< XCell >& rxCell, sal_uInt8 nErrorCode ) const +{ + Reference< XFormulaTokens > xTokens( rxCell, UNO_QUERY ); + OSL_ENSURE( xTokens.is(), "WorksheetHelper::setErrorCell - missing formula interface" ); + if( xTokens.is() ) + { + SimpleFormulaContext aContext( xTokens, false, false ); + getFormulaParser().convertErrorToFormula( aContext, nErrorCode ); + } +} + +void WorksheetHelper::setCell( CellModel& orModel ) const +{ + OSL_ENSURE( orModel.mxCell.is(), "WorksheetHelper::setCell - missing cell interface" ); + if( orModel.mbHasValueStr ) switch( orModel.mnCellType ) + { + case XML_b: + setBooleanCell( orModel.mxCell, orModel.maValueStr.toDouble() != 0.0 ); + // #108770# set 'Standard' number format for all Boolean cells + orModel.mnNumFmtId = 0; + break; + case XML_n: + orModel.mxCell->setValue( orModel.maValueStr.toDouble() ); + break; + case XML_e: + setErrorCell( orModel.mxCell, orModel.maValueStr ); + break; + case XML_str: + setStringCell( orModel.mxCell, orModel.maValueStr ); + break; + case XML_s: + setSharedStringCell( orModel.mxCell, orModel.maValueStr.toInt32(), orModel.mnXfId ); + break; + } +} + +void WorksheetHelper::setStandardNumFmt( const Reference< XCell >& rxCell, sal_Int16 nStdNumFmt ) const +{ + try + { + Reference< XNumberFormatsSupplier > xNumFmtsSupp( getDocument(), UNO_QUERY_THROW ); + Reference< XNumberFormatTypes > xNumFmtTypes( xNumFmtsSupp->getNumberFormats(), UNO_QUERY_THROW ); + sal_Int32 nIndex = xNumFmtTypes->getStandardFormat( nStdNumFmt, Locale() ); + PropertySet aPropSet( rxCell ); + aPropSet.setProperty( PROP_NumberFormat, nIndex ); + } + catch( Exception& ) + { + } +} + +void WorksheetHelper::setSheetType( WorksheetType eSheetType ) +{ + mrSheetData.setSheetType( eSheetType ); +} + +void WorksheetHelper::setCellFormat( const CellModel& rModel ) +{ + mrSheetData.setCellFormat( rModel ); +} + +void WorksheetHelper::setMergedRange( const CellRangeAddress& rRange ) +{ + mrSheetData.setMergedRange( rRange ); +} + +void WorksheetHelper::setPageBreak( const PageBreakModel& rModel, bool bRowBreak ) +{ + mrSheetData.setPageBreak( rModel, bRowBreak ); +} + +void WorksheetHelper::setHyperlink( const HyperlinkModel& rModel ) +{ + mrSheetData.setHyperlink( rModel ); +} + +void WorksheetHelper::setValidation( const ValidationModel& rModel ) +{ + mrSheetData.setValidation( rModel ); +} + +void WorksheetHelper::setTableOperation( const CellRangeAddress& rRange, const DataTableModel& rModel ) const +{ + OSL_ENSURE( getAddressConverter().checkCellRange( rRange, true, false ), "WorksheetHelper::setTableOperation - invalid range" ); + bool bOk = false; + if( !rModel.mbRef1Deleted && (rModel.maRef1.getLength() > 0) && (rRange.StartColumn > 0) && (rRange.StartRow > 0) ) + { + CellRangeAddress aOpRange = rRange; + CellAddress aRef1, aRef2; + if( getAddressConverter().convertToCellAddress( aRef1, rModel.maRef1, mrSheetData.getSheetIndex(), true ) ) try + { + if( rModel.mb2dTable ) + { + if( !rModel.mbRef2Deleted && getAddressConverter().convertToCellAddress( aRef2, rModel.maRef2, mrSheetData.getSheetIndex(), true ) ) + { + // API call expects input values inside operation range + --aOpRange.StartColumn; + --aOpRange.StartRow; + // formula range is top-left cell of operation range + CellRangeAddress aFormulaRange( mrSheetData.getSheetIndex(), aOpRange.StartColumn, aOpRange.StartRow, aOpRange.StartColumn, aOpRange.StartRow ); + // set multiple operation + Reference< XMultipleOperation > xMultOp( mrSheetData.getCellRange( aOpRange ), UNO_QUERY_THROW ); + xMultOp->setTableOperation( aFormulaRange, ::com::sun::star::sheet::TableOperationMode_BOTH, aRef2, aRef1 ); + bOk = true; + } + } + else if( rModel.mbRowTable ) + { + // formula range is column to the left of operation range + CellRangeAddress aFormulaRange( mrSheetData.getSheetIndex(), aOpRange.StartColumn - 1, aOpRange.StartRow, aOpRange.StartColumn - 1, aOpRange.EndRow ); + // API call expects input values (top row) inside operation range + --aOpRange.StartRow; + // set multiple operation + Reference< XMultipleOperation > xMultOp( mrSheetData.getCellRange( aOpRange ), UNO_QUERY_THROW ); + xMultOp->setTableOperation( aFormulaRange, ::com::sun::star::sheet::TableOperationMode_ROW, aRef1, aRef1 ); + bOk = true; + } + else + { + // formula range is row above operation range + CellRangeAddress aFormulaRange( mrSheetData.getSheetIndex(), aOpRange.StartColumn, aOpRange.StartRow - 1, aOpRange.EndColumn, aOpRange.StartRow - 1 ); + // API call expects input values (left column) inside operation range + --aOpRange.StartColumn; + // set multiple operation + Reference< XMultipleOperation > xMultOp( mrSheetData.getCellRange( aOpRange ), UNO_QUERY_THROW ); + xMultOp->setTableOperation( aFormulaRange, ::com::sun::star::sheet::TableOperationMode_COLUMN, aRef1, aRef1 ); + bOk = true; + } + } + catch( Exception& ) + { + } + } + + // on error: fill cell range with error codes + if( !bOk ) + { + for( CellAddress aPos( mrSheetData.getSheetIndex(), rRange.StartColumn, rRange.StartRow ); aPos.Row <= rRange.EndRow; ++aPos.Row ) + for( aPos.Column = rRange.StartColumn; aPos.Column <= rRange.EndColumn; ++aPos.Column ) + setErrorCell( mrSheetData.getCell( aPos ), BIFF_ERR_REF ); + } +} + +void WorksheetHelper::setLabelRanges( const ApiCellRangeList& rColRanges, const ApiCellRangeList& rRowRanges ) +{ + const CellAddress& rMaxPos = getAddressConverter().getMaxApiAddress(); + Reference< XLabelRanges > xLabelRanges; + PropertySet aPropSet( getSheet() ); + + if( !rColRanges.empty() && aPropSet.getProperty( xLabelRanges, PROP_ColumnLabelRanges ) && xLabelRanges.is() ) + { + for( ApiCellRangeList::const_iterator aIt = rColRanges.begin(), aEnd = rColRanges.end(); aIt != aEnd; ++aIt ) + { + CellRangeAddress aDataRange = *aIt; + if( aDataRange.EndRow < rMaxPos.Row ) + { + aDataRange.StartRow = aDataRange.EndRow + 1; + aDataRange.EndRow = rMaxPos.Row; + } + else if( aDataRange.StartRow > 0 ) + { + aDataRange.EndRow = aDataRange.StartRow - 1; + aDataRange.StartRow = 0; + } + xLabelRanges->addNew( *aIt, aDataRange ); + } + } + + if( !rRowRanges.empty() && aPropSet.getProperty( xLabelRanges, PROP_RowLabelRanges ) && xLabelRanges.is() ) + { + for( ApiCellRangeList::const_iterator aIt = rRowRanges.begin(), aEnd = rRowRanges.end(); aIt != aEnd; ++aIt ) + { + CellRangeAddress aDataRange = *aIt; + if( aDataRange.EndColumn < rMaxPos.Column ) + { + aDataRange.StartColumn = aDataRange.EndColumn + 1; + aDataRange.EndColumn = rMaxPos.Column; + } + else if( aDataRange.StartColumn > 0 ) + { + aDataRange.EndColumn = aDataRange.StartColumn - 1; + aDataRange.StartColumn = 0; + } + xLabelRanges->addNew( *aIt, aDataRange ); + } + } +} + +void WorksheetHelper::setDrawingPath( const OUString& rDrawingPath ) +{ + mrSheetData.setDrawingPath( rDrawingPath ); +} + +void WorksheetHelper::setVmlDrawingPath( const OUString& rVmlDrawingPath ) +{ + mrSheetData.setVmlDrawingPath( rVmlDrawingPath ); +} + +void WorksheetHelper::extendUsedArea( const CellAddress& rAddress ) +{ + mrSheetData.extendUsedArea( rAddress ); +} + +void WorksheetHelper::extendUsedArea( const CellRangeAddress& rRange ) +{ + mrSheetData.extendUsedArea( rRange ); +} + +void WorksheetHelper::extendShapeBoundingBox( const Rectangle& rShapeRect ) +{ + mrSheetData.extendShapeBoundingBox( rShapeRect ); +} + +void WorksheetHelper::setBaseColumnWidth( sal_Int32 nWidth ) +{ + mrSheetData.setBaseColumnWidth( nWidth ); +} + +void WorksheetHelper::setDefaultColumnWidth( double fWidth ) +{ + mrSheetData.setDefaultColumnWidth( fWidth ); +} + +void WorksheetHelper::setDefaultColumnFormat( sal_Int32 nFirstCol, sal_Int32 nLastCol, sal_Int32 nXfId ) +{ + mrSheetData.convertColumnFormat( nFirstCol, nLastCol, nXfId ); +} + +void WorksheetHelper::setColumnModel( const ColumnModel& rModel ) +{ + mrSheetData.setColumnModel( rModel ); +} + +void WorksheetHelper::setDefaultRowSettings( double fHeight, bool bCustomHeight, bool bHidden, bool bThickTop, bool bThickBottom ) +{ + mrSheetData.setDefaultRowSettings( fHeight, bCustomHeight, bHidden, bThickTop, bThickBottom ); +} + +void WorksheetHelper::setRowModel( const RowModel& rModel ) +{ + mrSheetData.setRowModel( rModel ); +} + +void WorksheetHelper::initializeWorksheetImport() +{ + mrSheetData.initializeWorksheetImport(); +} + +void WorksheetHelper::finalizeWorksheetImport() +{ + mrSheetData.finalizeWorksheetImport(); +} + +// ============================================================================ + +namespace prv { + +WorksheetDataOwner::WorksheetDataOwner( WorksheetDataRef xSheetData ) : + mxSheetData( xSheetData ) +{ +} + +WorksheetDataOwner::~WorksheetDataOwner() +{ +} + +} // namespace prv + +// ---------------------------------------------------------------------------- + +WorksheetHelperRoot::WorksheetHelperRoot( const WorkbookHelper& rHelper, ISegmentProgressBarRef xProgressBar, WorksheetType eSheetType, sal_Int16 nSheet ) : + prv::WorksheetDataOwner( prv::WorksheetDataRef( new WorksheetData( rHelper, xProgressBar, eSheetType, nSheet ) ) ), + WorksheetHelper( *mxSheetData ) +{ +} + +WorksheetHelperRoot::WorksheetHelperRoot( const WorksheetHelper& rHelper ) : + prv::WorksheetDataOwner( prv::WorksheetDataRef() ), + WorksheetHelper( rHelper ) +{ +} + +WorksheetHelperRoot::WorksheetHelperRoot( const WorksheetHelperRoot& rHelper ) : + prv::WorksheetDataOwner( rHelper.mxSheetData ), + WorksheetHelper( rHelper ) +{ +} + +bool WorksheetHelperRoot::isValidSheet() const +{ + return mxSheetData->isValidSheet(); +} + +// ============================================================================ +// ============================================================================ + +} // namespace xls +} // namespace oox + diff --git a/oox/source/xls/worksheetsettings.cxx b/oox/source/xls/worksheetsettings.cxx new file mode 100644 index 000000000000..0cc9d5f17780 --- /dev/null +++ b/oox/source/xls/worksheetsettings.cxx @@ -0,0 +1,338 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "oox/xls/worksheetsettings.hxx" +#include "oox/helper/attributelist.hxx" +#include "oox/helper/recordinputstream.hxx" +#include "oox/xls/biffinputstream.hxx" +#include "oox/xls/pagesettings.hxx" +#include "oox/xls/workbooksettings.hxx" +#include "oox/core/filterbase.hxx" +#include "properties.hxx" + +#include <com/sun/star/util/XProtectable.hpp> + +using ::rtl::OUString; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::util::XProtectable; +using ::oox::core::CodecHelper; + +namespace oox { +namespace xls { + +// ============================================================================ + +namespace { + +const sal_uInt8 OOBIN_SHEETPR_FILTERMODE = 0x01; +const sal_uInt8 OOBIN_SHEETPR_EVAL_CF = 0x02; + +const sal_uInt16 BIFF_SHEETPR_DIALOGSHEET = 0x0010; +const sal_uInt16 BIFF_SHEETPR_APPLYSTYLES = 0x0020; +const sal_uInt16 BIFF_SHEETPR_SYMBOLSBELOW = 0x0040; +const sal_uInt16 BIFF_SHEETPR_SYMBOLSRIGHT = 0x0080; +const sal_uInt16 BIFF_SHEETPR_FITTOPAGES = 0x0100; +const sal_uInt16 BIFF_SHEETPR_SKIPEXT = 0x0200; /// BIFF3-BIFF4 + +const sal_uInt32 BIFF_SHEETPROT_OBJECTS = 0x00000001; +const sal_uInt32 BIFF_SHEETPROT_SCENARIOS = 0x00000002; +const sal_uInt32 BIFF_SHEETPROT_FORMAT_CELLS = 0x00000004; +const sal_uInt32 BIFF_SHEETPROT_FORMAT_COLUMNS = 0x00000008; +const sal_uInt32 BIFF_SHEETPROT_FORMAT_ROWS = 0x00000010; +const sal_uInt32 BIFF_SHEETPROT_INSERT_COLUMNS = 0x00000020; +const sal_uInt32 BIFF_SHEETPROT_INSERT_ROWS = 0x00000040; +const sal_uInt32 BIFF_SHEETPROT_INSERT_HLINKS = 0x00000080; +const sal_uInt32 BIFF_SHEETPROT_DELETE_COLUMNS = 0x00000100; +const sal_uInt32 BIFF_SHEETPROT_DELETE_ROWS = 0x00000200; +const sal_uInt32 BIFF_SHEETPROT_SELECT_LOCKED = 0x00000400; +const sal_uInt32 BIFF_SHEETPROT_SORT = 0x00000800; +const sal_uInt32 BIFF_SHEETPROT_AUTOFILTER = 0x00001000; +const sal_uInt32 BIFF_SHEETPROT_PIVOTTABLES = 0x00002000; +const sal_uInt32 BIFF_SHEETPROT_SELECT_UNLOCKED = 0x00004000; + +} // namespace + +// ============================================================================ + +SheetSettingsModel::SheetSettingsModel() : + mbFilterMode( false ), + mbApplyStyles( false ), + mbSummaryBelow( true ), + mbSummaryRight( true ) +{ +} + +// ============================================================================ + +SheetProtectionModel::SheetProtectionModel() : + mnPasswordHash( 0 ), + mbSheet( false ), + mbObjects( false ), + mbScenarios( false ), + mbFormatCells( true ), + mbFormatColumns( true ), + mbFormatRows( true ), + mbInsertColumns( true ), + mbInsertRows( true ), + mbInsertHyperlinks( true ), + mbDeleteColumns( true ), + mbDeleteRows( true ), + mbSelectLocked( false ), + mbSort( true ), + mbAutoFilter( true ), + mbPivotTables( true ), + mbSelectUnlocked( false ) +{ +} + +// ============================================================================ + +WorksheetSettings::WorksheetSettings( const WorksheetHelper& rHelper ) : + WorksheetHelper( rHelper ), + maPhoneticSett( rHelper ) +{ +} + +void WorksheetSettings::importSheetPr( const AttributeList& rAttribs ) +{ + maSheetSettings.maCodeName = rAttribs.getString( XML_codeName, OUString() ); + maSheetSettings.mbFilterMode = rAttribs.getBool( XML_filterMode, false ); +} + +void WorksheetSettings::importChartSheetPr( const AttributeList& rAttribs ) +{ + maSheetSettings.maCodeName = rAttribs.getString( XML_codeName, OUString() ); +} + +void WorksheetSettings::importTabColor( const AttributeList& rAttribs ) +{ + maSheetSettings.maTabColor.importColor( rAttribs ); +} + +void WorksheetSettings::importOutlinePr( const AttributeList& rAttribs ) +{ + maSheetSettings.mbApplyStyles = rAttribs.getBool( XML_applyStyles, false ); + maSheetSettings.mbSummaryBelow = rAttribs.getBool( XML_summaryBelow, true ); + maSheetSettings.mbSummaryRight = rAttribs.getBool( XML_summaryRight, true ); +} + +void WorksheetSettings::importSheetProtection( const AttributeList& rAttribs ) +{ + maSheetProt.mnPasswordHash = CodecHelper::getPasswordHash( rAttribs, XML_password ); + maSheetProt.mbSheet = rAttribs.getBool( XML_sheet, false ); + maSheetProt.mbObjects = rAttribs.getBool( XML_objects, false ); + maSheetProt.mbScenarios = rAttribs.getBool( XML_scenarios, false ); + maSheetProt.mbFormatCells = rAttribs.getBool( XML_formatCells, true ); + maSheetProt.mbFormatColumns = rAttribs.getBool( XML_formatColumns, true ); + maSheetProt.mbFormatRows = rAttribs.getBool( XML_formatRows, true ); + maSheetProt.mbInsertColumns = rAttribs.getBool( XML_insertColumns, true ); + maSheetProt.mbInsertRows = rAttribs.getBool( XML_insertRows, true ); + maSheetProt.mbInsertHyperlinks = rAttribs.getBool( XML_insertHyperlinks, true ); + maSheetProt.mbDeleteColumns = rAttribs.getBool( XML_deleteColumns, true ); + maSheetProt.mbDeleteRows = rAttribs.getBool( XML_deleteRows, true ); + maSheetProt.mbSelectLocked = rAttribs.getBool( XML_selectLockedCells, false ); + maSheetProt.mbSort = rAttribs.getBool( XML_sort, true ); + maSheetProt.mbAutoFilter = rAttribs.getBool( XML_autoFilter, true ); + maSheetProt.mbPivotTables = rAttribs.getBool( XML_pivotTables, true ); + maSheetProt.mbSelectUnlocked = rAttribs.getBool( XML_selectUnlockedCells, false ); +} + +void WorksheetSettings::importChartProtection( const AttributeList& rAttribs ) +{ + maSheetProt.mnPasswordHash = CodecHelper::getPasswordHash( rAttribs, XML_password ); + maSheetProt.mbSheet = rAttribs.getBool( XML_content, false ); + maSheetProt.mbObjects = rAttribs.getBool( XML_objects, false ); +} + +void WorksheetSettings::importPhoneticPr( const AttributeList& rAttribs ) +{ + maPhoneticSett.importPhoneticPr( rAttribs ); +} + +void WorksheetSettings::importSheetPr( RecordInputStream& rStrm ) +{ + sal_uInt16 nFlags1; + sal_uInt8 nFlags2; + rStrm >> nFlags1 >> nFlags2 >> maSheetSettings.maTabColor; + rStrm.skip( 8 ); // sync anchor cell + rStrm >> maSheetSettings.maCodeName; + // sheet settings + maSheetSettings.mbFilterMode = getFlag( nFlags2, OOBIN_SHEETPR_FILTERMODE ); + // outline settings, equal flags in BIFF and OOBIN + maSheetSettings.mbApplyStyles = getFlag( nFlags1, BIFF_SHEETPR_APPLYSTYLES ); + maSheetSettings.mbSummaryRight = getFlag( nFlags1, BIFF_SHEETPR_SYMBOLSRIGHT ); + maSheetSettings.mbSummaryBelow = getFlag( nFlags1, BIFF_SHEETPR_SYMBOLSBELOW ); + /* Fit printout to width/height - for whatever reason, this flag is still + stored separated from the page settings */ + getPageSettings().setFitToPagesMode( getFlag( nFlags1, BIFF_SHEETPR_FITTOPAGES ) ); +} + +void WorksheetSettings::importChartSheetPr( RecordInputStream& rStrm ) +{ + rStrm.skip( 2 ); // flags, contains only the 'published' flag + rStrm >> maSheetSettings.maTabColor >> maSheetSettings.maCodeName; +} + +void WorksheetSettings::importSheetProtection( RecordInputStream& rStrm ) +{ + rStrm >> maSheetProt.mnPasswordHash; + // no flags field for all these boolean flags?!? + maSheetProt.mbSheet = rStrm.readInt32() != 0; + maSheetProt.mbObjects = rStrm.readInt32() != 0; + maSheetProt.mbScenarios = rStrm.readInt32() != 0; + maSheetProt.mbFormatCells = rStrm.readInt32() != 0; + maSheetProt.mbFormatColumns = rStrm.readInt32() != 0; + maSheetProt.mbFormatRows = rStrm.readInt32() != 0; + maSheetProt.mbInsertColumns = rStrm.readInt32() != 0; + maSheetProt.mbInsertRows = rStrm.readInt32() != 0; + maSheetProt.mbInsertHyperlinks = rStrm.readInt32() != 0; + maSheetProt.mbDeleteColumns = rStrm.readInt32() != 0; + maSheetProt.mbDeleteRows = rStrm.readInt32() != 0; + maSheetProt.mbSelectLocked = rStrm.readInt32() != 0; + maSheetProt.mbSort = rStrm.readInt32() != 0; + maSheetProt.mbAutoFilter = rStrm.readInt32() != 0; + maSheetProt.mbPivotTables = rStrm.readInt32() != 0; + maSheetProt.mbSelectUnlocked = rStrm.readInt32() != 0; +} + +void WorksheetSettings::importChartProtection( RecordInputStream& rStrm ) +{ + rStrm >> maSheetProt.mnPasswordHash; + // no flags field for all these boolean flags?!? + maSheetProt.mbSheet = rStrm.readInt32() != 0; + maSheetProt.mbObjects = rStrm.readInt32() != 0; +} + +void WorksheetSettings::importPhoneticPr( RecordInputStream& rStrm ) +{ + maPhoneticSett.importPhoneticPr( rStrm ); +} + +void WorksheetSettings::importSheetPr( BiffInputStream& rStrm ) +{ + sal_uInt16 nFlags; + rStrm >> nFlags; + // worksheet vs. dialogsheet + if( getFlag( nFlags, BIFF_SHEETPR_DIALOGSHEET ) ) + { + OSL_ENSURE( getSheetType() == SHEETTYPE_WORKSHEET, "WorksheetSettings::importSheetPr - unexpected sheet type" ); + setSheetType( SHEETTYPE_DIALOGSHEET ); + } + // outline settings + maSheetSettings.mbApplyStyles = getFlag( nFlags, BIFF_SHEETPR_APPLYSTYLES ); + maSheetSettings.mbSummaryRight = getFlag( nFlags, BIFF_SHEETPR_SYMBOLSRIGHT ); + maSheetSettings.mbSummaryBelow = getFlag( nFlags, BIFF_SHEETPR_SYMBOLSBELOW ); + // fit printout to width/height + getPageSettings().setFitToPagesMode( getFlag( nFlags, BIFF_SHEETPR_FITTOPAGES ) ); + // save external linked values, in BIFF5-BIFF8 moved to BOOKBOOK record + if( getBiff() <= BIFF4 ) + getWorkbookSettings().setSaveExtLinkValues( !getFlag( nFlags, BIFF_SHEETPR_SKIPEXT ) ); +} + +void WorksheetSettings::importProtect( BiffInputStream& rStrm ) +{ + maSheetProt.mbSheet = rStrm.readuInt16() != 0; +} + +void WorksheetSettings::importObjectProtect( BiffInputStream& rStrm ) +{ + maSheetProt.mbObjects = rStrm.readuInt16() != 0; +} + +void WorksheetSettings::importScenProtect( BiffInputStream& rStrm ) +{ + maSheetProt.mbScenarios = rStrm.readuInt16() != 0; +} + +void WorksheetSettings::importPassword( BiffInputStream& rStrm ) +{ + rStrm >> maSheetProt.mnPasswordHash; +} + +void WorksheetSettings::importSheetProtection( BiffInputStream& rStrm ) +{ + sal_uInt32 nFlags = rStrm.readuInt32(); + // set flag means protection is disabled + maSheetProt.mbObjects = !getFlag( nFlags, BIFF_SHEETPROT_OBJECTS ); + maSheetProt.mbScenarios = !getFlag( nFlags, BIFF_SHEETPROT_SCENARIOS ); + maSheetProt.mbFormatCells = !getFlag( nFlags, BIFF_SHEETPROT_FORMAT_CELLS ); + maSheetProt.mbFormatColumns = !getFlag( nFlags, BIFF_SHEETPROT_FORMAT_COLUMNS ); + maSheetProt.mbFormatRows = !getFlag( nFlags, BIFF_SHEETPROT_FORMAT_ROWS ); + maSheetProt.mbInsertColumns = !getFlag( nFlags, BIFF_SHEETPROT_INSERT_COLUMNS ); + maSheetProt.mbInsertRows = !getFlag( nFlags, BIFF_SHEETPROT_INSERT_ROWS ); + maSheetProt.mbInsertHyperlinks = !getFlag( nFlags, BIFF_SHEETPROT_INSERT_HLINKS ); + maSheetProt.mbDeleteColumns = !getFlag( nFlags, BIFF_SHEETPROT_DELETE_COLUMNS ); + maSheetProt.mbDeleteRows = !getFlag( nFlags, BIFF_SHEETPROT_DELETE_ROWS ); + maSheetProt.mbSelectLocked = !getFlag( nFlags, BIFF_SHEETPROT_SELECT_LOCKED ); + maSheetProt.mbSort = !getFlag( nFlags, BIFF_SHEETPROT_SORT ); + maSheetProt.mbAutoFilter = !getFlag( nFlags, BIFF_SHEETPROT_AUTOFILTER ); + maSheetProt.mbPivotTables = !getFlag( nFlags, BIFF_SHEETPROT_PIVOTTABLES ); + maSheetProt.mbSelectUnlocked = !getFlag( nFlags, BIFF_SHEETPROT_SELECT_UNLOCKED ); +} + +void WorksheetSettings::importCodeName( BiffInputStream& rStrm ) +{ + maSheetSettings.maCodeName = rStrm.readUniString(); +} + +void WorksheetSettings::importPhoneticPr( BiffInputStream& rStrm ) +{ + maPhoneticSett.importPhoneticPr( rStrm ); +} + +void WorksheetSettings::finalizeImport() +{ + // sheet protection + if( maSheetProt.mbSheet ) try + { + Reference< XProtectable > xProtectable( getSheet(), UNO_QUERY_THROW ); + xProtectable->protect( OUString() ); + } + catch( Exception& ) + { + } + + // VBA code name + PropertySet aPropSet( getSheet() ); + aPropSet.setProperty( PROP_CodeName, maSheetSettings.maCodeName ); + + if (!maSheetSettings.maTabColor.isAuto()) + { + sal_Int32 nColor = maSheetSettings.maTabColor.getColor(getBaseFilter().getGraphicHelper()); + aPropSet.setProperty(PROP_TabColor, nColor); + } +} + +// ============================================================================ + +} // namespace xls +} // namespace oox + |