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