/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <memory>
#include <utility>
#include <xecontent.hxx>
#include <vector>
#include <algorithm>
#include <string_view>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/sheet/XAreaLinks.hpp>
#include <com/sun/star/sheet/XAreaLink.hpp>
#include <com/sun/star/sheet/TableValidationVisibility.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <sfx2/objsh.hxx>
#include <tools/urlobj.hxx>
#include <formula/grammar.hxx>
#include <scitems.hxx>
#include <editeng/flditem.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <validat.hxx>
#include <unonames.hxx>
#include <convuno.hxx>
#include <rangenam.hxx>
#include <tokenarray.hxx>
#include <stlpool.hxx>
#include <patattr.hxx>
#include <fapihelper.hxx>
#include <xehelper.hxx>
#include <xestyle.hxx>
#include <xename.hxx>
#include <xlcontent.hxx>
#include <xltools.hxx>
#include <xeformula.hxx>
#include <rtl/uuid.h>
#include <sal/log.hxx>
#include <oox/export/utils.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/relationship.hxx>
#include <comphelper/string.hxx>
#include <o3tl/string_view.hxx>
using namespace ::oox;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::table::CellRangeAddress;
using ::com::sun::star::sheet::XAreaLinks;
using ::com::sun::star::sheet::XAreaLink;
// Shared string table ========================================================
namespace {
/** A single string entry in the hash table. */
struct XclExpHashEntry
{
const XclExpString* mpString; /// Pointer to the string (no ownership).
sal_uInt32 mnSstIndex; /// The SST index of this string.
explicit XclExpHashEntry( const XclExpString* pString, sal_uInt32 nSstIndex ) :
mpString( pString ), mnSstIndex( nSstIndex ) {}
};
/** Function object for strict weak ordering. */
struct XclExpHashEntrySWO
{
bool operator()( const XclExpHashEntry& rLeft, const XclExpHashEntry& rRight ) const
{ return *rLeft.mpString < *rRight.mpString; }
};
}
/** Implementation of the SST export.
@descr Stores all passed strings in a hash table and prevents repeated
insertion of equal strings. */
class XclExpSstImpl
{
public:
explicit XclExpSstImpl();
/** Inserts the passed string, if not already inserted, and returns the unique SST index. */
sal_uInt32 Insert( XclExpStringRef xString );
/** Writes the complete SST and EXTSST records. */
void Save( XclExpStream& rStrm );
void SaveXml( XclExpXmlStream& rStrm );
private:
typedef ::std::vector< XclExpHashEntry > XclExpHashVec;
std::vector< XclExpStringRef > maStringVector; /// List of unique strings (in SST ID order).
std::vector< XclExpHashVec >
maHashTab; /// Hashed table that manages string pointers.
sal_uInt32 mnTotal; /// Total count of strings (including doubles).
sal_uInt32 mnSize; /// Size of the SST (count of unique strings).
};
const sal_uInt32 EXC_SST_HASHTABLE_SIZE = 2048;
XclExpSstImpl::XclExpSstImpl() :
maHashTab( EXC_SST_HASHTABLE_SIZE ),
mnTotal( 0 ),
mnSize( 0 )
{
}
sal_uInt32 XclExpSstImpl::Insert( XclExpStringRef xString )
{
OSL_ENSURE( xString, "XclExpSstImpl::Insert - empty pointer not allowed" );
if( !xString )
xString.reset( new XclExpString );
++mnTotal;
sal_uInt32 nSstIndex = 0;
// calculate hash value in range [0,EXC_SST_HASHTABLE_SIZE)
sal_uInt16 nHash = xString->GetHash();
nHash = (nHash ^ (nHash / EXC_SST_HASHTABLE_SIZE)) % EXC_SST_HASHTABLE_SIZE;
XclExpHashVec& rVec = maHashTab[ nHash ];
XclExpHashEntry aEntry( xString.get(), mnSize );
XclExpHashVec::iterator aIt = ::std::lower_bound( rVec.begin(), rVec.end(), aEntry, XclExpHashEntrySWO() );
if( (aIt == rVec.end()) || (*aIt->mpString != *xString) )
{
nSstIndex = mnSize;
maStringVector.push_back( xString );
rVec.insert( aIt, aEntry );
++mnSize;
}
else
{
nSstIndex = aIt->mnSstIndex;
}
return nSstIndex;
}
void XclExpSstImpl::Save( XclExpStream& rStrm )
{
if( maStringVector.empty() )
return;
SvMemoryStream aExtSst( 8192 );
sal_uInt32 nBucket = mnSize;
while( nBucket > 0x0100 )
nBucket /= 2;
sal_uInt16 nPerBucket = llimit_cast< sal_uInt16 >( nBucket, 8 );
sal_uInt16 nBucketIndex = 0;
// *** write the SST record ***
rStrm.StartRecord( EXC_ID_SST, 8 );
rStrm << mnTotal << mnSize;
for (auto const& elem : maStringVector)
{
if( !nBucketIndex )
{
// write bucket info before string to get correct record position
sal_uInt32 nStrmPos = static_cast< sal_uInt32 >( rStrm.GetSvStreamPos() );
sal_uInt16 nRecPos = rStrm.GetRawRecPos() + 4;
aExtSst.WriteUInt32( nStrmPos ) // stream position
.WriteUInt16( nRecPos ) // position from start of SST or CONTINUE
.WriteUInt16( 0 ); // reserved
}
rStrm << *elem;
if( ++nBucketIndex == nPerBucket )
nBucketIndex = 0;
}
rStrm.EndRecord();
// *** write the EXTSST record ***
rStrm.StartRecord( EXC_ID_EXTSST, 0 );
rStrm << nPerBucket;
rStrm.SetSliceSize( 8 ); // size of one bucket info
aExtSst.Seek( STREAM_SEEK_TO_BEGIN );
rStrm.CopyFromStream( aExtSst );
rStrm.EndRecord();
}
void XclExpSstImpl::SaveXml( XclExpXmlStream& rStrm )
{
if( maStringVector.empty() )
return;
sax_fastparser::FSHelperPtr pSst = rStrm.CreateOutputStream(
u"xl/sharedStrings.xml"_ustr,
u"sharedStrings.xml",
rStrm.GetCurrentStream()->getOutputStream(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
oox::getRelationship(Relationship::SHAREDSTRINGS));
rStrm.PushStream( pSst );
pSst->startElement( XML_sst,
XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)),
XML_count, OString::number(mnTotal),
XML_uniqueCount, OString::number(mnSize) );
for (auto const& elem : maStringVector)
{
pSst->startElement(XML_si);
elem->WriteXml( rStrm );
pSst->endElement( XML_si );
}
pSst->endElement( XML_sst );
rStrm.PopStream();
}
XclExpSst::XclExpSst() :
mxImpl( new XclExpSstImpl )
{
}
XclExpSst::~XclExpSst()
{
}
sal_uInt32 XclExpSst::Insert( const XclExpStringRef& xString )
{
return mxImpl->Insert( xString );
}
void XclExpSst::Save( XclExpStream& rStrm )
{
mxImpl->Save( rStrm );
}
void XclExpSst::SaveXml( XclExpXmlStream& rStrm )
{
mxImpl->SaveXml( rStrm );
}
// Merged cells ===============================================================
XclExpMergedcells::XclExpMergedcells( const XclExpRoot& rRoot ) :
XclExpRoot( rRoot )
{
}
void XclExpMergedcells::AppendRange( const ScRange& rRange, sal_uInt32 nBaseXFId )
{
if( GetBiff() == EXC_BIFF8 )
{
maMergedRanges.push_back( rRange );
maBaseXFIds.push_back( nBaseXFId );
}
}
sal_uInt32 XclExpMergedcells::GetBaseXFId( const ScAddress& rPos ) const
{
OSL_ENSURE( maBaseXFIds.size() == maMergedRanges.size(), "XclExpMergedcells::GetBaseXFId - invalid lists" );
ScfUInt32Vec::const_iterator aIt = maBaseXFIds.begin();
ScRangeList& rNCRanges = const_cast< ScRangeList& >( maMergedRanges );
for ( size_t i = 0, nRanges = rNCRanges.size(); i < nRanges; ++i, ++aIt )
{
const ScRange & rScRange = rNCRanges[ i ];
if( rScRange.Contains( rPos ) )
return *aIt;
}
return EXC_XFID_NOTFOUND;
}
void XclExpMergedcells::Save( XclExpStream& rStrm )
{
if( GetBiff() != EXC_BIFF8 )
return;
XclRangeList aXclRanges;
GetAddressConverter().ConvertRangeList( aXclRanges, maMergedRanges, true );
size_t nFirstRange = 0;
size_t nRemainingRanges = aXclRanges.size();
while( nRemainingRanges > 0 )
{
size_t nRangeCount = ::std::min< size_t >( nRemainingRanges, EXC_MERGEDCELLS_MAXCOUNT );
rStrm.StartRecord( EXC_ID_MERGEDCELLS, 2 + 8 * nRangeCount );
aXclRanges.WriteSubList( rStrm, nFirstRange, nRangeCount );
rStrm.EndRecord();
nFirstRange += nRangeCount;
nRemainingRanges -= nRangeCount;
}
}
void XclExpMergedcells::SaveXml( XclExpXmlStream& rStrm )
{
size_t nCount = maMergedRanges.size();
if( !nCount )
return;
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement(XML_mergeCells, XML_count, OString::number(nCount));
for( size_t i = 0; i < nCount; ++i )
{
const ScRange & rRange = maMergedRanges[ i ];
rWorksheet->singleElement(XML_mergeCell, XML_ref,
XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), rRange));
}
rWorksheet->endElement( XML_mergeCells );
}
// Hyperlinks =================================================================
XclExpHyperlink::XclExpHyperlink( const XclExpRoot& rRoot, const SvxURLField& rUrlField, const ScAddress& rScPos ) :
XclExpRecord( EXC_ID_HLINK ),
maScPos( rScPos ),
mxVarData( new SvMemoryStream ),
mnFlags( 0 )
{
const OUString& rUrl = rUrlField.GetURL();
const OUString& rRepr = rUrlField.GetRepresentation();
INetURLObject aUrlObj( rUrl );
const INetProtocol eProtocol = aUrlObj.GetProtocol();
bool bWithRepr = !rRepr.isEmpty();
XclExpStream aXclStrm( *mxVarData, rRoot ); // using in raw write mode.
// description
if( bWithRepr )
{
XclExpString aDescr( rRepr, XclStrFlags::ForceUnicode, 255 );
aXclStrm << sal_uInt32( aDescr.Len() + 1 ); // string length + 1 trailing zero word
aDescr.WriteBuffer( aXclStrm ); // NO flags
aXclStrm << sal_uInt16( 0 );
mnFlags |= EXC_HLINK_DESCR;
m_Repr = rRepr;
}
// file link or URL
if( eProtocol == INetProtocol::File || eProtocol == INetProtocol::Smb )
{
sal_uInt16 nLevel;
bool bRel;
OUString aFileName(
BuildFileName(nLevel, bRel, rUrl, rRoot, rRoot.GetOutput() == EXC_OUTPUT_XML_2007));
if( eProtocol == INetProtocol::Smb )
{
// #n382718# (and #n261623#) Convert smb notation to '\\'
aFileName = aUrlObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
aFileName = aFileName.copy(4); // skip the 'smb:' part
aFileName = aFileName.replace('/', '\\');
}
if( !bRel )
mnFlags |= EXC_HLINK_ABS;
mnFlags |= EXC_HLINK_BODY;
OString aAsciiLink(OUStringToOString(aFileName,
rRoot.GetTextEncoding()));
XclExpString aLink( aFileName, XclStrFlags::ForceUnicode, 255 );
aXclStrm << XclTools::maGuidFileMoniker
<< nLevel
<< sal_uInt32( aAsciiLink.getLength() + 1 ); // string length + 1 trailing zero byte
aXclStrm.Write( aAsciiLink.getStr(), aAsciiLink.getLength() );
aXclStrm << sal_uInt8( 0 )
<< sal_uInt32( 0xDEADFFFF );
aXclStrm.WriteZeroBytes( 20 );
aXclStrm << sal_uInt32( aLink.GetBufferSize() + 6 )
<< sal_uInt32( aLink.GetBufferSize() ) // byte count, not string length
<< sal_uInt16( 0x0003 );
aLink.WriteBuffer( aXclStrm ); // NO flags
if (m_Repr.isEmpty())
m_Repr = aFileName;
msTarget = XclXmlUtils::ToOUString( aLink );
if( bRel )
{
for( int i = 0; i < nLevel; ++i )
msTarget = "../" + msTarget;
}
else if (rRoot.GetOutput() != EXC_OUTPUT_XML_2007)
{
// xls expects the file:/// part appended ( or at least
// ms2007 does, ms2010 is more tolerant )
msTarget = "file:///" + msTarget;
}
}
else if( eProtocol != INetProtocol::NotValid )
{
XclExpString aUrl( aUrlObj.GetURLNoMark(), XclStrFlags::ForceUnicode, 255 );
aXclStrm << XclTools::maGuidUrlMoniker
<< sal_uInt32( aUrl.GetBufferSize() + 2 ); // byte count + 1 trailing zero word
aUrl.WriteBuffer( aXclStrm ); // NO flags
aXclStrm << sal_uInt16( 0 );
mnFlags |= EXC_HLINK_BODY | EXC_HLINK_ABS;
if (m_Repr.isEmpty())
m_Repr = rUrl;
msTarget = XclXmlUtils::ToOUString( aUrl );
}
else if( !rUrl.isEmpty() && rUrl[0] == '#' ) // hack for #89066#
{
OUString aTextMark( rUrl.copy( 1 ) );
sal_Int32 nSepPos = aTextMark.lastIndexOf( '!' );
sal_Int32 nPointPos = aTextMark.lastIndexOf( '.' );
// last dot is the separator, if there is no ! after it
if(nSepPos < nPointPos)
{
nSepPos = nPointPos;
aTextMark = aTextMark.replaceAt( nSepPos, 1, u"!" );
}
if (nSepPos != -1)
{
std::u16string_view aSheetName(aTextMark.subView(0, nSepPos));
if (aSheetName.find(' ') != std::u16string_view::npos && aSheetName[0] != '\'')
{
aTextMark = "'" + aTextMark.replaceAt(nSepPos, 0, u"'");
}
}
else
{
SCTAB nTab;
if (rRoot.GetDoc().GetTable(aTextMark, nTab))
aTextMark += "!A1"; // tdf#143220 link to sheet not valid without cell reference
}
mxTextMark.reset( new XclExpString( aTextMark, XclStrFlags::ForceUnicode, 255 ) );
}
// text mark
if( !mxTextMark && aUrlObj.HasMark() )
mxTextMark.reset( new XclExpString( aUrlObj.GetMark(), XclStrFlags::ForceUnicode, 255 ) );
if( mxTextMark )
{
aXclStrm << sal_uInt32( mxTextMark->Len() + 1 ); // string length + 1 trailing zero word
mxTextMark->WriteBuffer( aXclStrm ); // NO flags
aXclStrm << sal_uInt16( 0 );
mnFlags |= EXC_HLINK_MARK;
OUString location = XclXmlUtils::ToOUString(*mxTextMark);
if (!location.isEmpty() && msTarget.endsWith(Concat2View("#" + location)))
msTarget = msTarget.copy(0, msTarget.getLength() - location.getLength() - 1);
}
SetRecSize( 32 + mxVarData->Tell() );
}
XclExpHyperlink::~XclExpHyperlink()
{
}
OUString XclExpHyperlink::BuildFileName(
sal_uInt16& rnLevel, bool& rbRel, const OUString& rUrl, const XclExpRoot& rRoot, bool bEncoded )
{
INetURLObject aURLObject( rUrl );
OUString aDosName(bEncoded ? aURLObject.GetMainURL(INetURLObject::DecodeMechanism::ToIUri)
: aURLObject.getFSysPath(FSysStyle::Dos));
rnLevel = 0;
rbRel = rRoot.IsRelUrl();
if( rbRel )
{
// try to convert to relative file name
OUString aTmpName( aDosName );
aDosName = INetURLObject::GetRelURL( rRoot.GetBasePath(), rUrl,
INetURLObject::EncodeMechanism::WasEncoded,
(bEncoded ? INetURLObject::DecodeMechanism::ToIUri : INetURLObject::DecodeMechanism::WithCharset));
if (aDosName.startsWith(INET_FILE_SCHEME))
{
// not converted to rel -> back to old, return absolute flag
aDosName = aTmpName;
rbRel = false;
}
else if (aDosName.startsWith("./"))
{
aDosName = aDosName.copy(2);
}
else
{
while (aDosName.startsWith("../"))
{
aDosName = aDosName.copy(3);
++rnLevel;
}
}
}
return aDosName;
}
void XclExpHyperlink::WriteBody( XclExpStream& rStrm )
{
sal_uInt16 nXclCol = static_cast< sal_uInt16 >( maScPos.Col() );
sal_uInt16 nXclRow = static_cast< sal_uInt16 >( maScPos.Row() );
rStrm << nXclRow << nXclRow << nXclCol << nXclCol;
WriteEmbeddedData( rStrm );
}
void XclExpHyperlink::WriteEmbeddedData( XclExpStream& rStrm )
{
rStrm << XclTools::maGuidStdLink
<< sal_uInt32( 2 )
<< mnFlags;
mxVarData->Seek( STREAM_SEEK_TO_BEGIN );
rStrm.CopyFromStream( *mxVarData );
}
void XclExpHyperlink::SaveXml( XclExpXmlStream& rStrm )
{
OUString sId = !msTarget.isEmpty() ? rStrm.addRelation( rStrm.GetCurrentStream()->getOutputStream(),
oox::getRelationship(Relationship::HYPERLINK),
msTarget, true ) : OUString();
std::optional<OString> sTextMark;
if (mxTextMark)
sTextMark = XclXmlUtils::ToOString(*mxTextMark);
rStrm.GetCurrentStream()->singleElement( XML_hyperlink,
XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), ScRange(maScPos)),
FSNS( XML_r, XML_id ), sax_fastparser::UseIf(sId, !sId.isEmpty()),
XML_location, sTextMark,
// OOXTODO: XML_tooltip, from record HLinkTooltip 800h wzTooltip
XML_display, m_Repr );
}
// Label ranges ===============================================================
XclExpLabelranges::XclExpLabelranges( const XclExpRoot& rRoot ) :
XclExpRoot( rRoot )
{
SCTAB nScTab = GetCurrScTab();
// row label ranges
FillRangeList( maRowRanges, rRoot.GetDoc().GetRowNameRangesRef(), nScTab );
// row labels only over 1 column (restriction of Excel97/2000/XP)
for ( size_t i = 0, nRanges = maRowRanges.size(); i < nRanges; ++i )
{
ScRange & rScRange = maRowRanges[ i ];
if( rScRange.aStart.Col() != rScRange.aEnd.Col() )
rScRange.aEnd.SetCol( rScRange.aStart.Col() );
}
// col label ranges
FillRangeList( maColRanges, rRoot.GetDoc().GetColNameRangesRef(), nScTab );
}
void XclExpLabelranges::FillRangeList( ScRangeList& rScRanges,
const ScRangePairListRef& xLabelRangesRef, SCTAB nScTab )
{
for ( size_t i = 0, nPairs = xLabelRangesRef->size(); i < nPairs; ++i )
{
const ScRangePair & rRangePair = (*xLabelRangesRef)[i];
const ScRange& rScRange = rRangePair.GetRange( 0 );
if( rScRange.aStart.Tab() == nScTab )
rScRanges.push_back( rScRange );
}
}
void XclExpLabelranges::Save( XclExpStream& rStrm )
{
XclExpAddressConverter& rAddrConv = GetAddressConverter();
XclRangeList aRowXclRanges, aColXclRanges;
rAddrConv.ConvertRangeList( aRowXclRanges, maRowRanges, false );
rAddrConv.ConvertRangeList( aColXclRanges, maColRanges, false );
if( !aRowXclRanges.empty() || !aColXclRanges.empty() )
{
rStrm.StartRecord( EXC_ID_LABELRANGES, 4 + 8 * (aRowXclRanges.size() + aColXclRanges.size()) );
rStrm << aRowXclRanges << aColXclRanges;
rStrm.EndRecord();
}
}
// Conditional formatting ====================================================
/** Represents a CF record that contains one condition of a conditional format. */
class XclExpCFImpl : protected XclExpRoot
{
public:
explicit XclExpCFImpl( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin );
/** Writes the body of the CF record. */
void WriteBody( XclExpStream& rStrm );
void SaveXml( XclExpXmlStream& rStrm );
private:
const ScCondFormatEntry& mrFormatEntry; /// Calc conditional format entry.
ScAddress maOrigin; /// Top left cell of the combined range
XclFontData maFontData; /// Font formatting attributes.
XclExpCellBorder maBorder; /// Border formatting attributes.
XclExpCellArea maArea; /// Pattern formatting attributes.
XclTokenArrayRef mxTokArr1; /// Formula for first condition.
XclTokenArrayRef mxTokArr2; /// Formula for second condition.
sal_uInt32 mnFontColorId; /// Font color ID.
sal_uInt8 mnType; /// Type of the condition (cell/formula).
sal_uInt8 mnOperator; /// Comparison operator for cell type.
sal_Int32 mnPriority; /// Priority of this entry; needed for oox export
bool mbFontUsed; /// true = Any font attribute used.
bool mbHeightUsed; /// true = Font height used.
bool mbWeightUsed; /// true = Font weight used.
bool mbColorUsed; /// true = Font color used.
bool mbUnderlUsed; /// true = Font underline type used.
bool mbItalicUsed; /// true = Font posture used.
bool mbStrikeUsed; /// true = Font strikeout used.
bool mbBorderUsed; /// true = Border attribute used.
bool mbPattUsed; /// true = Pattern attribute used.
bool mbFormula2;
};
XclExpCFImpl::XclExpCFImpl( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ) :
XclExpRoot( rRoot ),
mrFormatEntry( rFormatEntry ),
maOrigin( aOrigin ),
mnFontColorId( 0 ),
mnType( EXC_CF_TYPE_CELL ),
mnOperator( EXC_CF_CMP_NONE ),
mnPriority( nPriority ),
mbFontUsed( false ),
mbHeightUsed( false ),
mbWeightUsed( false ),
mbColorUsed( false ),
mbUnderlUsed( false ),
mbItalicUsed( false ),
mbStrikeUsed( false ),
mbBorderUsed( false ),
mbPattUsed( false ),
mbFormula2(false)
{
// Set correct tab for maOrigin from GetValidSrcPos() of the format-entry.
ScAddress aValidSrcPos = mrFormatEntry.GetValidSrcPos();
maOrigin.SetTab(aValidSrcPos.Tab());
/* Get formatting attributes here, and not in WriteBody(). This is needed to
correctly insert all colors into the palette. */
if( SfxStyleSheetBase* pStyleSheet = GetDoc().GetStyleSheetPool()->Find( mrFormatEntry.GetStyle(), SfxStyleFamily::Para ) )
{
const SfxItemSet& rItemSet = pStyleSheet->GetItemSet();
// font
mbHeightUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_HEIGHT, true );
mbWeightUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_WEIGHT, true );
mbColorUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_COLOR, true );
mbUnderlUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_UNDERLINE, true );
mbItalicUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_POSTURE, true );
mbStrikeUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_CROSSEDOUT, true );
mbFontUsed = mbHeightUsed || mbWeightUsed || mbColorUsed || mbUnderlUsed || mbItalicUsed || mbStrikeUsed;
if( mbFontUsed )
{
vcl::Font aFont;
model::ComplexColor aComplexColor;
ScPatternAttr::fillFontOnly(aFont, rItemSet);
ScPatternAttr::fillColor(aComplexColor, rItemSet, ScAutoFontColorMode::Raw);
maFontData.FillFromVclFont(aFont, aComplexColor);
mnFontColorId = GetPalette().InsertColor(maFontData.maComplexColor.getFinalColor(), EXC_COLOR_CELLTEXT);
}
// border
mbBorderUsed = ScfTools::CheckItem( rItemSet, ATTR_BORDER, true );
if( mbBorderUsed )
maBorder.FillFromItemSet( rItemSet, GetPalette(), GetBiff() );
// pattern
mbPattUsed = ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, true );
if( mbPattUsed )
maArea.FillFromItemSet( rItemSet, GetPalette(), true );
}
// *** mode and comparison operator ***
switch( rFormatEntry.GetOperation() )
{
case ScConditionMode::NONE:
mnType = EXC_CF_TYPE_NONE;
break;
case ScConditionMode::Between:
mnOperator = EXC_CF_CMP_BETWEEN;
mbFormula2 = true;
break;
case ScConditionMode::NotBetween:
mnOperator = EXC_CF_CMP_NOT_BETWEEN;
mbFormula2 = true;
break;
case ScConditionMode::Equal:
mnOperator = EXC_CF_CMP_EQUAL;
break;
case ScConditionMode::NotEqual:
mnOperator = EXC_CF_CMP_NOT_EQUAL;
break;
case ScConditionMode::Greater:
mnOperator = EXC_CF_CMP_GREATER;
break;
case ScConditionMode::Less:
mnOperator = EXC_CF_CMP_LESS;
break;
case ScConditionMode::EqGreater:
mnOperator = EXC_CF_CMP_GREATER_EQUAL;
break;
case ScConditionMode::EqLess:
mnOperator = EXC_CF_CMP_LESS_EQUAL;
break;
case ScConditionMode::Direct:
mnType = EXC_CF_TYPE_FMLA;
break;
default:
mnType = EXC_CF_TYPE_NONE;
OSL_FAIL( "XclExpCF::WriteBody - unknown condition type" );
}
}
void XclExpCFImpl::WriteBody( XclExpStream& rStrm )
{
// *** formulas ***
XclExpFormulaCompiler& rFmlaComp = GetFormulaCompiler();
std::unique_ptr< ScTokenArray > xScTokArr( mrFormatEntry.CreateFlatCopiedTokenArray( 0 ) );
mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_CONDFMT, *xScTokArr );
if (mbFormula2)
{
xScTokArr = mrFormatEntry.CreateFlatCopiedTokenArray( 1 );
mxTokArr2 = rFmlaComp.CreateFormula( EXC_FMLATYPE_CONDFMT, *xScTokArr );
}
// *** mode and comparison operator ***
rStrm << mnType << mnOperator;
// *** formula sizes ***
sal_uInt16 nFmlaSize1 = mxTokArr1 ? mxTokArr1->GetSize() : 0;
sal_uInt16 nFmlaSize2 = mxTokArr2 ? mxTokArr2->GetSize() : 0;
rStrm << nFmlaSize1 << nFmlaSize2;
// *** formatting blocks ***
if( mbFontUsed || mbBorderUsed || mbPattUsed )
{
sal_uInt32 nFlags = EXC_CF_ALLDEFAULT;
::set_flag( nFlags, EXC_CF_BLOCK_FONT, mbFontUsed );
::set_flag( nFlags, EXC_CF_BLOCK_BORDER, mbBorderUsed );
::set_flag( nFlags, EXC_CF_BLOCK_AREA, mbPattUsed );
// attributes used -> set flags to 0.
::set_flag( nFlags, EXC_CF_BORDER_ALL, !mbBorderUsed );
::set_flag( nFlags, EXC_CF_AREA_ALL, !mbPattUsed );
rStrm << nFlags << sal_uInt16( 0 );
if( mbFontUsed )
{
// font height, 0xFFFFFFFF indicates unused
sal_uInt32 nHeight = mbHeightUsed ? maFontData.mnHeight : 0xFFFFFFFF;
// font style: italic and strikeout
sal_uInt32 nStyle = 0;
::set_flag( nStyle, EXC_CF_FONT_STYLE, maFontData.mbItalic );
::set_flag( nStyle, EXC_CF_FONT_STRIKEOUT, maFontData.mbStrikeout );
// font color, 0xFFFFFFFF indicates unused
sal_uInt32 nColor = mbColorUsed ? GetPalette().GetColorIndex( mnFontColorId ) : 0xFFFFFFFF;
// font used flags for italic, weight, and strikeout -> 0 = used, 1 = default
sal_uInt32 nFontFlags1 = EXC_CF_FONT_ALLDEFAULT;
::set_flag( nFontFlags1, EXC_CF_FONT_STYLE, !(mbItalicUsed || mbWeightUsed) );
::set_flag( nFontFlags1, EXC_CF_FONT_STRIKEOUT, !mbStrikeUsed );
// font used flag for underline -> 0 = used, 1 = default
sal_uInt32 nFontFlags3 = mbUnderlUsed ? 0 : EXC_CF_FONT_UNDERL;
rStrm.WriteZeroBytesToRecord( 64 );
rStrm << nHeight
<< nStyle
<< maFontData.mnWeight
<< EXC_FONTESC_NONE
<< maFontData.mnUnderline;
rStrm.WriteZeroBytesToRecord( 3 );
rStrm << nColor
<< sal_uInt32( 0 )
<< nFontFlags1
<< EXC_CF_FONT_ESCAPEM // escapement never used -> set the flag
<< nFontFlags3;
rStrm.WriteZeroBytesToRecord( 16 );
rStrm << sal_uInt16( 1 ); // must be 1
}
if( mbBorderUsed )
{
sal_uInt16 nLineStyle = 0;
sal_uInt32 nLineColor = 0;
maBorder.SetFinalColors( GetPalette() );
maBorder.FillToCF8( nLineStyle, nLineColor );
rStrm << nLineStyle << nLineColor << sal_uInt16( 0 );
}
if( mbPattUsed )
{
sal_uInt16 nPattern = 0, nColor = 0;
maArea.SetFinalColors( GetPalette() );
maArea.FillToCF8( nPattern, nColor );
rStrm << nPattern << nColor;
}
}
else
{
// no data blocks at all
rStrm << sal_uInt32( 0 ) << sal_uInt16( 0 );
}
// *** formulas ***
if( mxTokArr1 )
mxTokArr1->WriteArray( rStrm );
if( mxTokArr2 )
mxTokArr2->WriteArray( rStrm );
}
namespace {
const char* GetOperatorString(ScConditionMode eMode, bool& bFrmla2)
{
const char *pRet = nullptr;
switch(eMode)
{
case ScConditionMode::Equal:
pRet = "equal";
break;
case ScConditionMode::Less:
pRet = "lessThan";
break;
case ScConditionMode::Greater:
pRet = "greaterThan";
break;
case ScConditionMode::EqLess:
pRet = "lessThanOrEqual";
break;
case ScConditionMode::EqGreater:
pRet = "greaterThanOrEqual";
break;
case ScConditionMode::NotEqual:
pRet = "notEqual";
break;
case ScConditionMode::Between:
bFrmla2 = true;
pRet = "between";
break;
case ScConditionMode::NotBetween:
bFrmla2 = true;
pRet = "notBetween";
break;
case ScConditionMode::Duplicate:
case ScConditionMode::NotDuplicate:
pRet = nullptr;
break;
case ScConditionMode::BeginsWith:
pRet = "beginsWith";
break;
case ScConditionMode::EndsWith:
pRet = "endsWith";
break;
case ScConditionMode::ContainsText:
pRet = "containsText";
break;
case ScConditionMode::NotContainsText:
pRet = "notContains";
break;
case ScConditionMode::Direct:
break;
case ScConditionMode::NONE:
default:
break;
}
return pRet;
}
const char* GetTypeString(ScConditionMode eMode)
{
switch(eMode)
{
case ScConditionMode::Direct:
return "expression";
case ScConditionMode::Top10:
case ScConditionMode::TopPercent:
case ScConditionMode::Bottom10:
case ScConditionMode::BottomPercent:
return "top10";
case ScConditionMode::AboveAverage:
case ScConditionMode::BelowAverage:
case ScConditionMode::AboveEqualAverage:
case ScConditionMode::BelowEqualAverage:
return "aboveAverage";
case ScConditionMode::NotDuplicate:
return "uniqueValues";
case ScConditionMode::Duplicate:
return "duplicateValues";
case ScConditionMode::Error:
return "containsErrors";
case ScConditionMode::NoError:
return "notContainsErrors";
case ScConditionMode::BeginsWith:
return "beginsWith";
case ScConditionMode::EndsWith:
return "endsWith";
case ScConditionMode::ContainsText:
return "containsText";
case ScConditionMode::NotContainsText:
return "notContainsText";
default:
return "cellIs";
}
}
bool IsTopBottomRule(ScConditionMode eMode)
{
switch(eMode)
{
case ScConditionMode::Top10:
case ScConditionMode::Bottom10:
case ScConditionMode::TopPercent:
case ScConditionMode::BottomPercent:
return true;
default:
break;
}
return false;
}
bool IsTextRule(ScConditionMode eMode)
{
switch(eMode)
{
case ScConditionMode::BeginsWith:
case ScConditionMode::EndsWith:
case ScConditionMode::ContainsText:
case ScConditionMode::NotContainsText:
return true;
default:
break;
}
return false;
}
bool RequiresFormula(ScConditionMode eMode)
{
if (IsTopBottomRule(eMode))
return false;
else if (IsTextRule(eMode))
return false;
switch (eMode)
{
case ScConditionMode::NoError:
case ScConditionMode::Error:
case ScConditionMode::Duplicate:
case ScConditionMode::NotDuplicate:
return false;
default:
break;
}
return true;
}
bool RequiresFixedFormula(ScConditionMode eMode)
{
switch(eMode)
{
case ScConditionMode::NoError:
case ScConditionMode::Error:
case ScConditionMode::BeginsWith:
case ScConditionMode::EndsWith:
case ScConditionMode::ContainsText:
case ScConditionMode::NotContainsText:
return true;
default:
break;
}
return false;
}
OString GetFixedFormula(ScConditionMode eMode, const ScAddress& rAddress, std::string_view rText)
{
OStringBuffer aBuffer;
XclXmlUtils::ToOString(aBuffer, rAddress);
OString aPos = aBuffer.makeStringAndClear();
switch (eMode)
{
case ScConditionMode::Error:
return OString("ISERROR(" + aPos + ")") ;
case ScConditionMode::NoError:
return OString("NOT(ISERROR(" + aPos + "))") ;
case ScConditionMode::BeginsWith:
return OString("LEFT(" + aPos + ",LEN(\"" + rText + "\"))=\"" + rText + "\"");
case ScConditionMode::EndsWith:
return OString("RIGHT(" + aPos +",LEN(\"" + rText + "\"))=\"" + rText + "\"");
case ScConditionMode::ContainsText:
return OString(OString::Concat("NOT(ISERROR(SEARCH(\"") + rText + "\"," + aPos + ")))");
case ScConditionMode::NotContainsText:
return OString(OString::Concat("ISERROR(SEARCH(\"") + rText + "\"," + aPos + "))");
default:
break;
}
return ""_ostr;
}
}
void XclExpCFImpl::SaveXml( XclExpXmlStream& rStrm )
{
bool bFmla2 = false;
ScConditionMode eOperation = mrFormatEntry.GetOperation();
bool bAboveAverage = eOperation == ScConditionMode::AboveAverage ||
eOperation == ScConditionMode::AboveEqualAverage;
bool bEqualAverage = eOperation == ScConditionMode::AboveEqualAverage ||
eOperation == ScConditionMode::BelowEqualAverage;
bool bBottom = eOperation == ScConditionMode::Bottom10
|| eOperation == ScConditionMode::BottomPercent;
bool bPercent = eOperation == ScConditionMode::TopPercent ||
eOperation == ScConditionMode::BottomPercent;
OUString aRank(u"0"_ustr);
if(IsTopBottomRule(eOperation))
{
// position and formula grammar are not important
// we only store a number there
aRank = mrFormatEntry.GetExpression(ScAddress(0,0,0), 0);
}
OString aText;
if(IsTextRule(eOperation))
{
// we need to write the text without quotes
// we have to actually get the string from
// the token array for that
std::unique_ptr<ScTokenArray> pTokenArray(mrFormatEntry.CreateFlatCopiedTokenArray(0));
if(pTokenArray->GetLen())
{
formula::StackVar eType = pTokenArray->FirstToken()->GetType();
switch (eType)
{
case formula::svDouble:
{
aText = OString::number(pTokenArray->FirstToken()->GetDouble());
break;
}
default:
{
aText = pTokenArray->FirstToken()->GetString().getString().toUtf8();
break;
}
}
}
}
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_cfRule,
XML_type, GetTypeString( mrFormatEntry.GetOperation() ),
XML_priority, OString::number(mnPriority + 1),
XML_operator, GetOperatorString( mrFormatEntry.GetOperation(), bFmla2 ),
XML_aboveAverage, ToPsz10(bAboveAverage),
XML_equalAverage, ToPsz10(bEqualAverage),
XML_bottom, ToPsz10(bBottom),
XML_percent, ToPsz10(bPercent),
XML_rank, aRank,
XML_text, aText,
XML_dxfId, OString::number(GetDxfs().GetDxfId(mrFormatEntry.GetStyle())) );
if (RequiresFixedFormula(eOperation))
{
rWorksheet->startElement(XML_formula);
OString aFormula = GetFixedFormula(eOperation, maOrigin, aText);
rWorksheet->writeEscaped(aFormula.getStr());
rWorksheet->endElement( XML_formula );
}
else if(RequiresFormula(eOperation))
{
rWorksheet->startElement(XML_formula);
std::unique_ptr<ScTokenArray> pTokenArray(mrFormatEntry.CreateFlatCopiedTokenArray(0));
rWorksheet->writeEscaped(XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormatEntry.GetValidSrcPos(),
pTokenArray.get()));
rWorksheet->endElement( XML_formula );
if (bFmla2)
{
rWorksheet->startElement(XML_formula);
std::unique_ptr<ScTokenArray> pTokenArray2(mrFormatEntry.CreateFlatCopiedTokenArray(1));
rWorksheet->writeEscaped(XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormatEntry.GetValidSrcPos(),
pTokenArray2.get()));
rWorksheet->endElement( XML_formula );
}
}
// OOXTODO: XML_extLst
rWorksheet->endElement( XML_cfRule );
}
XclExpCF::XclExpCF( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ) :
XclExpRecord( EXC_ID_CF ),
XclExpRoot( rRoot ),
mxImpl( new XclExpCFImpl( rRoot, rFormatEntry, nPriority, aOrigin ) )
{
}
XclExpCF::~XclExpCF()
{
}
void XclExpCF::WriteBody( XclExpStream& rStrm )
{
mxImpl->WriteBody( rStrm );
}
void XclExpCF::SaveXml( XclExpXmlStream& rStrm )
{
mxImpl->SaveXml( rStrm );
}
XclExpDateFormat::XclExpDateFormat( const XclExpRoot& rRoot, const ScCondDateFormatEntry& rFormatEntry, sal_Int32 nPriority ):
XclExpRecord( EXC_ID_CF ),
XclExpRoot( rRoot ),
mrFormatEntry(rFormatEntry),
mnPriority(nPriority)
{
}
XclExpDateFormat::~XclExpDateFormat()
{
}
namespace {
const char* getTimePeriodString( condformat::ScCondFormatDateType eType )
{
switch(eType)
{
case condformat::TODAY:
return "today";
case condformat::YESTERDAY:
return "yesterday";
case condformat::TOMORROW:
return "yesterday";
case condformat::THISWEEK:
return "thisWeek";
case condformat::LASTWEEK:
return "lastWeek";
case condformat::NEXTWEEK:
return "nextWeek";
case condformat::THISMONTH:
return "thisMonth";
case condformat::LASTMONTH:
return "lastMonth";
case condformat::NEXTMONTH:
return "nextMonth";
case condformat::LAST7DAYS:
return "last7Days";
default:
break;
}
return nullptr;
}
}
void XclExpDateFormat::SaveXml( XclExpXmlStream& rStrm )
{
// only write the supported entries into OOXML
const char* sTimePeriod = getTimePeriodString(mrFormatEntry.GetDateType());
if(!sTimePeriod)
return;
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_cfRule,
XML_type, "timePeriod",
XML_priority, OString::number(mnPriority + 1),
XML_timePeriod, sTimePeriod,
XML_dxfId, OString::number(GetDxfs().GetDxfId(mrFormatEntry.GetStyleName())) );
rWorksheet->endElement( XML_cfRule);
}
XclExpCfvo::XclExpCfvo(const XclExpRoot& rRoot, const ScColorScaleEntry& rEntry, const ScAddress& rAddr, bool bFirst):
XclExpRoot( rRoot ),
mrEntry(rEntry),
maSrcPos(rAddr),
mbFirst(bFirst)
{
}
namespace {
OString getColorScaleType( const ScColorScaleEntry& rEntry, bool bFirst )
{
switch(rEntry.GetType())
{
case COLORSCALE_MIN:
return "min"_ostr;
case COLORSCALE_MAX:
return "max"_ostr;
case COLORSCALE_PERCENT:
return "percent"_ostr;
case COLORSCALE_FORMULA:
return "formula"_ostr;
case COLORSCALE_AUTO:
if(bFirst)
return "min"_ostr;
else
return "max"_ostr;
case COLORSCALE_PERCENTILE:
return "percentile"_ostr;
default:
break;
}
return "num"_ostr;
}
}
void XclExpCfvo::SaveXml( XclExpXmlStream& rStrm )
{
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
OString aValue;
if(mrEntry.GetType() == COLORSCALE_FORMULA)
{
OUString aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), maSrcPos,
mrEntry.GetFormula());
aValue = OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8 );
}
else
{
aValue = OString::number( mrEntry.GetValue() );
}
rWorksheet->startElement( XML_cfvo,
XML_type, getColorScaleType(mrEntry, mbFirst),
XML_val, aValue,
XML_gte, sax_fastparser::UseIf("0", !mrEntry.GetGreaterThanOrEqual()));
rWorksheet->endElement( XML_cfvo );
}
XclExpColScaleCol::XclExpColScaleCol( const XclExpRoot& rRoot, const Color& rColor ):
XclExpRoot( rRoot ),
mrColor( rColor )
{
}
XclExpColScaleCol::~XclExpColScaleCol()
{
}
void XclExpColScaleCol::SaveXml( XclExpXmlStream& rStrm )
{
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement(XML_color, XML_rgb, XclXmlUtils::ToOString(mrColor));
rWorksheet->endElement( XML_color );
}
namespace {
OString createHexStringFromDigit(sal_uInt8 nDigit)
{
OString aString = OString::number( nDigit, 16 );
if(aString.getLength() == 1)
aString += OString::number(0);
return aString;
}
OString createGuidStringFromInt(sal_uInt8 nGuid[16])
{
OStringBuffer aBuffer("{");
for(size_t i = 0; i < 16; ++i)
{
aBuffer.append(createHexStringFromDigit(nGuid[i]));
if(i == 3|| i == 5 || i == 7 || i == 9 )
aBuffer.append('-');
}
aBuffer.append('}');
OString aString = aBuffer.makeStringAndClear();
return aString.toAsciiUpperCase();
}
OString generateGUIDString()
{
sal_uInt8 nGuid[16];
rtl_createUuid(nGuid, nullptr, true);
return createGuidStringFromInt(nGuid);
}
}
XclExpCondfmt::XclExpCondfmt( const XclExpRoot& rRoot, const ScConditionalFormat& rCondFormat, const XclExtLstRef& xExtLst, sal_Int32& rIndex ) :
XclExpRecord( EXC_ID_CONDFMT ),
XclExpRoot( rRoot )
{
const ScRangeList& aScRanges = rCondFormat.GetRange();
GetAddressConverter().ConvertRangeList( maXclRanges, aScRanges, true );
if( maXclRanges.empty() )
return;
std::vector<XclExpExtCondFormatData> aExtEntries;
ScAddress aOrigin = aScRanges.Combine().aStart;
for( size_t nIndex = 0, nCount = rCondFormat.size(); nIndex < nCount; ++nIndex )
if( const ScFormatEntry* pFormatEntry = rCondFormat.GetEntry( nIndex ) )
{
if(pFormatEntry->GetType() == ScFormatEntry::Type::Condition)
maCFList.AppendNewRecord( new XclExpCF( GetRoot(), static_cast<const ScCondFormatEntry&>(*pFormatEntry), ++rIndex, aOrigin ) );
else if(pFormatEntry->GetType() == ScFormatEntry::Type::ExtCondition)
{
const ScCondFormatEntry& rFormat = static_cast<const ScCondFormatEntry&>(*pFormatEntry);
XclExpExtCondFormatData aExtEntry;
aExtEntry.nPriority = ++rIndex;
aExtEntry.aGUID = generateGUIDString();
aExtEntry.pEntry = &rFormat;
aExtEntries.push_back(aExtEntry);
}
else if(pFormatEntry->GetType() == ScFormatEntry::Type::Colorscale)
maCFList.AppendNewRecord( new XclExpColorScale( GetRoot(), static_cast<const ScColorScaleFormat&>(*pFormatEntry), ++rIndex ) );
else if(pFormatEntry->GetType() == ScFormatEntry::Type::Databar)
{
const ScDataBarFormat& rFormat = static_cast<const ScDataBarFormat&>(*pFormatEntry);
XclExpExtCondFormatData aExtEntry;
aExtEntry.nPriority = -1;
aExtEntry.aGUID = generateGUIDString();
aExtEntry.pEntry = &rFormat;
aExtEntries.push_back(aExtEntry);
maCFList.AppendNewRecord( new XclExpDataBar( GetRoot(), rFormat, ++rIndex, aExtEntry.aGUID));
}
else if(pFormatEntry->GetType() == ScFormatEntry::Type::Iconset)
{
// don't export iconSet entries that are not in OOXML
const ScIconSetFormat& rIconSet = static_cast<const ScIconSetFormat&>(*pFormatEntry);
bool bNeedsExt = false;
switch (rIconSet.GetIconSetData()->eIconSetType)
{
case IconSet_3Smilies:
case IconSet_3ColorSmilies:
case IconSet_3Stars:
case IconSet_3Triangles:
case IconSet_5Boxes:
{
bNeedsExt = true;
}
break;
default:
break;
}
bNeedsExt |= rIconSet.GetIconSetData()->mbCustom;
if (bNeedsExt)
{
XclExpExtCondFormatData aExtEntry;
aExtEntry.nPriority = ++rIndex;
aExtEntry.aGUID = generateGUIDString();
aExtEntry.pEntry = &rIconSet;
aExtEntries.push_back(aExtEntry);
}
else
maCFList.AppendNewRecord( new XclExpIconSet( GetRoot(), rIconSet, ++rIndex ) );
}
else if(pFormatEntry->GetType() == ScFormatEntry::Type::Date)
maCFList.AppendNewRecord( new XclExpDateFormat( GetRoot(), static_cast<const ScCondDateFormatEntry&>(*pFormatEntry), ++rIndex ) );
}
aScRanges.Format( msSeqRef, ScRefFlags::VALID, GetDoc(), formula::FormulaGrammar::CONV_XL_OOX, ' ', true );
if(!aExtEntries.empty() && xExtLst)
{
XclExpExt* pParent = xExtLst->GetItem( XclExpExtDataBarType );
if( !pParent )
{
xExtLst->AddRecord( new XclExpExtCondFormat( *xExtLst ) );
pParent = xExtLst->GetItem( XclExpExtDataBarType );
}
static_cast<XclExpExtCondFormat*>(xExtLst->GetItem( XclExpExtDataBarType ))->AddRecord(
new XclExpExtConditionalFormatting( *pParent, aExtEntries, aScRanges));
}
}
XclExpCondfmt::~XclExpCondfmt()
{
}
bool XclExpCondfmt::IsValidForBinary() const
{
// ccf (2 bytes): An unsigned integer that specifies the count of CF records that follow this
// record. MUST be greater than or equal to 0x0001, and less than or equal to 0x0003.
SAL_WARN_IF( maCFList.GetSize() > 3, "sc.filter", "More than 3 conditional filters for cell(s), won't export");
return !maCFList.IsEmpty() && maCFList.GetSize() <= 3 && !maXclRanges.empty();
}
bool XclExpCondfmt::IsValidForXml() const
{
return !maCFList.IsEmpty() && !maXclRanges.empty();
}
void XclExpCondfmt::Save( XclExpStream& rStrm )
{
if( IsValidForBinary() )
{
XclExpRecord::Save( rStrm );
maCFList.Save( rStrm );
}
}
void XclExpCondfmt::WriteBody( XclExpStream& rStrm )
{
OSL_ENSURE( !maCFList.IsEmpty(), "XclExpCondfmt::WriteBody - no CF records to write" );
OSL_ENSURE( !maXclRanges.empty(), "XclExpCondfmt::WriteBody - no cell ranges found" );
rStrm << static_cast< sal_uInt16 >( maCFList.GetSize() )
<< sal_uInt16( 1 )
<< maXclRanges.GetEnclosingRange()
<< maXclRanges;
}
void XclExpCondfmt::SaveXml( XclExpXmlStream& rStrm )
{
if( !IsValidForXml() )
return;
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_conditionalFormatting,
XML_sqref, msSeqRef
// OOXTODO: XML_pivot
);
maCFList.SaveXml( rStrm );
rWorksheet->endElement( XML_conditionalFormatting );
}
XclExpColorScale::XclExpColorScale( const XclExpRoot& rRoot, const ScColorScaleFormat& rFormat, sal_Int32 nPriority ):
XclExpRoot( rRoot ),
mnPriority( nPriority )
{
const ScRange & rRange = rFormat.GetRange().front();
ScAddress aAddr = rRange.aStart;
for(const auto& rxColorScaleEntry : rFormat)
{
// exact position is not important, we allow only absolute refs
XclExpCfvoList::RecordRefType xCfvo( new XclExpCfvo( GetRoot(), *rxColorScaleEntry, aAddr ) );
maCfvoList.AppendRecord( xCfvo );
XclExpColScaleColList::RecordRefType xClo( new XclExpColScaleCol( GetRoot(), rxColorScaleEntry->GetColor() ) );
maColList.AppendRecord( xClo );
}
}
void XclExpColorScale::SaveXml( XclExpXmlStream& rStrm )
{
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_cfRule,
XML_type, "colorScale",
XML_priority, OString::number(mnPriority + 1) );
rWorksheet->startElement(XML_colorScale);
maCfvoList.SaveXml(rStrm);
maColList.SaveXml(rStrm);
rWorksheet->endElement( XML_colorScale );
rWorksheet->endElement( XML_cfRule );
}
XclExpDataBar::XclExpDataBar( const XclExpRoot& rRoot, const ScDataBarFormat& rFormat, sal_Int32 nPriority, OString aGUID):
XclExpRoot( rRoot ),
mrFormat( rFormat ),
mnPriority( nPriority ),
maGUID(std::move(aGUID))
{
const ScRange & rRange = rFormat.GetRange().front();
ScAddress aAddr = rRange.aStart;
// exact position is not important, we allow only absolute refs
mpCfvoLowerLimit.reset(
new XclExpCfvo(GetRoot(), *mrFormat.GetDataBarData()->mpLowerLimit, aAddr, true));
mpCfvoUpperLimit.reset(
new XclExpCfvo(GetRoot(), *mrFormat.GetDataBarData()->mpUpperLimit, aAddr, false));
mpCol.reset( new XclExpColScaleCol( GetRoot(), mrFormat.GetDataBarData()->maPositiveColor ) );
}
void XclExpDataBar::SaveXml( XclExpXmlStream& rStrm )
{
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_cfRule,
XML_type, "dataBar",
XML_priority, OString::number(mnPriority + 1) );
rWorksheet->startElement( XML_dataBar,
XML_showValue, ToPsz10(!mrFormat.GetDataBarData()->mbOnlyBar),
XML_minLength, OString::number(sal_uInt32(mrFormat.GetDataBarData()->mnMinLength)),
XML_maxLength, OString::number(sal_uInt32(mrFormat.GetDataBarData()->mnMaxLength)) );
mpCfvoLowerLimit->SaveXml(rStrm);
mpCfvoUpperLimit->SaveXml(rStrm);
mpCol->SaveXml(rStrm);
rWorksheet->endElement( XML_dataBar );
// extLst entries for Excel 2010 and 2013
rWorksheet->startElement(XML_extLst);
rWorksheet->startElement(XML_ext,
FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)),
XML_uri, "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}");
rWorksheet->startElementNS( XML_x14, XML_id );
rWorksheet->write(maGUID);
rWorksheet->endElementNS( XML_x14, XML_id );
rWorksheet->endElement( XML_ext );
rWorksheet->endElement( XML_extLst );
rWorksheet->endElement( XML_cfRule );
}
XclExpIconSet::XclExpIconSet( const XclExpRoot& rRoot, const ScIconSetFormat& rFormat, sal_Int32 nPriority ):
XclExpRoot( rRoot ),
mrFormat( rFormat ),
mnPriority( nPriority )
{
const ScRange & rRange = rFormat.GetRange().front();
ScAddress aAddr = rRange.aStart;
for (auto const& itr : rFormat)
{
// exact position is not important, we allow only absolute refs
XclExpCfvoList::RecordRefType xCfvo( new XclExpCfvo( GetRoot(), *itr, aAddr ) );
maCfvoList.AppendRecord( xCfvo );
}
}
void XclExpIconSet::SaveXml( XclExpXmlStream& rStrm )
{
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_cfRule,
XML_type, "iconSet",
XML_priority, OString::number(mnPriority + 1) );
OUString aIconSetName = ScIconSetFormat::getIconSetName(mrFormat.GetIconSetData()->eIconSetType);
rWorksheet->startElement( XML_iconSet,
XML_iconSet, aIconSetName,
XML_showValue, sax_fastparser::UseIf("0", !mrFormat.GetIconSetData()->mbShowValue),
XML_reverse, sax_fastparser::UseIf("1", mrFormat.GetIconSetData()->mbReverse));
maCfvoList.SaveXml( rStrm );
rWorksheet->endElement( XML_iconSet );
rWorksheet->endElement( XML_cfRule );
}
XclExpCondFormatBuffer::XclExpCondFormatBuffer( const XclExpRoot& rRoot, const XclExtLstRef& xExtLst ) :
XclExpRoot( rRoot )
{
if( const ScConditionalFormatList* pCondFmtList = GetDoc().GetCondFormList(GetCurrScTab()) )
{
sal_Int32 nIndex = 0;
for( const auto& rxCondFmt : *pCondFmtList)
{
XclExpCondfmtList::RecordRefType xCondfmtRec( new XclExpCondfmt( GetRoot(), *rxCondFmt, xExtLst, nIndex ));
if( xCondfmtRec->IsValidForXml() )
maCondfmtList.AppendRecord( xCondfmtRec );
}
}
}
void XclExpCondFormatBuffer::Save( XclExpStream& rStrm )
{
maCondfmtList.Save( rStrm );
}
void XclExpCondFormatBuffer::SaveXml( XclExpXmlStream& rStrm )
{
maCondfmtList.SaveXml( rStrm );
}
// Validation =================================================================
namespace {
/** Writes a formula for the DV record. */
void lclWriteDvFormula( XclExpStream& rStrm, const XclTokenArray* pXclTokArr )
{
sal_uInt16 nFmlaSize = pXclTokArr ? pXclTokArr->GetSize() : 0;
rStrm << nFmlaSize << sal_uInt16( 0 );
if( pXclTokArr )
pXclTokArr->WriteArray( rStrm );
}
/** Writes a formula for the DV record, based on a single string. */
void lclWriteDvFormula( XclExpStream& rStrm, const XclExpString& rString )
{
// fake a formula with a single tStr token
rStrm << static_cast< sal_uInt16 >( rString.GetSize() + 1 )
<< sal_uInt16( 0 )
<< EXC_TOKID_STR
<< rString;
}
const char* lcl_GetValidationType( sal_uInt32 nFlags )
{
switch( nFlags & EXC_DV_MODE_MASK )
{
case EXC_DV_MODE_ANY: return "none";
case EXC_DV_MODE_WHOLE: return "whole";
case EXC_DV_MODE_DECIMAL: return "decimal";
case EXC_DV_MODE_LIST: return "list";
case EXC_DV_MODE_DATE: return "date";
case EXC_DV_MODE_TIME: return "time";
case EXC_DV_MODE_TEXTLEN: return "textLength";
case EXC_DV_MODE_CUSTOM: return "custom";
}
return nullptr;
}
const char* lcl_GetOperatorType( sal_uInt32 nFlags )
{
switch( nFlags & EXC_DV_COND_MASK )
{
case EXC_DV_COND_BETWEEN: return "between";
case EXC_DV_COND_NOTBETWEEN: return "notBetween";
case EXC_DV_COND_EQUAL: return "equal";
case EXC_DV_COND_NOTEQUAL: return "notEqual";
case EXC_DV_COND_GREATER: return "greaterThan";
case EXC_DV_COND_LESS: return "lessThan";
case EXC_DV_COND_EQGREATER: return "greaterThanOrEqual";
case EXC_DV_COND_EQLESS: return "lessThanOrEqual";
}
return nullptr;
}
const char* lcl_GetErrorType( sal_uInt32 nFlags )
{
switch (nFlags & EXC_DV_ERROR_MASK)
{
case EXC_DV_ERROR_STOP: return "stop";
case EXC_DV_ERROR_WARNING: return "warning";
case EXC_DV_ERROR_INFO: return "information";
}
return nullptr;
}
void lcl_SetValidationText(const OUString& rText, XclExpString& rValidationText)
{
if ( !rText.isEmpty() )
{
// maximum length allowed in Excel is 255 characters
if ( rText.getLength() > 255 )
{
OUStringBuffer aBuf( rText );
rValidationText.Assign(
comphelper::string::truncateToLength(aBuf, 255).makeStringAndClear() );
}
else
rValidationText.Assign( rText );
}
else
rValidationText.Assign( '\0' );
}
} // namespace
XclExpDV::XclExpDV( const XclExpRoot& rRoot, sal_uInt32 nScHandle ) :
XclExpRecord( EXC_ID_DV ),
XclExpRoot( rRoot ),
mnFlags( 0 ),
mnScHandle( nScHandle )
{
if( const ScValidationData* pValData = GetDoc().GetValidationEntry( mnScHandle ) )
{
// prompt box - empty string represented by single NUL character
OUString aTitle, aText;
bool bShowPrompt = pValData->GetInput( aTitle, aText );
lcl_SetValidationText(aTitle, maPromptTitle);
lcl_SetValidationText(aText, maPromptText);
// error box - empty string represented by single NUL character
ScValidErrorStyle eScErrorStyle;
bool bShowError = pValData->GetErrMsg( aTitle, aText, eScErrorStyle );
lcl_SetValidationText(aTitle, maErrorTitle);
lcl_SetValidationText(aText, maErrorText);
// flags
switch( pValData->GetDataMode() )
{
case SC_VALID_ANY: mnFlags |= EXC_DV_MODE_ANY; break;
case SC_VALID_WHOLE: mnFlags |= EXC_DV_MODE_WHOLE; break;
case SC_VALID_DECIMAL: mnFlags |= EXC_DV_MODE_DECIMAL; break;
case SC_VALID_LIST: mnFlags |= EXC_DV_MODE_LIST; break;
case SC_VALID_DATE: mnFlags |= EXC_DV_MODE_DATE; break;
case SC_VALID_TIME: mnFlags |= EXC_DV_MODE_TIME; break;
case SC_VALID_TEXTLEN: mnFlags |= EXC_DV_MODE_TEXTLEN; break;
case SC_VALID_CUSTOM: mnFlags |= EXC_DV_MODE_CUSTOM; break;
default: OSL_FAIL( "XclExpDV::XclExpDV - unknown mode" );
}
switch( pValData->GetOperation() )
{
case ScConditionMode::NONE:
case ScConditionMode::Equal: mnFlags |= EXC_DV_COND_EQUAL; break;
case ScConditionMode::Less: mnFlags |= EXC_DV_COND_LESS; break;
case ScConditionMode::Greater: mnFlags |= EXC_DV_COND_GREATER; break;
case ScConditionMode::EqLess: mnFlags |= EXC_DV_COND_EQLESS; break;
case ScConditionMode::EqGreater: mnFlags |= EXC_DV_COND_EQGREATER; break;
case ScConditionMode::NotEqual: mnFlags |= EXC_DV_COND_NOTEQUAL; break;
case ScConditionMode::Between: mnFlags |= EXC_DV_COND_BETWEEN; break;
case ScConditionMode::NotBetween: mnFlags |= EXC_DV_COND_NOTBETWEEN; break;
default: OSL_FAIL( "XclExpDV::XclExpDV - unknown condition" );
}
switch( eScErrorStyle )
{
case SC_VALERR_STOP: mnFlags |= EXC_DV_ERROR_STOP; break;
case SC_VALERR_WARNING: mnFlags |= EXC_DV_ERROR_WARNING; break;
case SC_VALERR_INFO: mnFlags |= EXC_DV_ERROR_INFO; break;
case SC_VALERR_MACRO:
// set INFO for validity with macro call, delete title
mnFlags |= EXC_DV_ERROR_INFO;
maErrorTitle.Assign( '\0' ); // contains macro name
break;
default: OSL_FAIL( "XclExpDV::XclExpDV - unknown error style" );
}
::set_flag( mnFlags, EXC_DV_IGNOREBLANK, pValData->IsIgnoreBlank() );
::set_flag( mnFlags, EXC_DV_SUPPRESSDROPDOWN, pValData->GetListType() == css::sheet::TableValidationVisibility::INVISIBLE );
::set_flag( mnFlags, EXC_DV_SHOWPROMPT, bShowPrompt );
::set_flag( mnFlags, EXC_DV_SHOWERROR, bShowError );
// formulas
XclExpFormulaCompiler& rFmlaComp = GetFormulaCompiler();
// first formula
std::unique_ptr< ScTokenArray > xScTokArr = pValData->CreateFlatCopiedTokenArray( 0 );
if (xScTokArr)
{
if( pValData->GetDataMode() == SC_VALID_LIST )
{
OUString aString;
if( XclTokenArrayHelper::GetStringList( aString, *xScTokArr, '\n' ) )
{
bool bList = false;
OUStringBuffer sListBuf;
OUStringBuffer sFormulaBuf("\"");
/* Formula is a list of string tokens -> build the Excel string.
Data validity is BIFF8 only (important for the XclExpString object).
Excel uses the NUL character as string list separator. */
mxString1.reset( new XclExpString( XclStrFlags::EightBitLength ) );
if (!aString.isEmpty())
{
sal_Int32 nStringIx = 0;
for(;;)
{
const std::u16string_view aToken( o3tl::getToken(aString, 0, '\n', nStringIx ) );
if (aToken.find(',') != std::u16string_view::npos)
{
sListBuf.append(OUString::Concat("\"") + aToken + "\"");
bList = true;
}
else
sListBuf.append(aToken);
mxString1->Append( aToken );
sFormulaBuf.append( aToken );
if (nStringIx<0)
break;
sal_Unicode cUnicodeChar = 0;
mxString1->Append( std::u16string_view(&cUnicodeChar, 1) );
sFormulaBuf.append( ',' );
sListBuf.append( ',' );
}
}
::set_flag( mnFlags, EXC_DV_STRINGLIST );
// maximum length allowed in Excel is 255 characters, and don't end with an empty token
// It should be 8192 but Excel doesn't accept it for unknown reason
// See also https://bugs.documentfoundation.org/show_bug.cgi?id=137167#c2 for more details
sal_uInt32 nLen = sFormulaBuf.getLength();
if( nLen > 256 ) // 255 + beginning quote mark
{
nLen = 256;
if( sFormulaBuf[nLen - 1] == ',' )
--nLen;
sFormulaBuf.truncate(nLen);
}
sFormulaBuf.append( '"' );
msFormula1 = sFormulaBuf.makeStringAndClear();
if (bList)
msList = sListBuf.makeStringAndClear();
else
sListBuf.remove(0, sListBuf.getLength());
}
else
{
/* All other formulas in validation are stored like conditional
formatting formulas (with tRefN/tAreaN tokens as value or
array class). But NOT the cell references and defined names
in list validation - they are stored as reference class
tokens... Example:
1) Cell must be equal to A1 -> formula is =A1 -> writes tRefNV token
2) List is taken from A1 -> formula is =A1 -> writes tRefNR token
Formula compiler supports this by offering two different functions
CreateDataValFormula() and CreateListValFormula(). */
if(GetOutput() == EXC_OUTPUT_BINARY)
mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_LISTVAL, *xScTokArr );
else
msFormula1 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(),
xScTokArr.get());
}
}
else
{
// no list validation -> convert the formula
if(GetOutput() == EXC_OUTPUT_BINARY)
mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_DATAVAL, *xScTokArr );
else
msFormula1 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(),
xScTokArr.get());
}
}
// second formula
xScTokArr = pValData->CreateFlatCopiedTokenArray( 1 );
if (xScTokArr)
{
if(GetOutput() == EXC_OUTPUT_BINARY)
mxTokArr2 = rFmlaComp.CreateFormula( EXC_FMLATYPE_DATAVAL, *xScTokArr );
else
msFormula2 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(),
xScTokArr.get());
}
}
else
{
OSL_FAIL( "XclExpDV::XclExpDV - missing core data" );
mnScHandle = SAL_MAX_UINT32;
}
}
XclExpDV::~XclExpDV()
{
}
void XclExpDV::InsertCellRange( const ScRange& rRange )
{
maScRanges.Join( rRange );
}
bool XclExpDV::Finalize()
{
GetAddressConverter().ConvertRangeList( maXclRanges, maScRanges, true );
return (mnScHandle != SAL_MAX_UINT32) && !maXclRanges.empty();
}
void XclExpDV::WriteBody( XclExpStream& rStrm )
{
// flags and strings
rStrm << mnFlags << maPromptTitle << maErrorTitle << maPromptText << maErrorText;
// condition formulas
if( mxString1 )
lclWriteDvFormula( rStrm, *mxString1 );
else
lclWriteDvFormula( rStrm, mxTokArr1.get() );
lclWriteDvFormula( rStrm, mxTokArr2.get() );
// cell ranges
rStrm << maXclRanges;
}
void XclExpDV::SaveXml( XclExpXmlStream& rStrm )
{
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_dataValidation,
XML_allowBlank, ToPsz( ::get_flag( mnFlags, EXC_DV_IGNOREBLANK ) ),
XML_error, XESTRING_TO_PSZ( maErrorText ),
XML_errorStyle, lcl_GetErrorType(mnFlags),
XML_errorTitle, XESTRING_TO_PSZ( maErrorTitle ),
// OOXTODO: XML_imeMode,
XML_operator, lcl_GetOperatorType( mnFlags ),
XML_prompt, XESTRING_TO_PSZ( maPromptText ),
XML_promptTitle, XESTRING_TO_PSZ( maPromptTitle ),
// showDropDown should have been showNoDropDown - check oox/xlsx import for details
XML_showDropDown, ToPsz( ::get_flag( mnFlags, EXC_DV_SUPPRESSDROPDOWN ) ),
XML_showErrorMessage, ToPsz( ::get_flag( mnFlags, EXC_DV_SHOWERROR ) ),
XML_showInputMessage, ToPsz( ::get_flag( mnFlags, EXC_DV_SHOWPROMPT ) ),
XML_sqref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maScRanges),
XML_type, lcl_GetValidationType(mnFlags) );
if (!msList.isEmpty())
{
rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent),
FSNS(XML_xmlns, XML_x12ac),rStrm.getNamespaceURL(OOX_NS(x12ac)),
FSNS(XML_xmlns, XML_mc),rStrm.getNamespaceURL(OOX_NS(mce)));
rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x12ac");
rWorksheet->startElement(FSNS(XML_x12ac, XML_list));
rWorksheet->writeEscaped(msList);
rWorksheet->endElement(FSNS(XML_x12ac, XML_list));
rWorksheet->endElement(FSNS(XML_mc, XML_Choice));
rWorksheet->startElement(FSNS(XML_mc, XML_Fallback));
rWorksheet->startElement(XML_formula1);
rWorksheet->writeEscaped(msFormula1);
rWorksheet->endElement(XML_formula1);
rWorksheet->endElement(FSNS(XML_mc, XML_Fallback));
rWorksheet->endElement(FSNS(XML_mc, XML_AlternateContent));
}
if (msList.isEmpty() && !msFormula1.isEmpty())
{
rWorksheet->startElement(XML_formula1);
rWorksheet->writeEscaped( msFormula1 );
rWorksheet->endElement( XML_formula1 );
}
if( !msFormula2.isEmpty() )
{
rWorksheet->startElement(XML_formula2);
rWorksheet->writeEscaped( msFormula2 );
rWorksheet->endElement( XML_formula2 );
}
rWorksheet->endElement( XML_dataValidation );
}
XclExpDval::XclExpDval( const XclExpRoot& rRoot ) :
XclExpRecord( EXC_ID_DVAL, 18 ),
XclExpRoot( rRoot )
{
}
XclExpDval::~XclExpDval()
{
}
void XclExpDval::InsertCellRange( const ScRange& rRange, sal_uInt32 nScHandle )
{
if( GetBiff() == EXC_BIFF8 )
{
XclExpDV& rDVRec = SearchOrCreateDv( nScHandle );
rDVRec.InsertCellRange( rRange );
}
}
void XclExpDval::Save( XclExpStream& rStrm )
{
// check all records
size_t nPos = maDVList.GetSize();
while( nPos )
{
--nPos; // backwards to keep nPos valid
XclExpDVRef xDVRec = maDVList.GetRecord( nPos );
if( !xDVRec->Finalize() )
maDVList.RemoveRecord( nPos );
}
// write the DVAL and the DV's
if( !maDVList.IsEmpty() )
{
XclExpRecord::Save( rStrm );
maDVList.Save( rStrm );
}
}
void XclExpDval::SaveXml( XclExpXmlStream& rStrm )
{
if( maDVList.IsEmpty() )
return;
sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream();
rWorksheet->startElement( XML_dataValidations,
XML_count, OString::number(maDVList.GetSize())
// OOXTODO: XML_disablePrompts,
// OOXTODO: XML_xWindow,
// OOXTODO: XML_yWindow
);
maDVList.SaveXml( rStrm );
rWorksheet->endElement( XML_dataValidations );
}
XclExpDV& XclExpDval::SearchOrCreateDv( sal_uInt32 nScHandle )
{
// test last found record
if( mxLastFoundDV && (mxLastFoundDV->GetScHandle() == nScHandle) )
return *mxLastFoundDV;
// binary search
size_t nCurrPos = 0;
if( !maDVList.IsEmpty() )
{
size_t nFirstPos = 0;
size_t nLastPos = maDVList.GetSize() - 1;
bool bLoop = true;
sal_uInt32 nCurrScHandle = ::std::numeric_limits< sal_uInt32 >::max();
while( (nFirstPos <= nLastPos) && bLoop )
{
nCurrPos = (nFirstPos + nLastPos) / 2;
mxLastFoundDV = maDVList.GetRecord( nCurrPos );
nCurrScHandle = mxLastFoundDV->GetScHandle();
if( nCurrScHandle == nScHandle )
bLoop = false;
else if( nCurrScHandle < nScHandle )
nFirstPos = nCurrPos + 1;
else if( nCurrPos )
nLastPos = nCurrPos - 1;
else // special case for nLastPos = -1
bLoop = false;
}
if( nCurrScHandle == nScHandle )
return *mxLastFoundDV;
else if( nCurrScHandle < nScHandle )
++nCurrPos;
}
// create new DV record
mxLastFoundDV = new XclExpDV( *this, nScHandle );
maDVList.InsertRecord( mxLastFoundDV, nCurrPos );
return *mxLastFoundDV;
}
void XclExpDval::WriteBody( XclExpStream& rStrm )
{
rStrm.WriteZeroBytes( 10 );
rStrm << EXC_DVAL_NOOBJ << static_cast< sal_uInt32 >( maDVList.GetSize() );
}
// Web Queries ================================================================
XclExpWebQuery::XclExpWebQuery(
const OUString& rRangeName,
const OUString& rUrl,
std::u16string_view rSource,
sal_Int32 nRefrSecs ) :
maDestRange( rRangeName ),
maUrl( rUrl ),
// refresh delay time: seconds -> minutes
mnRefresh( ulimit_cast< sal_Int16 >( (nRefrSecs + 59) / 60 ) ),
mbEntireDoc( false )
{
// comma separated list of HTML table names or indexes
OUString aNewTables;
OUString aAppendTable;
bool bExitLoop = false;
if (!rSource.empty())
{
sal_Int32 nStringIx = 0;
do
{
OUString aToken( o3tl::getToken(rSource, 0, ';', nStringIx ) );
mbEntireDoc = ScfTools::IsHTMLDocName( aToken );
bExitLoop = mbEntireDoc || ScfTools::IsHTMLTablesName( aToken );
if( !bExitLoop && ScfTools::GetHTMLNameFromName( aToken, aAppendTable ) )
aNewTables = ScGlobal::addToken( aNewTables, aAppendTable, ',' );
}
while (nStringIx>0 && !bExitLoop);
}
if( !bExitLoop ) // neither HTML_all nor HTML_tables found
{
if( !aNewTables.isEmpty() )
mxQryTables.reset( new XclExpString( aNewTables ) );
else
mbEntireDoc = true;
}
}
XclExpWebQuery::~XclExpWebQuery()
{
}
void XclExpWebQuery::Save( XclExpStream& rStrm )
{
OSL_ENSURE( !mbEntireDoc || !mxQryTables, "XclExpWebQuery::Save - illegal mode" );
sal_uInt16 nFlags;
// QSI record
rStrm.StartRecord( EXC_ID_QSI, 10 + maDestRange.GetSize() );
rStrm << EXC_QSI_DEFAULTFLAGS
<< sal_uInt16( 0x0010 )
<< sal_uInt16( 0x0012 )
<< sal_uInt32( 0x00000000 )
<< maDestRange;
rStrm.EndRecord();
// PARAMQRY record
nFlags = 0;
::insert_value( nFlags, EXC_PQRYTYPE_WEBQUERY, 0, 3 );
::set_flag( nFlags, EXC_PQRY_WEBQUERY );
::set_flag( nFlags, EXC_PQRY_TABLES, !mbEntireDoc );
rStrm.StartRecord( EXC_ID_PQRY, 12 );
rStrm << nFlags
<< sal_uInt16( 0x0000 )
<< sal_uInt16( 0x0001 );
rStrm.WriteZeroBytes( 6 );
rStrm.EndRecord();
// WQSTRING record
rStrm.StartRecord( EXC_ID_WQSTRING, maUrl.GetSize() );
rStrm << maUrl;
rStrm.EndRecord();
// unknown record 0x0802
rStrm.StartRecord( EXC_ID_0802, 16 + maDestRange.GetSize() );
rStrm << EXC_ID_0802; // repeated record id ?!?
rStrm.WriteZeroBytes( 6 );
rStrm << sal_uInt16( 0x0003 )
<< sal_uInt32( 0x00000000 )
<< sal_uInt16( 0x0010 )
<< maDestRange;
rStrm.EndRecord();
// WEBQRYSETTINGS record
nFlags = mxQryTables ? EXC_WQSETT_SPECTABLES : EXC_WQSETT_ALL;
rStrm.StartRecord( EXC_ID_WQSETT, 28 );
rStrm << EXC_ID_WQSETT // repeated record id ?!?
<< sal_uInt16( 0x0000 )
<< sal_uInt16( 0x0004 )
<< sal_uInt16( 0x0000 )
<< EXC_WQSETT_DEFAULTFLAGS
<< nFlags;
rStrm.WriteZeroBytes( 10 );
rStrm << mnRefresh // refresh delay in minutes
<< EXC_WQSETT_FORMATFULL
<< sal_uInt16( 0x0000 );
rStrm.EndRecord();
// WEBQRYTABLES record
if( mxQryTables )
{
rStrm.StartRecord( EXC_ID_WQTABLES, 4 + mxQryTables->GetSize() );
rStrm << EXC_ID_WQTABLES // repeated record id ?!?
<< sal_uInt16( 0x0000 )
<< *mxQryTables; // comma separated list of source tables
rStrm.EndRecord();
}
}
XclExpWebQueryBuffer::XclExpWebQueryBuffer( const XclExpRoot& rRoot )
{
SCTAB nScTab = rRoot.GetCurrScTab();
ScDocShell* pShell = rRoot.GetDocShell();
if( !pShell ) return;
ScfPropertySet aModelProp( pShell->GetModel() );
if( !aModelProp.Is() ) return;
Reference< XAreaLinks > xAreaLinks;
aModelProp.GetProperty( xAreaLinks, SC_UNO_AREALINKS );
if( !xAreaLinks.is() ) return;
for( sal_Int32 nIndex = 0, nCount = xAreaLinks->getCount(); nIndex < nCount; ++nIndex )
{
Reference< XAreaLink > xAreaLink( xAreaLinks->getByIndex( nIndex ), UNO_QUERY );
if( xAreaLink.is() )
{
CellRangeAddress aDestRange( xAreaLink->getDestArea() );
if( static_cast< SCTAB >( aDestRange.Sheet ) == nScTab )
{
ScfPropertySet aLinkProp( xAreaLink );
OUString aFilter;
if( aLinkProp.GetProperty( aFilter, SC_UNONAME_FILTER ) &&
(aFilter == EXC_WEBQRY_FILTER) )
{
// get properties
OUString /*aFilterOpt,*/ aUrl;
sal_Int32 nRefresh = 0;
// aLinkProp.GetProperty( aFilterOpt, SC_UNONAME_FILTOPT );
aLinkProp.GetProperty( aUrl, SC_UNONAME_LINKURL );
aLinkProp.GetProperty( nRefresh, SC_UNONAME_REFDELAY );
OUString aAbsDoc( ScGlobal::GetAbsDocName( aUrl, pShell ) );
INetURLObject aUrlObj( aAbsDoc );
OUString aWebQueryUrl( aUrlObj.getFSysPath( FSysStyle::Dos ) );
if( aWebQueryUrl.isEmpty() )
aWebQueryUrl = aAbsDoc;
// find range or create a new range
OUString aRangeName;
ScRange aScDestRange;
ScUnoConversion::FillScRange( aScDestRange, aDestRange );
if( const ScRangeData* pRangeData = rRoot.GetNamedRanges().findByRange( aScDestRange ) )
{
aRangeName = pRangeData->GetName();
}
else
{
XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler();
XclExpNameManager& rNameMgr = rRoot.GetNameManager();
// create a new unique defined name containing the range
XclTokenArrayRef xTokArr = rFmlaComp.CreateFormula( EXC_FMLATYPE_WQUERY, aScDestRange );
sal_uInt16 nNameIdx = rNameMgr.InsertUniqueName( aUrlObj.getBase(), xTokArr, nScTab );
aRangeName = rNameMgr.GetOrigName( nNameIdx );
}
// create and store the web query record
if( !aRangeName.isEmpty() )
AppendNewRecord( new XclExpWebQuery(
aRangeName, aWebQueryUrl, xAreaLink->getSourceArea(), nRefresh ) );
}
}
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'ToOString' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'truncate' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'remove' is required to be utilized.