/* -*- 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 )
    {
        // There is an undocumented limitation of 8192 characters for XLSX hyperlink length; and
        // one more for XLS format, which is a strange number 2083; both found by experimentation:
        const sal_uInt16 nMaxLen = rRoot.GetOutput() == EXC_OUTPUT_XML_2007 ? 8192 : 2083;
        XclExpString aUrl(aUrlObj.GetURLNoMark(), XclStrFlags::ForceUnicode, nMaxLen);
        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, const OString& rText)
{
    OStringBuffer aBuffer;
    XclXmlUtils::ToOString(aBuffer, rAddress);
    OString aPos = aBuffer.makeStringAndClear();
    // double quotes in rText need to be escaped
    const OString aText = rText.replaceAll("\""_ostr, "\"\""_ostr);
    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(\"" + aText + "\"))=\"" + aText + "\"");
        case ScConditionMode::EndsWith:
            return OString("RIGHT(" + aPos +",LEN(\"" + aText + "\"))=\"" + aText + "\"");
        case ScConditionMode::ContainsText:
            return OString(OString::Concat("NOT(ISERROR(SEARCH(\"") + aText + "\"," + aPos + ")))");
        case ScConditionMode::NotContainsText:
            return OString(OString::Concat("ISERROR(SEARCH(\"") +  aText + "\"," + 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);
        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 "tomorrow";
        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.GetMode() != ScConditionMode::EqGreater));

    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: */
