/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <stdio.h>
#include <string.h>
#include <utility>
#include <filter/msfilter/util.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/sprintf.hxx>
#include <osl/diagnose.h>
#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/random.h>
#include <sax/fshelper.hxx>
#include <unotools/streamwrap.hxx>
#include <sot/storage.hxx>
#include <tools/urlobj.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <officecfg/Office/Calc.hxx>
#include <docuno.hxx>
#include <xestream.hxx>
#include <xladdress.hxx>
#include <xlstring.hxx>
#include <xltools.hxx>
#include <xeroot.hxx>
#include <xestring.hxx>
#include <xlstyle.hxx>
#include <rangelst.hxx>
#include <compiler.hxx>
#include <formulacell.hxx>
#include <tokenarray.hxx>
#include <tokenstringcontext.hxx>
#include <refreshtimerprotector.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <root.hxx>
#include <sfx2/app.hxx>
#include <docsh.hxx>
#include <tabvwsh.hxx>
#include <viewdata.hxx>
#include <excdoc.hxx>
#include <oox/token/tokens.hxx>
#include <oox/token/relationship.hxx>
#include <oox/export/drawingml.hxx>
#include <oox/export/utils.hxx>
#include <oox/export/ColorExportUtils.hxx>
#include <formula/grammar.hxx>
#include <oox/ole/vbaexport.hxx>
#include <excelvbaproject.hxx>
#include <com/sun/star/task/XStatusIndicator.hpp>
#include <memory>
#include <comphelper/servicehelper.hxx>
#include <comphelper/storagehelper.hxx>
#include <externalrefmgr.hxx>
#define DEBUG_XL_ENCRYPTION 0
using ::com::sun::star::uno::XInterface;
using ::std::vector;
using namespace com::sun::star;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::io;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::sheet;
using namespace ::com::sun::star::uno;
using namespace ::formula;
using namespace ::oox;
XclExpStream::XclExpStream( SvStream& rOutStrm, const XclExpRoot& rRoot, sal_uInt16 nMaxRecSize ) :
mrStrm( rOutStrm ),
mrRoot( rRoot ),
mbUseEncrypter( false ),
mnMaxRecSize( nMaxRecSize ),
mnCurrMaxSize( 0 ),
mnMaxSliceSize( 0 ),
mnHeaderSize( 0 ),
mnCurrSize( 0 ),
mnSliceSize( 0 ),
mnPredictSize( 0 ),
mnLastSizePos( 0 ),
mbInRec( false )
{
if( mnMaxRecSize == 0 )
mnMaxRecSize = (mrRoot.GetBiff() <= EXC_BIFF5) ? EXC_MAXRECSIZE_BIFF5 : EXC_MAXRECSIZE_BIFF8;
mnMaxContSize = mnMaxRecSize;
}
XclExpStream::~XclExpStream()
{
mrStrm.FlushBuffer();
}
void XclExpStream::StartRecord( sal_uInt16 nRecId, std::size_t nRecSize )
{
OSL_ENSURE( !mbInRec, "XclExpStream::StartRecord - another record still open" );
DisableEncryption();
mnMaxContSize = mnCurrMaxSize = mnMaxRecSize;
mnPredictSize = nRecSize;
mbInRec = true;
InitRecord( nRecId );
SetSliceSize( 0 );
EnableEncryption();
}
void XclExpStream::EndRecord()
{
OSL_ENSURE( mbInRec, "XclExpStream::EndRecord - no record open" );
DisableEncryption();
UpdateRecSize();
mrStrm.Seek( STREAM_SEEK_TO_END );
mbInRec = false;
}
void XclExpStream::SetSliceSize( sal_uInt16 nSize )
{
mnMaxSliceSize = nSize;
mnSliceSize = 0;
}
XclExpStream& XclExpStream::operator<<( sal_Int8 nValue )
{
PrepareWrite( 1 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, nValue);
else
mrStrm.WriteSChar( nValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( sal_uInt8 nValue )
{
PrepareWrite( 1 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, nValue);
else
mrStrm.WriteUChar( nValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( sal_Int16 nValue )
{
PrepareWrite( 2 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, nValue);
else
mrStrm.WriteInt16( nValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( sal_uInt16 nValue )
{
PrepareWrite( 2 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, nValue);
else
mrStrm.WriteUInt16( nValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( sal_Int32 nValue )
{
PrepareWrite( 4 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, nValue);
else
mrStrm.WriteInt32( nValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( sal_uInt32 nValue )
{
PrepareWrite( 4 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, nValue);
else
mrStrm.WriteUInt32( nValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( float fValue )
{
PrepareWrite( 4 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, fValue);
else
mrStrm.WriteFloat( fValue );
return *this;
}
XclExpStream& XclExpStream::operator<<( double fValue )
{
PrepareWrite( 8 );
if (mbUseEncrypter && HasValidEncrypter())
mxEncrypter->Encrypt(mrStrm, fValue);
else
mrStrm.WriteDouble( fValue );
return *this;
}
std::size_t XclExpStream::Write( const void* pData, std::size_t nBytes )
{
std::size_t nRet = 0;
if( pData && (nBytes > 0) )
{
if( mbInRec )
{
const sal_uInt8* pBuffer = static_cast< const sal_uInt8* >( pData );
std::size_t nBytesLeft = nBytes;
bool bValid = true;
while( bValid && (nBytesLeft > 0) )
{
std::size_t nWriteLen = ::std::min< std::size_t >( PrepareWrite(), nBytesLeft );
std::size_t nWriteRet = nWriteLen;
if (mbUseEncrypter && HasValidEncrypter())
{
OSL_ENSURE(nWriteLen > 0, "XclExpStream::Write: write length is 0!");
vector<sal_uInt8> aBytes(nWriteLen);
memcpy(aBytes.data(), pBuffer, nWriteLen);
mxEncrypter->EncryptBytes(mrStrm, aBytes);
// TODO: How do I check if all the bytes have been successfully written ?
}
else
{
nWriteRet = mrStrm.WriteBytes(pBuffer, nWriteLen);
bValid = (nWriteLen == nWriteRet);
OSL_ENSURE( bValid, "XclExpStream::Write - stream write error" );
}
pBuffer += nWriteRet;
nRet += nWriteRet;
nBytesLeft -= nWriteRet;
UpdateSizeVars( nWriteRet );
}
}
else
nRet = mrStrm.WriteBytes(pData, nBytes);
}
return nRet;
}
void XclExpStream::WriteZeroBytes( std::size_t nBytes )
{
if( mbInRec )
{
std::size_t nBytesLeft = nBytes;
while( nBytesLeft > 0 )
{
std::size_t nWriteLen = ::std::min< std::size_t >( PrepareWrite(), nBytesLeft );
WriteRawZeroBytes( nWriteLen );
nBytesLeft -= nWriteLen;
UpdateSizeVars( nWriteLen );
}
}
else
WriteRawZeroBytes( nBytes );
}
void XclExpStream::WriteZeroBytesToRecord( std::size_t nBytes )
{
if (!mbInRec)
// not in record.
return;
for (std::size_t i = 0; i < nBytes; ++i)
*this << sal_uInt8(0)/*nZero*/;
}
void XclExpStream::CopyFromStream(SvStream& rInStrm, sal_uInt64 const nBytes)
{
sal_uInt64 const nRemaining(rInStrm.remainingSize());
sal_uInt64 nBytesLeft = ::std::min(nBytes, nRemaining);
if( nBytesLeft <= 0 )
return;
const std::size_t nMaxBuffer = 4096;
std::unique_ptr<sal_uInt8[]> pBuffer(
new sal_uInt8[ ::std::min<std::size_t>(nBytesLeft, nMaxBuffer) ]);
bool bValid = true;
while( bValid && (nBytesLeft > 0) )
{
std::size_t nWriteLen = ::std::min<std::size_t>(nBytesLeft, nMaxBuffer);
rInStrm.ReadBytes(pBuffer.get(), nWriteLen);
std::size_t nWriteRet = Write( pBuffer.get(), nWriteLen );
bValid = (nWriteLen == nWriteRet);
nBytesLeft -= nWriteRet;
}
}
void XclExpStream::WriteUnicodeBuffer( const ScfUInt16Vec& rBuffer, sal_uInt8 nFlags )
{
SetSliceSize( 0 );
nFlags &= EXC_STRF_16BIT; // repeat only 16bit flag
sal_uInt16 nCharLen = nFlags ? 2 : 1;
for( const auto& rItem : rBuffer )
{
if( mbInRec && (mnCurrSize + nCharLen > mnCurrMaxSize) )
{
StartContinue();
operator<<( nFlags );
}
if( nCharLen == 2 )
operator<<( rItem );
else
operator<<( static_cast< sal_uInt8 >( rItem ) );
}
}
// Xcl has an obscure sense of whether starting a new record or not,
// and crashes if it encounters the string header at the very end of a record.
// Thus we add 1 to give some room, seems like they do it that way but with another count (10?)
void XclExpStream::WriteByteString( const OString& rString )
{
SetSliceSize( 0 );
std::size_t nLen = ::std::min< std::size_t >( rString.getLength(), 0x00FF );
nLen = ::std::min< std::size_t >( nLen, 0xFF );
sal_uInt16 nLeft = PrepareWrite();
if( mbInRec && (nLeft <= 1) )
StartContinue();
operator<<( static_cast< sal_uInt8 >( nLen ) );
Write( rString.getStr(), nLen );
}
void XclExpStream::WriteCharBuffer( const ScfUInt8Vec& rBuffer )
{
SetSliceSize( 0 );
Write( rBuffer.data(), rBuffer.size() );
}
void XclExpStream::SetEncrypter( XclExpEncrypterRef const & xEncrypter )
{
mxEncrypter = xEncrypter;
}
bool XclExpStream::HasValidEncrypter() const
{
return mxEncrypter && mxEncrypter->IsValid();
}
void XclExpStream::EnableEncryption( bool bEnable )
{
mbUseEncrypter = bEnable && HasValidEncrypter();
}
void XclExpStream::DisableEncryption()
{
EnableEncryption(false);
}
void XclExpStream::SetSvStreamPos(sal_uInt64 const nPos)
{
OSL_ENSURE( !mbInRec, "XclExpStream::SetSvStreamPos - not allowed inside of a record" );
mbInRec ? 0 : mrStrm.Seek( nPos );
}
// private --------------------------------------------------------------------
void XclExpStream::InitRecord( sal_uInt16 nRecId )
{
mrStrm.Seek( STREAM_SEEK_TO_END );
mrStrm.WriteUInt16( nRecId );
mnLastSizePos = mrStrm.Tell();
mnHeaderSize = static_cast< sal_uInt16 >( ::std::min< std::size_t >( mnPredictSize, mnCurrMaxSize ) );
mrStrm.WriteUInt16( mnHeaderSize );
mnCurrSize = mnSliceSize = 0;
}
void XclExpStream::UpdateRecSize()
{
if( mnCurrSize != mnHeaderSize )
{
mrStrm.Seek( mnLastSizePos );
mrStrm.WriteUInt16( mnCurrSize );
}
}
void XclExpStream::UpdateSizeVars( std::size_t nSize )
{
OSL_ENSURE( mnCurrSize + nSize <= mnCurrMaxSize, "XclExpStream::UpdateSizeVars - record overwritten" );
mnCurrSize = mnCurrSize + static_cast< sal_uInt16 >( nSize );
if( mnMaxSliceSize > 0 )
{
OSL_ENSURE( mnSliceSize + nSize <= mnMaxSliceSize, "XclExpStream::UpdateSizeVars - slice overwritten" );
mnSliceSize = mnSliceSize + static_cast< sal_uInt16 >( nSize );
if( mnSliceSize >= mnMaxSliceSize )
mnSliceSize = 0;
}
}
void XclExpStream::StartContinue()
{
UpdateRecSize();
mnCurrMaxSize = mnMaxContSize;
mnPredictSize -= mnCurrSize;
InitRecord( EXC_ID_CONT );
}
void XclExpStream::PrepareWrite( sal_uInt16 nSize )
{
if( mbInRec )
{
if( (mnCurrSize + nSize > mnCurrMaxSize) ||
((mnMaxSliceSize > 0) && (mnSliceSize == 0) && (mnCurrSize + mnMaxSliceSize > mnCurrMaxSize)) )
StartContinue();
UpdateSizeVars( nSize );
}
}
sal_uInt16 XclExpStream::PrepareWrite()
{
sal_uInt16 nRet = 0;
if( mbInRec )
{
if( (mnCurrSize >= mnCurrMaxSize) ||
((mnMaxSliceSize > 0) && (mnSliceSize == 0) && (mnCurrSize + mnMaxSliceSize > mnCurrMaxSize)) )
StartContinue();
UpdateSizeVars( 0 );
nRet = (mnMaxSliceSize > 0) ? (mnMaxSliceSize - mnSliceSize) : (mnCurrMaxSize - mnCurrSize);
}
return nRet;
}
void XclExpStream::WriteRawZeroBytes( std::size_t nBytes )
{
const sal_uInt32 nData = 0;
std::size_t nBytesLeft = nBytes;
while( nBytesLeft >= sizeof( nData ) )
{
mrStrm.WriteUInt32( nData );
nBytesLeft -= sizeof( nData );
}
if( nBytesLeft )
mrStrm.WriteBytes(&nData, nBytesLeft);
}
XclExpBiff8Encrypter::XclExpBiff8Encrypter( const XclExpRoot& rRoot ) :
mnOldPos(STREAM_SEEK_TO_END),
mbValid(false)
{
Sequence< NamedValue > aEncryptionData = rRoot.GetEncryptionData();
if( !aEncryptionData.hasElements() )
// Empty password. Get the default biff8 password.
aEncryptionData = XclExpRoot::GenerateDefaultEncryptionData();
Init( aEncryptionData );
}
XclExpBiff8Encrypter::~XclExpBiff8Encrypter()
{
}
void XclExpBiff8Encrypter::GetSaltDigest( sal_uInt8 pnSaltDigest[16] ) const
{
if ( sizeof( mpnSaltDigest ) == 16 )
memcpy( pnSaltDigest, mpnSaltDigest, 16 );
}
void XclExpBiff8Encrypter::GetSalt( sal_uInt8 pnSalt[16] ) const
{
if ( sizeof( mpnSalt ) == 16 )
memcpy( pnSalt, mpnSalt, 16 );
}
void XclExpBiff8Encrypter::GetDocId( sal_uInt8 pnDocId[16] ) const
{
if ( sizeof( mpnDocId ) == 16 )
memcpy( pnDocId, mpnDocId, 16 );
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt8 nData )
{
vector<sal_uInt8> aByte { nData };
EncryptBytes(rStrm, aByte);
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt16 nData )
{
::std::vector<sal_uInt8> pnBytes
{
o3tl::narrowing<sal_uInt8>(nData & 0xFF),
o3tl::narrowing<sal_uInt8>((nData >> 8) & 0xFF)
};
EncryptBytes(rStrm, pnBytes);
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt32 nData )
{
::std::vector<sal_uInt8> pnBytes
{
o3tl::narrowing<sal_uInt8>(nData & 0xFF),
o3tl::narrowing<sal_uInt8>((nData >> 8) & 0xFF),
o3tl::narrowing<sal_uInt8>((nData >> 16) & 0xFF),
o3tl::narrowing<sal_uInt8>((nData >> 24) & 0xFF)
};
EncryptBytes(rStrm, pnBytes);
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, float fValue )
{
::std::vector<sal_uInt8> pnBytes(4);
memcpy(pnBytes.data(), &fValue, 4);
EncryptBytes(rStrm, pnBytes);
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, double fValue )
{
::std::vector<sal_uInt8> pnBytes(8);
memcpy(pnBytes.data(), &fValue, 8);
EncryptBytes(rStrm, pnBytes);
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int8 nData )
{
Encrypt(rStrm, static_cast<sal_uInt8>(nData));
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int16 nData )
{
Encrypt(rStrm, static_cast<sal_uInt16>(nData));
}
void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int32 nData )
{
Encrypt(rStrm, static_cast<sal_uInt32>(nData));
}
void XclExpBiff8Encrypter::Init( const Sequence< NamedValue >& rEncryptionData )
{
mbValid = false;
if( !maCodec.InitCodec( rEncryptionData ) )
return;
maCodec.GetDocId( mpnDocId );
// generate the salt here
if (rtl_random_getBytes(nullptr, mpnSalt, 16) != rtl_Random_E_None)
{
throw uno::RuntimeException(u"rtl_random_getBytes failed"_ustr);
}
memset( mpnSaltDigest, 0, sizeof( mpnSaltDigest ) );
// generate salt hash.
::msfilter::MSCodec_Std97 aCodec;
aCodec.InitCodec( rEncryptionData );
aCodec.CreateSaltDigest( mpnSalt, mpnSaltDigest );
// verify to make sure it's in good shape.
mbValid = maCodec.VerifyKey( mpnSalt, mpnSaltDigest );
}
sal_uInt32 XclExpBiff8Encrypter::GetBlockPos( std::size_t nStrmPos )
{
return static_cast< sal_uInt32 >( nStrmPos / EXC_ENCR_BLOCKSIZE );
}
sal_uInt16 XclExpBiff8Encrypter::GetOffsetInBlock( std::size_t nStrmPos )
{
return static_cast< sal_uInt16 >( nStrmPos % EXC_ENCR_BLOCKSIZE );
}
void XclExpBiff8Encrypter::EncryptBytes( SvStream& rStrm, vector<sal_uInt8>& aBytes )
{
sal_uInt64 nStrmPos = rStrm.Tell();
sal_uInt16 nBlockOffset = GetOffsetInBlock(nStrmPos);
sal_uInt32 nBlockPos = GetBlockPos(nStrmPos);
SAL_INFO("sc.filter", "XclExpBiff8Encrypter::EncryptBytes: stream pos = "
<< nStrmPos << " offset in block = " << nBlockOffset
<< " block pos = " << nBlockPos);
sal_uInt16 nSize = static_cast< sal_uInt16 >( aBytes.size() );
if (nSize == 0)
return;
#if DEBUG_XL_ENCRYPTION
fprintf(stdout, "RAW: ");
for (sal_uInt16 i = 0; i < nSize; ++i)
fprintf(stdout, "%2.2X ", aBytes[i]);
fprintf(stdout, "\n");
#endif
if (mnOldPos != nStrmPos)
{
sal_uInt16 nOldOffset = GetOffsetInBlock(mnOldPos);
sal_uInt32 nOldBlockPos = GetBlockPos(mnOldPos);
if ( (nBlockPos != nOldBlockPos) || (nBlockOffset < nOldOffset) )
{
maCodec.InitCipher(nBlockPos);
nOldOffset = 0;
}
if (nBlockOffset > nOldOffset)
maCodec.Skip(nBlockOffset - nOldOffset);
}
sal_uInt16 nBytesLeft = nSize;
sal_uInt16 nPos = 0;
while (nBytesLeft > 0)
{
sal_uInt16 nBlockLeft = EXC_ENCR_BLOCKSIZE - nBlockOffset;
sal_uInt16 nEncBytes = ::std::min(nBlockLeft, nBytesLeft);
bool bRet = maCodec.Encode(&aBytes[nPos], nEncBytes, &aBytes[nPos], nEncBytes);
OSL_ENSURE(bRet, "XclExpBiff8Encrypter::EncryptBytes: encryption failed!!");
std::size_t nRet = rStrm.WriteBytes(&aBytes[nPos], nEncBytes);
OSL_ENSURE(nRet == nEncBytes, "XclExpBiff8Encrypter::EncryptBytes: fail to write to stream!!");
nStrmPos = rStrm.Tell();
nBlockOffset = GetOffsetInBlock(nStrmPos);
nBlockPos = GetBlockPos(nStrmPos);
if (nBlockOffset == 0)
maCodec.InitCipher(nBlockPos);
nBytesLeft -= nEncBytes;
nPos += nEncBytes;
}
mnOldPos = nStrmPos;
}
static const char* lcl_GetErrorString( FormulaError nScErrCode )
{
sal_uInt8 nXclErrCode = XclTools::GetXclErrorCode( nScErrCode );
switch( nXclErrCode )
{
case EXC_ERR_NULL: return "#NULL!";
case EXC_ERR_DIV0: return "#DIV/0!";
case EXC_ERR_VALUE: return "#VALUE!";
case EXC_ERR_REF: return "#REF!";
case EXC_ERR_NAME: return "#NAME?";
case EXC_ERR_NUM: return "#NUM!";
case EXC_ERR_NA:
default: return "#N/A";
}
}
void XclXmlUtils::GetFormulaTypeAndValue( ScFormulaCell& rCell, const char*& rsType, OUString& rsValue )
{
sc::FormulaResultValue aResValue = rCell.GetResult();
switch (aResValue.meType)
{
case sc::FormulaResultValue::Error:
rsType = "e";
rsValue = ToOUString(lcl_GetErrorString(aResValue.mnError));
break;
case sc::FormulaResultValue::Value:
rsType = rCell.GetFormatType() == SvNumFormatType::LOGICAL
&& (aResValue.mfValue == 0.0 || aResValue.mfValue == 1.0)
? "b"
: "n";
rsValue = OUString::number(aResValue.mfValue);
break;
case sc::FormulaResultValue::String:
rsType = "str";
rsValue = rCell.GetString().getString();
break;
case sc::FormulaResultValue::Invalid:
default:
// TODO : double-check this to see if this is correct.
rsType = "inlineStr";
rsValue = rCell.GetString().getString();
}
}
OUString XclXmlUtils::GetStreamName( const char* sStreamDir, const char* sStream, sal_Int32 nId )
{
OUStringBuffer sBuf;
if( sStreamDir )
sBuf.appendAscii( sStreamDir );
sBuf.appendAscii( sStream );
if( nId )
sBuf.append( nId );
if( strstr(sStream, "vml") )
sBuf.append( ".vml" );
else
sBuf.append( ".xml" );
return sBuf.makeStringAndClear();
}
OString XclXmlUtils::ToOString( const Color& rColor )
{
char buf[9];
o3tl::sprintf( buf, "%.2X%.2X%.2X%.2X", rColor.GetAlpha(), rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() );
buf[8] = '\0';
return buf;
}
OStringBuffer& XclXmlUtils::ToOString( OStringBuffer& s, const ScAddress& rAddress )
{
rAddress.Format(s, ScRefFlags::VALID, nullptr, ScAddress::Details( FormulaGrammar::CONV_XL_A1));
return s;
}
OString XclXmlUtils::ToOString( const ScfUInt16Vec& rBuffer )
{
if(rBuffer.empty())
return OString();
const sal_uInt16* pBuffer = rBuffer.data();
return OString(
reinterpret_cast<sal_Unicode const *>(pBuffer), rBuffer.size(),
RTL_TEXTENCODING_UTF8);
}
OString XclXmlUtils::ToOString( const ScDocument& rDoc, const ScRange& rRange, bool bFullAddressNotation )
{
OUString sRange(rRange.Format( rDoc, ScRefFlags::VALID,
ScAddress::Details( FormulaGrammar::CONV_XL_A1 ),
bFullAddressNotation ) );
return sRange.toUtf8();
}
OString XclXmlUtils::ToOString( const ScDocument& rDoc, const ScRangeList& rRangeList )
{
OUString s;
rRangeList.Format(s, ScRefFlags::VALID, rDoc, FormulaGrammar::CONV_XL_OOX, ' ');
return s.toUtf8();
}
static ScAddress lcl_ToAddress( const XclAddress& rAddress )
{
return ScAddress( rAddress.mnCol, rAddress.mnRow, 0 );
}
OStringBuffer& XclXmlUtils::ToOString( OStringBuffer& s, const XclAddress& rAddress )
{
return ToOString( s, lcl_ToAddress( rAddress ));
}
OString XclXmlUtils::ToOString( const XclExpString& s )
{
OSL_ENSURE( !s.IsRich(), "XclXmlUtils::ToOString(XclExpString): rich text string found!" );
return ToOString( s.GetUnicodeBuffer() );
}
static ScRange lcl_ToRange( const XclRange& rRange )
{
ScRange aRange;
aRange.aStart = lcl_ToAddress( rRange.maFirst );
aRange.aEnd = lcl_ToAddress( rRange.maLast );
return aRange;
}
OString XclXmlUtils::ToOString( const ScDocument& rDoc, const XclRangeList& rRanges )
{
ScRangeList aRanges;
for( const auto& rRange : rRanges )
{
aRanges.push_back( lcl_ToRange( rRange ) );
}
return ToOString( rDoc, aRanges );
}
OUString XclXmlUtils::ToOUString( const char* s )
{
return OUString( s, static_cast<sal_Int32>(strlen( s )), RTL_TEXTENCODING_ASCII_US );
}
OUString XclXmlUtils::ToOUString( const ScfUInt16Vec& rBuf, sal_Int32 nStart, sal_Int32 nLength )
{
if( nLength == -1 || ( nLength > (static_cast<sal_Int32>(rBuf.size()) - nStart) ) )
nLength = (rBuf.size() - nStart);
return nLength > 0
? OUString(
reinterpret_cast<sal_Unicode const *>(&rBuf[nStart]), nLength)
: OUString();
}
OUString XclXmlUtils::ToOUString(
sc::CompileFormulaContext& rCtx, const ScAddress& rAddress, const ScTokenArray* pTokenArray,
FormulaError nErrCode )
{
ScCompiler aCompiler( rCtx, rAddress, const_cast<ScTokenArray&>(*pTokenArray));
/* TODO: isn't this the same as passed in rCtx and thus superfluous? */
aCompiler.SetGrammar(FormulaGrammar::GRAM_OOXML);
sal_Int32 nLen = pTokenArray->GetLen();
OUStringBuffer aBuffer( nLen ? (nLen * 5) : 8 );
if (nLen)
aCompiler.CreateStringFromTokenArray( aBuffer );
else
{
if (nErrCode != FormulaError::NONE)
aCompiler.AppendErrorConstant( aBuffer, nErrCode);
else
{
// No code SHOULD be an "error cell", assert caller thought of that
// and it really is.
assert(!"No code and no error.");
}
}
return aBuffer.makeStringAndClear();
}
OUString XclXmlUtils::ToOUString( const XclExpString& s )
{
OSL_ENSURE( !s.IsRich(), "XclXmlUtils::ToOString(XclExpString): rich text string found!" );
return ToOUString( s.GetUnicodeBuffer() );
}
static const char* lcl_GetUnderlineStyle( FontLineStyle eUnderline, bool& bHaveUnderline )
{
bHaveUnderline = true;
switch( eUnderline )
{
// OOXTODO: doubleAccounting, singleAccounting
// OOXTODO: what should be done with the other FontLineStyle values?
case LINESTYLE_SINGLE: return "single";
case LINESTYLE_DOUBLE: return "double";
case LINESTYLE_NONE:
default: bHaveUnderline = false; return "none";
}
}
static const char* lcl_ToVerticalAlignmentRun( SvxEscapement eEscapement, bool& bHaveAlignment )
{
bHaveAlignment = true;
switch( eEscapement )
{
case SvxEscapement::Superscript: return "superscript";
case SvxEscapement::Subscript: return "subscript";
case SvxEscapement::Off:
default: bHaveAlignment = false; return "baseline";
}
}
sax_fastparser::FSHelperPtr XclXmlUtils::WriteFontData( sax_fastparser::FSHelperPtr pStream, const XclFontData& rFontData, sal_Int32 nFontId )
{
bool bHaveUnderline, bHaveVertAlign;
const char* pUnderline = lcl_GetUnderlineStyle( rFontData.GetScUnderline(), bHaveUnderline );
const char* pVertAlign = lcl_ToVerticalAlignmentRun( rFontData.GetScEscapement(), bHaveVertAlign );
if (rFontData.mnWeight > 400)
pStream->singleElement(XML_b, XML_val, ToPsz( true ));
if (rFontData.mbItalic)
pStream->singleElement(XML_i, XML_val, ToPsz( true ));
if (rFontData.mbStrikeout)
pStream->singleElement(XML_strike, XML_val, ToPsz( true ));
// OOXTODO: lcl_WriteValue( rStream, XML_condense, ); // mac compatibility setting
// OOXTODO: lcl_WriteValue( rStream, XML_extend, ); // compatibility setting
if (rFontData.mbOutline)
pStream->singleElement(XML_outline, XML_val, ToPsz( true ));
if (rFontData.mbShadow)
pStream->singleElement(XML_shadow, XML_val, ToPsz( true ));
if (bHaveUnderline)
pStream->singleElement(XML_u, XML_val, pUnderline);
if (bHaveVertAlign)
pStream->singleElement(XML_vertAlign, XML_val, pVertAlign);
pStream->singleElement(XML_sz, XML_val, OString::number( rFontData.mnHeight / 20.0 )); // Twips->Pt
auto& rComplexColor = rFontData.maComplexColor;
if (rComplexColor.isValidThemeType())
{
sal_Int32 nTheme = oox::convertThemeColorTypeToExcelThemeNumber(rComplexColor.getThemeColorType());
double fTintShade = oox::convertColorTransformsToTintOrShade(rComplexColor);
pStream->singleElement(XML_color,
XML_theme, OString::number(nTheme),
XML_tint, sax_fastparser::UseIf(OString::number(fTintShade), fTintShade != 0.0));
}
else if (rComplexColor.getFinalColor() != Color( ColorAlpha, 0, 0xFF, 0xFF, 0xFF))
{
pStream->singleElement(XML_color,
XML_rgb, XclXmlUtils::ToOString(rComplexColor.getFinalColor()));
}
pStream->singleElement(nFontId, XML_val, rFontData.maName);
pStream->singleElement(XML_family, XML_val, OString::number( rFontData.mnFamily ));
if (rFontData.mnCharSet != 0)
pStream->singleElement(XML_charset, XML_val, OString::number(rFontData.mnCharSet));
return pStream;
}
XclExpXmlStream::XclExpXmlStream( const uno::Reference< XComponentContext >& rCC, bool bExportVBA, bool bExportTemplate )
: XmlFilterBase( rCC ),
mpRoot( nullptr ),
mbExportVBA(bExportVBA),
mbExportTemplate(bExportTemplate)
{
}
XclExpXmlStream::~XclExpXmlStream()
{
assert(maStreams.empty() && "Forgotten PopStream()?");
}
sax_fastparser::FSHelperPtr& XclExpXmlStream::GetCurrentStream()
{
OSL_ENSURE( !maStreams.empty(), "XclExpXmlStream::GetCurrentStream - no current stream" );
return maStreams.top();
}
void XclExpXmlStream::PushStream( sax_fastparser::FSHelperPtr const & aStream )
{
maStreams.push( aStream );
}
void XclExpXmlStream::PopStream()
{
OSL_ENSURE( !maStreams.empty(), "XclExpXmlStream::PopStream - stack is empty!" );
maStreams.pop();
}
sax_fastparser::FSHelperPtr XclExpXmlStream::GetStreamForPath( const OUString& sPath )
{
if( maOpenedStreamMap.find( sPath ) == maOpenedStreamMap.end() )
return sax_fastparser::FSHelperPtr();
return maOpenedStreamMap[ sPath ].second;
}
void XclExpXmlStream::WriteAttribute(sal_Int32 nAttr, std::u16string_view sVal)
{
GetCurrentStream()->write(" ")->writeId(nAttr)->write("=\"")->writeEscaped(sVal)->write("\"");
}
sax_fastparser::FSHelperPtr XclExpXmlStream::CreateOutputStream (
const OUString& sFullStream,
std::u16string_view sRelativeStream,
const uno::Reference< XOutputStream >& xParentRelation,
const char* sContentType,
std::u16string_view sRelationshipType,
OUString* pRelationshipId )
{
OUString sRelationshipId;
if (xParentRelation.is())
sRelationshipId = addRelation( xParentRelation, OUString(sRelationshipType), sRelativeStream );
else
sRelationshipId = addRelation( OUString(sRelationshipType), sRelativeStream );
if( pRelationshipId )
*pRelationshipId = sRelationshipId;
sax_fastparser::FSHelperPtr p = openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) );
maOpenedStreamMap[ sFullStream ] = std::make_pair( sRelationshipId, p );
return p;
}
bool XclExpXmlStream::importDocument() noexcept
{
return false;
}
oox::vml::Drawing* XclExpXmlStream::getVmlDrawing()
{
return nullptr;
}
const oox::drawingml::Theme* XclExpXmlStream::getCurrentTheme() const
{
return nullptr;
}
oox::drawingml::table::TableStyleListPtr XclExpXmlStream::getTableStyles()
{
return oox::drawingml::table::TableStyleListPtr();
}
oox::drawingml::chart::ChartConverter* XclExpXmlStream::getChartConverter()
{
// DO NOT CALL
return nullptr;
}
ScDocShell* XclExpXmlStream::getDocShell()
{
uno::Reference< XInterface > xModel( getModel(), UNO_QUERY );
ScModelObj *pObj = comphelper::getFromUnoTunnel < ScModelObj >( xModel );
if ( pObj )
return static_cast < ScDocShell* >( pObj->GetEmbeddedObject() );
return nullptr;
}
bool XclExpXmlStream::exportDocument()
{
ScDocShell* pShell = getDocShell();
ScDocument& rDoc = pShell->GetDocument();
ScRefreshTimerProtector aProt(rDoc.GetRefreshTimerControlAddress());
const bool bValidateTabNames = officecfg::Office::Calc::Filter::Export::MS_Excel::TruncateLongSheetNames::get();
std::vector<OUString> aOriginalTabNames;
if (bValidateTabNames)
{
validateTabNames(aOriginalTabNames);
}
uno::Reference<task::XStatusIndicator> xStatusIndicator = getStatusIndicator();
if (xStatusIndicator.is())
xStatusIndicator->start(ScResId(STR_SAVE_DOC), 100);
// NOTE: Don't use SotStorage or SvStream any more, and never call
// SfxMedium::GetOutStream() anywhere in the xlsx export filter code!
// Instead, write via XOutputStream instance.
rtl::Reference<SotStorage> rStorage;
drawingml::DrawingML::ResetMlCounters();
auto& rGraphicExportCache = drawingml::GraphicExportCache::get();
rGraphicExportCache.push();
XclExpRootData aData(
EXC_BIFF8, *pShell->GetMedium (), rStorage, rDoc,
msfilter::util::getBestTextEncodingFromLocale(
Application::GetSettings().GetLanguageTag().getLocale()));
aData.meOutput = EXC_OUTPUT_XML_2007;
aData.maXclMaxPos.Set( EXC_MAXCOL_XML_2007, EXC_MAXROW_XML_2007, EXC_MAXTAB_XML_2007 );
aData.maMaxPos.SetCol( ::std::min( aData.maScMaxPos.Col(), aData.maXclMaxPos.Col() ) );
aData.maMaxPos.SetRow( ::std::min( aData.maScMaxPos.Row(), aData.maXclMaxPos.Row() ) );
aData.maMaxPos.SetTab( ::std::min( aData.maScMaxPos.Tab(), aData.maXclMaxPos.Tab() ) );
aData.mpCompileFormulaCxt = std::make_shared<sc::CompileFormulaContext>(rDoc);
// set target path to get correct relative links to target document, not source
INetURLObject aPath(getFileUrl());
aData.maBasePath = OUString("file:///" + aPath.GetPath() + "\\").replace('\\', '/')
// fix for Linux
.replaceFirst("file:////", "file:///");
XclExpRoot aRoot( aData );
mpRoot = &aRoot;
aRoot.GetOldRoot().pER = &aRoot;
aRoot.GetOldRoot().eDateiTyp = Biff8;
// Get the viewsettings before processing
if (ScViewData* pViewData = ScDocShell::GetViewData())
pViewData->WriteExtOptions( mpRoot->GetExtDocOptions() );
else
{
// Try to get ScViewData through the current ScDocShell
ScTabViewShell* pTabViewShell = pShell->GetBestViewShell( false );
if ( pTabViewShell )
{
pViewData = &pTabViewShell->GetViewData();
pViewData->WriteExtOptions( mpRoot->GetExtDocOptions() );
}
}
static constexpr OUString workbook = u"xl/workbook.xml"_ustr;
const char* pWorkbookContentType = nullptr;
if (mbExportVBA)
{
if (mbExportTemplate)
{
pWorkbookContentType = "application/vnd.ms-excel.template.macroEnabled.main+xml";
}
else
{
pWorkbookContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml";
}
}
else
{
if (mbExportTemplate)
{
pWorkbookContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml";
}
else
{
pWorkbookContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
}
}
PushStream( CreateOutputStream( workbook, workbook,
uno::Reference <XOutputStream>(),
pWorkbookContentType,
oox::getRelationship(Relationship::OFFICEDOCUMENT) ) );
if (mbExportVBA)
{
VbaExport aExport(getModel());
if (aExport.containsVBAProject())
{
SvMemoryStream aVbaStream(4096, 4096);
rtl::Reference<SotStorage> pVBAStorage(new SotStorage(aVbaStream));
aExport.exportVBA( pVBAStorage.get() );
aVbaStream.Seek(0);
css::uno::Reference<css::io::XInputStream> xVBAStream(
new utl::OInputStreamWrapper(aVbaStream));
css::uno::Reference<css::io::XOutputStream> xVBAOutput =
openFragmentStream(u"xl/vbaProject.bin"_ustr, u"application/vnd.ms-office.vbaProject"_ustr);
comphelper::OStorageHelper::CopyInputToOutput(xVBAStream, xVBAOutput);
addRelation(GetCurrentStream()->getOutputStream(), oox::getRelationship(Relationship::VBAPROJECT), u"vbaProject.bin");
}
}
// destruct at the end of the block
{
ExcDocument aDocRoot( aRoot );
if (xStatusIndicator.is())
xStatusIndicator->setValue(10);
aDocRoot.ReadDoc();
if (xStatusIndicator.is())
xStatusIndicator->setValue(40);
aDocRoot.WriteXml( *this );
rDoc.GetExternalRefManager()->disableSkipUnusedFileIds();
}
PopStream();
// Free all FSHelperPtr, to flush data before committing storage
for (auto& entry : maOpenedStreamMap)
{
if (!entry.second.second)
continue;
entry.second.second->endDocument();
}
maOpenedStreamMap.clear();
commitStorage();
if (bValidateTabNames)
{
restoreTabNames(aOriginalTabNames);
}
if (xStatusIndicator.is())
xStatusIndicator->end();
mpRoot = nullptr;
rGraphicExportCache.pop();
return true;
}
::oox::ole::VbaProject* XclExpXmlStream::implCreateVbaProject() const
{
return new ::oox::xls::ExcelVbaProject( getComponentContext(), uno::Reference< XSpreadsheetDocument >( getModel(), UNO_QUERY ) );
}
OUString XclExpXmlStream::getImplementationName()
{
return u"TODO"_ustr;
}
void XclExpXmlStream::validateTabNames(std::vector<OUString>& aOriginalTabNames)
{
const int MAX_TAB_NAME_LENGTH = 31;
ScDocShell* pShell = getDocShell();
ScDocument& rDoc = pShell->GetDocument();
// get original names
aOriginalTabNames.resize(rDoc.GetTableCount());
for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++)
{
rDoc.GetName(nTab, aOriginalTabNames[nTab]);
}
// new tab names
std::vector<OUString> aNewTabNames;
aNewTabNames.reserve(rDoc.GetTableCount());
// check and rename
for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++)
{
const OUString& rOriginalName = aOriginalTabNames[nTab];
if (rOriginalName.getLength() > MAX_TAB_NAME_LENGTH)
{
OUString aNewName;
// let's try just truncate "<first 31 chars>"
if (aNewName.isEmpty())
{
aNewName = rOriginalName.copy(0, MAX_TAB_NAME_LENGTH);
if (aNewTabNames.end() != std::find(aNewTabNames.begin(), aNewTabNames.end(), aNewName) ||
aOriginalTabNames.end() != std::find(aOriginalTabNames.begin(), aOriginalTabNames.end(), aNewName))
{
// was found => let's use another tab name
aNewName.clear();
}
}
// let's try "<first N chars>-XXX" template
for (int digits=1; digits<10 && aNewName.isEmpty(); digits++)
{
const int rangeStart = pow(10, digits - 1);
const int rangeEnd = pow(10, digits);
for (int i=rangeStart; i<rangeEnd && aNewName.isEmpty(); i++)
{
aNewName = OUString::Concat(rOriginalName.subView(0, MAX_TAB_NAME_LENGTH - 1 - digits)) + "-" + OUString::number(i);
if (aNewTabNames.end() != std::find(aNewTabNames.begin(), aNewTabNames.end(), aNewName) ||
aOriginalTabNames.end() != std::find(aOriginalTabNames.begin(), aOriginalTabNames.end(), aNewName))
{
// was found => let's use another tab name
aNewName.clear();
}
}
}
if (!aNewName.isEmpty())
{
// new name was created => rename
renameTab(nTab, aNewName);
aNewTabNames.push_back(aNewName);
}
else
{
// default: do not rename
aNewTabNames.push_back(rOriginalName);
}
}
else
{
// default: do not rename
aNewTabNames.push_back(rOriginalName);
}
}
}
void XclExpXmlStream::restoreTabNames(const std::vector<OUString>& aOriginalTabNames)
{
ScDocShell* pShell = getDocShell();
ScDocument& rDoc = pShell->GetDocument();
for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++)
{
const OUString& rOriginalName = aOriginalTabNames[nTab];
OUString rModifiedName;
rDoc.GetName(nTab, rModifiedName);
if (rOriginalName != rModifiedName)
{
renameTab(nTab, rOriginalName);
}
}
}
void XclExpXmlStream::renameTab(SCTAB aTab, OUString aNewName)
{
ScDocShell* pShell = getDocShell();
ScDocument& rDoc = pShell->GetDocument();
bool bAutoCalcShellDisabled = rDoc.IsAutoCalcShellDisabled();
bool bIdleEnabled = rDoc.IsIdleEnabled();
rDoc.SetAutoCalcShellDisabled( true );
rDoc.EnableIdle(false);
if (rDoc.RenameTab(aTab, aNewName))
{
SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScTablesChanged));
}
rDoc.SetAutoCalcShellDisabled( bAutoCalcShellDisabled );
rDoc.EnableIdle(bIdleEnabled);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'appendAscii' is required to be utilized.
↑ V530 The return value of function 'appendAscii' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'addRelation' is required to be utilized.
↑ V547 Expression '!"No code and no error."' is always false.
↑ V1051 Consider checking for misprints. It's possible that the 'mnCurrSize' should be checked here.