/* -*- 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 <sal/config.h>
 
#include <string_view>
 
#include <scitems.hxx>
#include <editeng/eeitem.hxx>
 
#include <utility>
#include <vcl/svapp.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/justifyitem.hxx>
#include <svx/xoutbmp.hxx>
#include <editeng/editeng.hxx>
#include <svtools/htmlcfg.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/frmhtmlw.hxx>
#include <sfx2/objsh.hxx>
#include <svl/urihelper.hxx>
#include <svtools/htmlkywd.hxx>
#include <svtools/htmlout.hxx>
#include <svtools/parhtml.hxx>
#include <vcl/outdev.hxx>
#include <stdio.h>
#include <osl/diagnose.h>
#include <o3tl/unit_conversion.hxx>
#include <o3tl/string_view.hxx>
 
#include <htmlexp.hxx>
#include <global.hxx>
#include <postit.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <attrib.hxx>
#include <patattr.hxx>
#include <stlpool.hxx>
#include <scresid.hxx>
#include <formulacell.hxx>
#include <cellform.hxx>
#include <docoptio.hxx>
#include <editutil.hxx>
#include <ftools.hxx>
#include <cellvalue.hxx>
#include <conditio.hxx>
#include <colorscale.hxx>
#include <mtvelements.hxx>
 
#include <editeng/flditem.hxx>
#include <editeng/borderline.hxx>
 
// Without strings.hrc: error C2679: binary '=' : no operator defined which takes a
// right-hand operand of type 'const class String (__stdcall *)(class ScResId)'
// at
// const String aStrTable( ScResId( SCSTR_TABLE ) ); aStrOut = aStrTable;
// ?!???
#include <strings.hrc>
#include <globstr.hrc>
 
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <rtl/strbuf.hxx>
#include <officecfg/Office/Common.hxx>
#include <tools/json_writer.hxx>
#include <svl/numformat.hxx>
#include <svl/zformat.hxx>
 
using ::editeng::SvxBorderLine;
using namespace ::com::sun::star;
 
const char sMyBegComment[]   = "<!-- ";
const char sMyEndComment[]   = " -->";
const char sDisplay[]        = "display:";
const char sBorder[]         = "border:";
const char sBackground[]     = "background:";
 
const sal_uInt16 ScHTMLExport::nDefaultFontSize[SC_HTML_FONTSIZES] =
{
    HTMLFONTSZ1_DFLT, HTMLFONTSZ2_DFLT, HTMLFONTSZ3_DFLT, HTMLFONTSZ4_DFLT,
    HTMLFONTSZ5_DFLT, HTMLFONTSZ6_DFLT, HTMLFONTSZ7_DFLT
};
 
sal_uInt16 ScHTMLExport::nFontSize[SC_HTML_FONTSIZES] = { 0 };
 
const char* ScHTMLExport::pFontSizeCss[SC_HTML_FONTSIZES] =
{
    "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"
};
 
const sal_uInt16 ScHTMLExport::nCellSpacing = 0;
const char ScHTMLExport::sIndentSource[nIndentMax+1] =
    "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
 
// Macros for HTML export
 
#define TAG_ON( tag )       HTMLOutFuncs::Out_AsciiTag( rStrm, tag )
#define TAG_OFF( tag )      HTMLOutFuncs::Out_AsciiTag( rStrm, tag, false )
#define OUT_STR( str )      HTMLOutFuncs::Out_String( rStrm, str, &aNonConvertibleChars )
#define OUT_LF()            rStrm.WriteOString( SAL_NEWLINE_STRING ).WriteOString( GetIndentStr() )
#define TAG_ON_LF( tag )    (TAG_ON( tag ).WriteOString( SAL_NEWLINE_STRING ).WriteOString( GetIndentStr() ))
#define TAG_OFF_LF( tag )   (TAG_OFF( tag ).WriteOString( SAL_NEWLINE_STRING ).WriteOString( GetIndentStr() ))
#define OUT_HR()            TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_horzrule )
#define OUT_COMMENT( comment )  (rStrm.WriteOString( sMyBegComment ), OUT_STR( comment ) \
                                .WriteOString( sMyEndComment ).WriteOString( SAL_NEWLINE_STRING ) \
                                .WriteOString( GetIndentStr() ))
 
#define OUT_SP_CSTR_ASS( s )    rStrm.WriteChar(' ').WriteOString( s ).WriteChar( '=' )
 
#define GLOBSTR(id) ScResId( id )
 
void ScFormatFilterPluginImpl::ScExportHTML( SvStream& rStrm, const OUString& rBaseURL, ScDocument* pDoc,
        const ScRange& rRange, const rtl_TextEncoding /*eNach*/, bool bAll,
        const OUString& rStreamPath, OUString& rNonConvertibleChars, const OUString& rFilterOptions )
{
    ScHTMLExport aEx( rStrm, rBaseURL, pDoc, rRange, bAll, rStreamPath, rFilterOptions );
    aEx.Write();
    rNonConvertibleChars = aEx.GetNonConvertibleChars();
}
 
static OString lcl_getColGroupString(sal_Int32 nSpan, sal_Int32 nWidth)
{
    OStringBuffer aByteStr(OString::Concat(OOO_STRING_SVTOOLS_HTML_colgroup)
        + " ");
    if( nSpan > 1 )
    {
        aByteStr.append(OString::Concat(OOO_STRING_SVTOOLS_HTML_O_span)
            + "=\""
            + OString::number(nSpan)
            + "\" ");
    }
    aByteStr.append(OString::Concat(OOO_STRING_SVTOOLS_HTML_O_width)
        + "=\""
        + OString::number(nWidth)
        + "\"");
    return aByteStr.makeStringAndClear();
}
 
static void lcl_AddStamp( OUString& rStr, std::u16string_view rName,
    const css::util::DateTime& rDateTime,
    const LocaleDataWrapper& rLoc )
{
    Date aD(rDateTime.Day, rDateTime.Month, rDateTime.Year);
    tools::Time aT(rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds,
            rDateTime.NanoSeconds);
    DateTime aDateTime(aD,aT);
 
    OUString        aStrDate    = rLoc.getDate( aDateTime );
    OUString        aStrTime    = rLoc.getTime( aDateTime );
 
    rStr += GLOBSTR( STR_BY ) + " ";
    if (!rName.empty())
        rStr += rName;
    else
        rStr += "???";
    rStr += " " + GLOBSTR( STR_ON ) + " ";
    if (!aStrDate.isEmpty())
        rStr += aStrDate;
    else
        rStr += "???";
    rStr += ", ";
    if (!aStrTime.isEmpty())
        rStr += aStrTime;
    else
        rStr += "???";
}
 
static OString lcl_makeHTMLColorTriplet(const Color& rColor)
{
    char    buf[24];
 
    // <font COLOR="#00FF40">hello</font>
    snprintf( buf, 24, "\"#%02X%02X%02X\"", rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() );
 
    return buf;
}
 
ScHTMLExport::ScHTMLExport( SvStream& rStrmP, OUString _aBaseURL, ScDocument* pDocP,
                            const ScRange& rRangeP, bool bAllP,
                            OUString aStreamPathP, std::u16string_view rFilterOptions ) :
    ScExportBase( rStrmP, pDocP, rRangeP ),
    aBaseURL(std::move( _aBaseURL )),
    aStreamPath(std::move( aStreamPathP )),
    pAppWin( Application::GetDefaultDevice() ),
    nUsedTables( 0 ),
    nIndent( 0 ),
    bAll( bAllP ),
    bTabHasGraphics( false ),
    bTabAlignedLeft( false ),
    bCalcAsShown( pDocP->GetDocOptions().IsCalcAsShown() ),
    bTableDataHeight( true ),
    mbSkipImages ( false ),
    mbSkipHeaderFooter( false )
{
    strcpy( sIndent, sIndentSource );
    sIndent[0] = 0;
 
    // set HTML configuration
    bCopyLocalFileToINet = officecfg::Office::Common::Filter::HTML::Export::LocalGraphic::get();
 
    if (rFilterOptions == u"SkipImages")
    {
        mbSkipImages = true;
    }
    else if (rFilterOptions == u"SkipHeaderFooter")
    {
        mbSkipHeaderFooter = true;
    }
 
    for ( sal_uInt16 j=0; j < SC_HTML_FONTSIZES; j++ )
    {
        sal_uInt16 nSize = SvxHtmlOptions::GetFontSize( j );
        // remember in Twips, like our SvxFontHeightItem
        if ( nSize )
            nFontSize[j] = nSize * 20;
        else
            nFontSize[j] = nDefaultFontSize[j] * 20;
    }
 
    const SCTAB nCount = pDoc->GetTableCount();
    for ( SCTAB nTab = 0; nTab < nCount; nTab++ )
    {
        if ( !IsEmptyTable( nTab ) )
            nUsedTables++;
    }
}
 
ScHTMLExport::~ScHTMLExport()
{
    aGraphList.clear();
}
 
sal_uInt16 ScHTMLExport::GetFontSizeNumber( sal_uInt16 nHeight )
{
    sal_uInt16 nSize = 1;
    for ( sal_uInt16 j=SC_HTML_FONTSIZES-1; j>0; j-- )
    {
        if( nHeight > (nFontSize[j] + nFontSize[j-1]) / 2 )
        {   // The one next to it
            nSize = j+1;
            break;
        }
    }
    return nSize;
}
 
const char* ScHTMLExport::GetFontSizeCss( sal_uInt16 nHeight )
{
    sal_uInt16 nSize = GetFontSizeNumber( nHeight );
    return pFontSizeCss[ nSize-1 ];
}
 
sal_uInt16 ScHTMLExport::ToPixel( sal_uInt16 nVal )
{
    if( nVal )
    {
        nVal = static_cast<sal_uInt16>(pAppWin->LogicToPixel(
                    Size( nVal, nVal ), MapMode( MapUnit::MapTwip ) ).Width());
        if( !nVal ) // If there's a Twip there should also be a Pixel
            nVal = 1;
    }
    return nVal;
}
 
Size ScHTMLExport::MMToPixel( const Size& rSize )
{
    Size aSize = pAppWin->LogicToPixel( rSize, MapMode( MapUnit::Map100thMM ) );
    // If there's something there should also be a Pixel
    if ( !aSize.Width() && rSize.Width() )
        aSize.setWidth( 1 );
    if ( !aSize.Height() && rSize.Height() )
        aSize.setHeight( 1 );
    return aSize;
}
 
void ScHTMLExport::Write()
{
    if (!mbSkipHeaderFooter)
    {
        rStrm.WriteChar( '<' ).WriteOString( OOO_STRING_SVTOOLS_HTML_doctype ).WriteChar( ' ' ).WriteOString( OOO_STRING_SVTOOLS_HTML_doctype5 ).WriteChar( '>' )
           .WriteOString( SAL_NEWLINE_STRING ).WriteOString( SAL_NEWLINE_STRING );
        TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_html );
        WriteHeader();
        OUT_LF();
    }
    WriteBody();
    OUT_LF();
    if (!mbSkipHeaderFooter)
        TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_html );
}
 
void ScHTMLExport::WriteHeader()
{
    IncIndent(1); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_head );
 
    if ( pDoc->IsClipOrUndo() )
    {   // no real DocInfo available, but some META information like charset needed
        SfxFrameHTMLWriter::Out_DocInfo( rStrm, aBaseURL, nullptr, sIndent, &aNonConvertibleChars );
    }
    else
    {
        uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
            static_cast<cppu::OWeakObject*>(pDoc->GetDocumentShell()->GetModel()), uno::UNO_QUERY_THROW);
        uno::Reference<document::XDocumentProperties> xDocProps
            = xDPS->getDocumentProperties();
        SfxFrameHTMLWriter::Out_DocInfo( rStrm, aBaseURL, xDocProps,
            sIndent, &aNonConvertibleChars );
        OUT_LF();
 
        if (!xDocProps->getPrintedBy().isEmpty())
        {
            OUT_COMMENT( GLOBSTR( STR_DOC_INFO ) );
            OUString aStrOut = GLOBSTR( STR_DOC_PRINTED ) + ": ";
            lcl_AddStamp( aStrOut, xDocProps->getPrintedBy(),
                xDocProps->getPrintDate(), ScGlobal::getLocaleData() );
            OUT_COMMENT( aStrOut );
        }
 
    }
    OUT_LF();
 
    // CSS1 StyleSheet
    PageDefaults( bAll ? 0 : aRange.aStart.Tab() );
    IncIndent(1);
    rStrm.WriteOString( "<" ).WriteOString( OOO_STRING_SVTOOLS_HTML_style ).WriteOString( " " ).WriteOString( OOO_STRING_SVTOOLS_HTML_O_type ).WriteOString( "=\"text/css\">" );
 
    OUT_LF();
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_body);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_division);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_table);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_thead);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_tbody);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_tfoot);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_tablerow);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_tableheader);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_tabledata);
    rStrm.WriteOString(",");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_parabreak);
    rStrm.WriteOString(" { ");
    rStrm.WriteOString("font-family:");
 
    if (!aHTMLStyle.aFontFamilyName.isEmpty())
    {
        const OUString& rList = aHTMLStyle.aFontFamilyName;
        for(sal_Int32 nPos {0};;)
        {
            rStrm.WriteChar( '\"' );
            OUT_STR( o3tl::getToken( rList, 0, ';', nPos ) );
            rStrm.WriteChar( '\"' );
            if (nPos<0)
                break;
            rStrm.WriteOString( ", " );
        }
    }
    rStrm.WriteOString("; ");
    rStrm.WriteOString("font-size:");
    rStrm.WriteOString(GetFontSizeCss(static_cast<sal_uInt16>(aHTMLStyle.nFontHeight)));
    rStrm.WriteOString(" }");
 
    OUT_LF();
 
    // write the style for the comments to make them stand out from normal cell content
    // this is done through only showing the cell contents when the custom indicator is hovered
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_anchor);
    rStrm.WriteOString(".comment-indicator:hover");
    rStrm.WriteOString(" + ");
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_comment2);
    rStrm.WriteOString(" { ");
    rStrm.WriteOString(sBackground);
    rStrm.WriteOString("#ffd");
    rStrm.WriteOString("; ");
    rStrm.WriteOString("position:");
    rStrm.WriteOString("absolute");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(sDisplay);
    rStrm.WriteOString("block");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(sBorder);
    rStrm.WriteOString("1px solid black");
    rStrm.WriteOString("; ");
    rStrm.WriteOString("padding:");
    rStrm.WriteOString("0.5em");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(" } ");
 
    OUT_LF();
 
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_anchor);
    rStrm.WriteOString(".comment-indicator");
    rStrm.WriteOString(" { ");
    rStrm.WriteOString(sBackground);
    rStrm.WriteOString("red");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(sDisplay);
    rStrm.WriteOString("inline-block");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(sBorder);
    rStrm.WriteOString("1px solid black");
    rStrm.WriteOString("; ");
    rStrm.WriteOString("width:");
    rStrm.WriteOString("0.5em");
    rStrm.WriteOString("; ");
    rStrm.WriteOString("height:");
    rStrm.WriteOString("0.5em");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(" } ");
 
    OUT_LF();
 
    rStrm.WriteOString(OOO_STRING_SVTOOLS_HTML_comment2);
    rStrm.WriteOString(" { ");
    rStrm.WriteOString(sDisplay);
    rStrm.WriteOString("none");
    rStrm.WriteOString("; ");
    rStrm.WriteOString(" } ");
 
 
    IncIndent(-1);
    OUT_LF();
    TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_style );
 
    IncIndent(-1);
    OUT_LF();
    TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_head );
}
 
void ScHTMLExport::WriteOverview()
{
    if ( nUsedTables <= 1 )
        return;
 
    IncIndent(1);
    OUT_HR();
    IncIndent(1); TAG_ON( OOO_STRING_SVTOOLS_HTML_parabreak ); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_center );
    TAG_ON( OOO_STRING_SVTOOLS_HTML_head1 );
    OUT_STR( ScResId( STR_OVERVIEW ) );
    TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_head1 );
 
    OUString aStr;
 
    const SCTAB nCount = pDoc->GetTableCount();
    for ( SCTAB nTab = 0; nTab < nCount; nTab++ )
    {
        if ( !IsEmptyTable( nTab ) )
        {
            pDoc->GetName( nTab, aStr );
            rStrm.WriteOString( "<A HREF=\"#table" )
               .WriteOString( OString::number(nTab) )
               .WriteOString( "\">" );
            OUT_STR( aStr );
            rStrm.WriteOString( "</A>" );
            TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_linebreak );
        }
    }
 
    IncIndent(-1); OUT_LF();
    IncIndent(-1); TAG_OFF( OOO_STRING_SVTOOLS_HTML_center ); TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_parabreak );
}
 
const SfxItemSet& ScHTMLExport::PageDefaults( SCTAB nTab )
{
    SfxStyleSheetBasePool*  pStylePool  = pDoc->GetStyleSheetPool();
    SfxStyleSheetBase*      pStyleSheet = nullptr;
    OSL_ENSURE( pStylePool, "StylePool not found! :-(" );
 
    // remember defaults for compare in WriteCell
    if ( !aHTMLStyle.bInitialized )
    {
        pStyleSheet = pStylePool->Find(
                ScResId(STR_STYLENAME_STANDARD),
                SfxStyleFamily::Para );
        OSL_ENSURE( pStyleSheet, "ParaStyle not found! :-(" );
        if (!pStyleSheet)
            pStyleSheet = pStylePool->First(SfxStyleFamily::Para);
        const SfxItemSet& rSetPara = pStyleSheet->GetItemSet();
 
        aHTMLStyle.nDefaultScriptType = ScGlobal::GetDefaultScriptType();
        aHTMLStyle.aFontFamilyName = static_cast<const SvxFontItem&>((rSetPara.Get(
                        ScGlobal::GetScriptedWhichID(
                            aHTMLStyle.nDefaultScriptType, ATTR_FONT
                            )))).GetFamilyName();
        aHTMLStyle.nFontHeight = static_cast<const SvxFontHeightItem&>((rSetPara.Get(
                        ScGlobal::GetScriptedWhichID(
                            aHTMLStyle.nDefaultScriptType, ATTR_FONT_HEIGHT
                            )))).GetHeight();
        aHTMLStyle.nFontSizeNumber = GetFontSizeNumber( static_cast< sal_uInt16 >( aHTMLStyle.nFontHeight ) );
    }
 
    // Page style sheet printer settings, e.g. for background graphics.
    // There's only one background graphic in HTML!
    pStyleSheet = pStylePool->Find( pDoc->GetPageStyle( nTab ), SfxStyleFamily::Page );
    OSL_ENSURE( pStyleSheet, "PageStyle not found! :-(" );
    if (!pStyleSheet)
        pStyleSheet = pStylePool->First(SfxStyleFamily::Page);
    const SfxItemSet& rSet = pStyleSheet->GetItemSet();
    if ( !aHTMLStyle.bInitialized )
    {
        const SvxBrushItem* pBrushItem = &rSet.Get( ATTR_BACKGROUND );
        aHTMLStyle.aBackgroundColor = pBrushItem->GetColor();
        aHTMLStyle.bInitialized = true;
    }
    return rSet;
}
 
OString ScHTMLExport::BorderToStyle(const char* pBorderName,
        const SvxBorderLine* pLine, bool& bInsertSemicolon)
{
    OStringBuffer aOut;
 
    if ( pLine )
    {
        if ( bInsertSemicolon )
            aOut.append("; ");
 
        // which border
        aOut.append(OString::Concat("border-") + pBorderName + ": ");
 
        // thickness
        int nWidth = pLine->GetWidth();
        int nPxWidth = (nWidth > 0) ?
            std::max(o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::px), sal_Int64(1)) : 0;
        aOut.append(OString::number(nPxWidth) + "px ");
        switch (pLine->GetBorderLineStyle())
        {
            case SvxBorderLineStyle::SOLID:
                aOut.append("solid");
                break;
            case SvxBorderLineStyle::DOTTED:
                aOut.append("dotted");
                break;
            case SvxBorderLineStyle::DASHED:
            case SvxBorderLineStyle::DASH_DOT:
            case SvxBorderLineStyle::DASH_DOT_DOT:
                aOut.append("dashed");
                break;
            case SvxBorderLineStyle::DOUBLE:
            case SvxBorderLineStyle::DOUBLE_THIN:
            case SvxBorderLineStyle::THINTHICK_SMALLGAP:
            case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
            case SvxBorderLineStyle::THINTHICK_LARGEGAP:
            case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
            case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
            case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
                aOut.append("double");
                break;
            case SvxBorderLineStyle::EMBOSSED:
                aOut.append("ridge");
                break;
            case SvxBorderLineStyle::ENGRAVED:
                aOut.append("groove");
                break;
            case SvxBorderLineStyle::OUTSET:
                aOut.append("outset");
                break;
            case SvxBorderLineStyle::INSET:
                aOut.append("inset");
                break;
            default:
                aOut.append("hidden");
        }
        aOut.append(" #");
 
        // color
        char hex[7];
        snprintf( hex, 7, "%06" SAL_PRIxUINT32, static_cast<sal_uInt32>( pLine->GetColor().GetRGBColor() ) );
        hex[6] = 0;
 
        aOut.append(hex);
 
        bInsertSemicolon = true;
    }
 
    return aOut.makeStringAndClear();
}
 
void ScHTMLExport::WriteBody()
{
    const SfxItemSet& rSet = PageDefaults( bAll ? 0 : aRange.aStart.Tab() );
    const SvxBrushItem* pBrushItem = &rSet.Get( ATTR_BACKGROUND );
 
    // default text color black
    if (!mbSkipHeaderFooter)
    {
        rStrm.WriteChar( '<' ).WriteOString( OOO_STRING_SVTOOLS_HTML_body );
 
        if (!mbSkipImages)
        {
            if ( bAll && GPOS_NONE != pBrushItem->GetGraphicPos() )
            {
                OUString aLink = pBrushItem->GetGraphicLink();
                OUString aGrfNm;
 
                // Embedded graphic -> write using WriteGraphic
                if( aLink.isEmpty() )
                {
                    const Graphic* pGrf = pBrushItem->GetGraphic();
                    if( pGrf )
                    {
                        // Save graphic as (JPG) file
                        aGrfNm = aStreamPath;
                        ErrCode nErr = XOutBitmap::WriteGraphic( *pGrf, aGrfNm,
                            u"JPG"_ustr, XOutFlags::UseNativeIfPossible );
                        if( !nErr ) // Contains errors, as we have nothing to output
                        {
                            aGrfNm = URIHelper::SmartRel2Abs(
                                    INetURLObject(aBaseURL),
                                    aGrfNm, URIHelper::GetMaybeFileHdl());
                            aLink = aGrfNm;
                        }
                    }
                }
                else
                {
                    aGrfNm = aLink;
                    if( bCopyLocalFileToINet )
                    {
                        CopyLocalFileToINet( aGrfNm, aStreamPath );
                    }
                    else
                        aGrfNm = URIHelper::SmartRel2Abs(
                                INetURLObject(aBaseURL),
                                aGrfNm, URIHelper::GetMaybeFileHdl());
                    aLink = aGrfNm;
                }
                if( !aLink.isEmpty() )
                {
                    rStrm.WriteChar( ' ' ).WriteOString( OOO_STRING_SVTOOLS_HTML_O_background ).WriteOString( "=\"" );
                    OUT_STR( URIHelper::simpleNormalizedMakeRelative(
                                aBaseURL,
                                aLink ) ).WriteChar( '\"' );
                }
            }
        }
        if ( !aHTMLStyle.aBackgroundColor.IsTransparent() )
        {   // A transparent background color should always result in default
            // background of the browser. Also, HTMLOutFuncs::Out_Color() writes
            // black #000000 for COL_AUTO which is the same as white #ffffff with
            // transparency set to 0xff, our default background.
            OUT_SP_CSTR_ASS( OOO_STRING_SVTOOLS_HTML_O_bgcolor );
            HTMLOutFuncs::Out_Color( rStrm, aHTMLStyle.aBackgroundColor );
        }
 
        rStrm.WriteChar( '>' ); OUT_LF();
 
        // A marker right after <body> can be used, so that data-sheets-* attributes are considered
        // at all. This is disabled by default.
        OString aMarker;
        char* pEnv = getenv("SC_DEBUG_HTML_MARKER");
        if (pEnv)
        {
            aMarker = pEnv;
        }
        else if (comphelper::LibreOfficeKit::isActive())
        {
            aMarker = "<google-sheets-html-origin/>"_ostr;
        }
        rStrm.WriteOString(aMarker);
    }
 
    if ( bAll )
        WriteOverview();
 
    WriteTables();
 
    if (!mbSkipHeaderFooter)
        TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_body );
}
 
void ScHTMLExport::WriteTables()
{
    const SCTAB nTabCount = pDoc->GetTableCount();
    const OUString  aStrTable( ScResId( SCSTR_TABLE ) );
    OUString   aStr;
    OUString        aStrOut;
    SCCOL           nStartCol;
    SCROW           nStartRow;
    SCTAB           nStartTab;
    SCCOL           nEndCol;
    SCROW           nEndRow;
    SCTAB           nEndTab;
    SCCOL           nStartColFix = 0;
    SCROW           nStartRowFix = 0;
    SCCOL           nEndColFix = 0;
    SCROW           nEndRowFix = 0;
    ScDrawLayer*    pDrawLayer = pDoc->GetDrawLayer();
    if ( bAll )
    {
        nStartTab = 0;
        nEndTab = nTabCount - 1;
    }
    else
    {
        nStartCol = nStartColFix = aRange.aStart.Col();
        nStartRow = nStartRowFix = aRange.aStart.Row();
        nStartTab = aRange.aStart.Tab();
        nEndCol = nEndColFix = aRange.aEnd.Col();
        nEndRow = nEndRowFix = aRange.aEnd.Row();
        nEndTab = aRange.aEnd.Tab();
    }
    SCTAB nTableStrNum = 1;
    for ( SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++ )
    {
        if ( !pDoc->IsVisible( nTab ) )
            continue;   // for
 
        if ( bAll )
        {
            if ( !GetDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ) )
                continue;   // for
 
            if ( nUsedTables > 1 )
            {
                aStrOut = aStrTable + " "  + OUString::number( nTableStrNum++ ) + ": ";
 
                OUT_HR();
 
                // Write anchor
                rStrm.WriteOString( "<A NAME=\"table" )
                   .WriteOString( OString::number(nTab) )
                   .WriteOString( "\">" );
                TAG_ON( OOO_STRING_SVTOOLS_HTML_head1 );
                OUT_STR( aStrOut );
                TAG_ON( OOO_STRING_SVTOOLS_HTML_emphasis );
 
                pDoc->GetName( nTab, aStr );
                OUT_STR( aStr );
 
                TAG_OFF( OOO_STRING_SVTOOLS_HTML_emphasis );
                TAG_OFF( OOO_STRING_SVTOOLS_HTML_head1 );
                rStrm.WriteOString( "</A>" ); OUT_LF();
            }
        }
        else
        {
            nStartCol = nStartColFix;
            nStartRow = nStartRowFix;
            nEndCol = nEndColFix;
            nEndRow = nEndRowFix;
            if ( !TrimDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ) )
                continue;   // for
        }
 
        // <TABLE ...>
        OStringBuffer aByteStrOut(OOO_STRING_SVTOOLS_HTML_table);
 
        bTabHasGraphics = bTabAlignedLeft = false;
        if ( bAll && pDrawLayer )
            PrepareGraphics( pDrawLayer, nTab, nStartCol, nStartRow,
                nEndCol, nEndRow );
 
        // more <TABLE ...>
        if ( bTabAlignedLeft )
        {
            aByteStrOut.append(" " OOO_STRING_SVTOOLS_HTML_O_align
                    "=\""
                    OOO_STRING_SVTOOLS_HTML_AL_left "\"");
        }
            // ALIGN=LEFT allow text and graphics to flow around
        // CELLSPACING
        aByteStrOut.append(" " OOO_STRING_SVTOOLS_HTML_O_cellspacing
                "=\"" +
                OString::number(nCellSpacing) + "\"");
 
        // BORDER=0, we do the styling of the cells in <TD>
        aByteStrOut.append(" " OOO_STRING_SVTOOLS_HTML_O_border "=\"0\"");
        IncIndent(1); TAG_ON_LF( aByteStrOut.makeStringAndClear() );
 
        // --- <COLGROUP> ----
        {
            SCCOL nCol = nStartCol;
            sal_Int32 nWidth = 0;
            sal_Int32 nSpan = 0;
            while( nCol <= nEndCol )
            {
                if( pDoc->ColHidden(nCol, nTab) )
                {
                    ++nCol;
                    continue;
                }
 
                if( nWidth != ToPixel( pDoc->GetColWidth( nCol, nTab ) ) )
                {
                    if( nSpan != 0 )
                    {
                        TAG_ON(lcl_getColGroupString(nSpan, nWidth));
                        TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_colgroup );
                    }
                    nWidth = ToPixel( pDoc->GetColWidth( nCol, nTab ) );
                    nSpan = 1;
                }
                else
                    nSpan++;
                nCol++;
            }
            if( nSpan )
            {
                TAG_ON(lcl_getColGroupString(nSpan, nWidth));
                TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_colgroup );
            }
        }
 
        // <TBODY> // Re-enable only when THEAD and TFOOT are exported
        // IncIndent(1); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_tbody );
        // At least old (3.x, 4.x?) Netscape doesn't follow <TABLE COLS=n> and
        // <COL WIDTH=x> specified, but needs a width at every column.
        bool bHasHiddenRows = pDoc->HasHiddenRows(nStartRow, nEndRow, nTab);
        // We need to cache sc::ColumnBlockPosition per each column.
        std::vector< sc::ColumnBlockPosition > blockPos( nEndCol - nStartCol + 1 );
        for( SCCOL i = nStartCol; i <= nEndCol; ++i )
            pDoc->InitColumnBlockPosition( blockPos[ i - nStartCol ], nTab, i );
        for ( SCROW nRow=nStartRow; nRow<=nEndRow; nRow++ )
        {
            if ( bHasHiddenRows && pDoc->RowHidden(nRow, nTab) )
            {
                nRow = pDoc->FirstVisibleRow(nRow+1, nEndRow, nTab);
                --nRow;
                continue;   // for
            }
 
            IncIndent(1); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_tablerow );
            bTableDataHeight = true;  // height at every first cell of each row
            for ( SCCOL nCol2=nStartCol; nCol2<=nEndCol; nCol2++ )
            {
                if ( pDoc->ColHidden(nCol2, nTab) )
                    continue;   // for
 
                if ( nCol2 == nEndCol )
                    IncIndent(-1);
                WriteCell( blockPos[ nCol2 - nStartCol ], nCol2, nRow, nTab );
                bTableDataHeight = false;
            }
 
            if ( nRow == nEndRow )
                IncIndent(-1);
            TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_tablerow );
        }
        // TODO: Uncomment later
        // IncIndent(-1); TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_tbody );
 
        IncIndent(-1); TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_table );
 
        if ( bTabHasGraphics && !mbSkipImages )
        {
            // the rest that is not in a cell
            size_t ListSize = aGraphList.size();
            for ( size_t i = 0; i < ListSize; ++i )
            {
                ScHTMLGraphEntry* pE = &aGraphList[ i ];
                if ( !pE->bWritten )
                    WriteGraphEntry( pE );
            }
            aGraphList.clear();
            if ( bTabAlignedLeft )
            {
                // clear <TABLE ALIGN=LEFT> with <BR CLEAR=LEFT>
                aByteStrOut.append(
                        OOO_STRING_SVTOOLS_HTML_linebreak
                        " "
                        OOO_STRING_SVTOOLS_HTML_O_clear "="
                        OOO_STRING_SVTOOLS_HTML_AL_left);
                TAG_ON_LF( aByteStrOut.makeStringAndClear() );
            }
        }
 
        if ( bAll )
            OUT_COMMENT( u"**************************************************************************" );
    }
}
 
void ScHTMLExport::WriteCell( sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow, SCTAB nTab )
{
    std::optional<Color> aColorScale;
    ScAddress aPos( nCol, nRow, nTab );
    ScRefCellValue aCell(*pDoc, aPos, rBlockPos);
    const ScPatternAttr* pAttr = pDoc->GetPattern( nCol, nRow, nTab );
    const SfxItemSet* pCondItemSet = pDoc->GetCondResult( nCol, nRow, nTab, &aCell );
    if (!pCondItemSet)
    {
        ScConditionalFormatList* pCondList = pDoc->GetCondFormList(nTab);
        const ScCondFormatItem& rCondItem = pAttr->GetItem(ATTR_CONDITIONAL);
        const ScCondFormatIndexes& rCondIndex = rCondItem.GetCondFormatData();
        if (rCondIndex.size() > 0)
        {
            ScConditionalFormat* pCondFmt = pCondList->GetFormat(rCondIndex[0]);
            if (pCondFmt)
            {
                const ScColorScaleFormat* pEntry = dynamic_cast<const ScColorScaleFormat*>(pCondFmt->GetEntry(0));
                if (pEntry)
                    aColorScale = pEntry->GetColor(aPos);
            }
        }
    }
 
    const ScMergeFlagAttr& rMergeFlagAttr = pAttr->GetItem( ATTR_MERGE_FLAG, pCondItemSet );
    if ( rMergeFlagAttr.IsOverlapped() )
        return ;
 
    ScHTMLGraphEntry* pGraphEntry = nullptr;
    if ( bTabHasGraphics && !mbSkipImages )
    {
        size_t ListSize = aGraphList.size();
        for ( size_t i = 0; i < ListSize; ++i )
        {
            ScHTMLGraphEntry* pE = &aGraphList[ i ];
            if ( pE->bInCell && pE->aRange.Contains( aPos ) )
            {
                if ( pE->aRange.aStart == aPos )
                {
                    pGraphEntry = pE;
                    break;  // for
                }
                else
                    return ; // Is a Col/RowSpan, Overlapped
            }
        }
    }
 
    sal_uInt32 nFormat = pAttr->GetNumberFormat( pFormatter );
    bool bValueData = aCell.hasNumeric();
    SvtScriptType nScriptType = SvtScriptType::NONE;
    if (!aCell.isEmpty())
        nScriptType = pDoc->GetScriptType(nCol, nRow, nTab, &aCell);
 
    if ( nScriptType == SvtScriptType::NONE )
        nScriptType = aHTMLStyle.nDefaultScriptType;
 
    OStringBuffer aStrTD(OOO_STRING_SVTOOLS_HTML_tabledata);
 
    // border of the cells
    const SvxBoxItem* pBorder = pDoc->GetAttr( nCol, nRow, nTab, ATTR_BORDER );
    if ( pBorder && (pBorder->GetTop() || pBorder->GetBottom() || pBorder->GetLeft() || pBorder->GetRight()) )
    {
        aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_style "=\"");
 
        bool bInsertSemicolon = false;
        aStrTD.append(BorderToStyle("top", pBorder->GetTop(),
            bInsertSemicolon));
        aStrTD.append(BorderToStyle("bottom", pBorder->GetBottom(),
            bInsertSemicolon));
        aStrTD.append(BorderToStyle("left", pBorder->GetLeft(),
            bInsertSemicolon));
        aStrTD.append(BorderToStyle("right", pBorder->GetRight(),
            bInsertSemicolon));
 
        aStrTD.append('"');
    }
 
    const char* pChar;
    sal_uInt16 nHeightPixel;
 
    const ScMergeAttr& rMergeAttr = pAttr->GetItem( ATTR_MERGE, pCondItemSet );
    if ( pGraphEntry || rMergeAttr.IsMerged() )
    {
        SCCOL nC, jC;
        SCROW nR;
        tools::Long v;
        if ( pGraphEntry )
            nC = std::max( SCCOL(pGraphEntry->aRange.aEnd.Col() - nCol + 1),
                           rMergeAttr.GetColMerge() );
        else
            nC = rMergeAttr.GetColMerge();
        if ( nC > 1 )
        {
            aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_colspan
                    "=" + OString::number(static_cast<sal_Int32>(nC)));
            nC = nC + nCol;
            for ( jC=nCol, v=0; jC<nC; jC++ )
                v += pDoc->GetColWidth( jC, nTab );
        }
 
        if ( pGraphEntry )
            nR = std::max( SCROW(pGraphEntry->aRange.aEnd.Row() - nRow + 1),
                           rMergeAttr.GetRowMerge() );
        else
            nR = rMergeAttr.GetRowMerge();
        if ( nR > 1 )
        {
            aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_rowspan
                "=" + OString::number(static_cast<sal_Int32>(nR)));
            nR += nRow;
            v = pDoc->GetRowHeight( nRow, nR-1, nTab );
            nHeightPixel = ToPixel( static_cast< sal_uInt16 >( v ) );
        }
        else
            nHeightPixel = ToPixel( pDoc->GetRowHeight( nRow, nTab ) );
    }
    else
        nHeightPixel = ToPixel( pDoc->GetRowHeight( nRow, nTab ) );
 
    if ( bTableDataHeight )
    {
        aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_height "=\"" +
                OString::number(nHeightPixel) + "\"");
    }
 
    const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>( pAttr->GetItem(
            ScGlobal::GetScriptedWhichID( nScriptType, ATTR_FONT),
            pCondItemSet) );
 
    const SvxFontHeightItem& rFontHeightItem = static_cast<const SvxFontHeightItem&>(
        pAttr->GetItem( ScGlobal::GetScriptedWhichID( nScriptType,
                    ATTR_FONT_HEIGHT), pCondItemSet) );
 
    const SvxWeightItem& rWeightItem = static_cast<const SvxWeightItem&>( pAttr->GetItem(
            ScGlobal::GetScriptedWhichID( nScriptType, ATTR_FONT_WEIGHT),
            pCondItemSet) );
 
    const SvxPostureItem& rPostureItem = static_cast<const SvxPostureItem&>(
        pAttr->GetItem( ScGlobal::GetScriptedWhichID( nScriptType,
                    ATTR_FONT_POSTURE), pCondItemSet) );
 
    const SvxUnderlineItem& rUnderlineItem =
        pAttr->GetItem( ATTR_FONT_UNDERLINE, pCondItemSet );
 
    const SvxCrossedOutItem& rCrossedOutItem =
        pAttr->GetItem( ATTR_FONT_CROSSEDOUT, pCondItemSet );
 
    const SvxColorItem& rColorItem = pAttr->GetItem(
            ATTR_FONT_COLOR, pCondItemSet );
 
    const SvxHorJustifyItem& rHorJustifyItem =
        pAttr->GetItem( ATTR_HOR_JUSTIFY, pCondItemSet );
 
    const SvxVerJustifyItem& rVerJustifyItem =
        pAttr->GetItem( ATTR_VER_JUSTIFY, pCondItemSet );
 
    const SvxBrushItem& rBrushItem = pAttr->GetItem(
            ATTR_BACKGROUND, pCondItemSet );
 
    Color aBgColor;
    if ( aColorScale )
        aBgColor = *aColorScale;
    else if ( rBrushItem.GetColor().GetAlpha() == 0 )
        aBgColor = aHTMLStyle.aBackgroundColor; // No unwanted background color
    else
        aBgColor = rBrushItem.GetColor();
 
    bool bBold          = ( WEIGHT_BOLD      <= rWeightItem.GetWeight() );
    bool bItalic        = ( ITALIC_NONE      != rPostureItem.GetPosture() );
    bool bUnderline     = ( LINESTYLE_NONE   != rUnderlineItem.GetLineStyle() );
    bool bCrossedOut    = ( STRIKEOUT_SINGLE <= rCrossedOutItem.GetStrikeout() );
    bool bSetFontColor  = ( COL_AUTO         != rColorItem.GetValue() );  // default is AUTO now
    bool bSetFontName   = ( aHTMLStyle.aFontFamilyName  != rFontItem.GetFamilyName() );
    sal_uInt16 nSetFontSizeNumber = 0;
    sal_uInt32 nFontHeight = rFontHeightItem.GetHeight();
    if ( nFontHeight != aHTMLStyle.nFontHeight )
    {
        nSetFontSizeNumber = GetFontSizeNumber( static_cast<sal_uInt16>(nFontHeight) );
        if ( nSetFontSizeNumber == aHTMLStyle.nFontSizeNumber )
            nSetFontSizeNumber = 0;   // no difference, don't set
    }
 
    bool bSetFont = (bSetFontColor || bSetFontName || nSetFontSizeNumber);
 
    //! TODO: we could entirely use CSS1 here instead, but that would exclude
    //! Netscape 3.0 and Netscape 4.x without JavaScript enabled.
    //! Do we want that?
 
    switch( rHorJustifyItem.GetValue() )
    {
        case SvxCellHorJustify::Standard:
            pChar = (bValueData ? OOO_STRING_SVTOOLS_HTML_AL_right : OOO_STRING_SVTOOLS_HTML_AL_left);
            break;
        case SvxCellHorJustify::Center:    pChar = OOO_STRING_SVTOOLS_HTML_AL_center;  break;
        case SvxCellHorJustify::Block:     pChar = OOO_STRING_SVTOOLS_HTML_AL_justify; break;
        case SvxCellHorJustify::Right:     pChar = OOO_STRING_SVTOOLS_HTML_AL_right;   break;
        case SvxCellHorJustify::Left:
        case SvxCellHorJustify::Repeat:
        default:                        pChar = OOO_STRING_SVTOOLS_HTML_AL_left;    break;
    }
 
    aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_align "=\"" +
        OString::Concat(pChar) + "\"");
 
    switch( rVerJustifyItem.GetValue() )
    {
        case SvxCellVerJustify::Top:       pChar = OOO_STRING_SVTOOLS_HTML_VA_top;     break;
        case SvxCellVerJustify::Center:    pChar = OOO_STRING_SVTOOLS_HTML_VA_middle;  break;
        case SvxCellVerJustify::Bottom:    pChar = OOO_STRING_SVTOOLS_HTML_VA_bottom;  break;
        case SvxCellVerJustify::Standard:
        default:                        pChar = nullptr;
    }
    if ( pChar )
    {
        aStrTD.append(OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_valign "=") + pChar);
    }
 
    if ( aHTMLStyle.aBackgroundColor != aBgColor )
    {
        aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_bgcolor "="
            + lcl_makeHTMLColorTriplet(aBgColor));
    }
 
    double fVal = 0.0;
    if ( bValueData )
    {
        switch (aCell.getType())
        {
            case CELLTYPE_VALUE:
                fVal = aCell.getDouble();
                if ( bCalcAsShown && fVal != 0.0 )
                    fVal = pDoc->RoundValueAsShown( fVal, nFormat );
                break;
            case CELLTYPE_FORMULA:
                fVal = aCell.getFormula()->GetValue();
                break;
            default:
                OSL_FAIL( "value data with unsupported cell type" );
        }
    }
 
    aStrTD.append(HTMLOutFuncs::CreateTableDataOptionsValNum(bValueData, fVal,
        nFormat, *pFormatter, &aNonConvertibleChars));
 
    std::optional<tools::JsonWriter> oJson;
    const SvNumberformat* pNumberFormat = nullptr;
    if (bValueData)
    {
        if (nFormat)
        {
            const SvNumberformat* pFormatEntry = pFormatter->GetEntry(nFormat);
            if (pFormatEntry)
            {
                OUString aNumStr = pFormatEntry->GetFormatstring();
                if (aNumStr == "BOOLEAN")
                {
                    // 4 is boolean.
                    oJson.emplace();
                    oJson->put("1", static_cast<sal_Int32>(4));
                    oJson->put("4", static_cast<sal_Int32>(fVal));
                }
                else
                {
                    // 3 is number.
                    oJson.emplace();
                    oJson->put("1", static_cast<sal_Int32>(3));
                    oJson->put("3", static_cast<sal_Int32>(fVal));
                    pNumberFormat = pFormatEntry;
                }
            }
        }
 
        if (aCell.getType() == CELLTYPE_FORMULA)
        {
            // If it's a formula, then also emit that, grammar is R1C1 reference style.
            OUString aFormula = aCell.getFormula()->GetFormula(
                    formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
            aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_DSformula "=\""
                    + HTMLOutFuncs::ConvertStringToHTML(aFormula) + "\"");
        }
    }
    else
    {
        // 2 is text.
        oJson.emplace();
        oJson->put("1", static_cast<sal_Int32>(2));
        oJson->put("2", pDoc->GetString(aPos));
    }
 
    if (oJson)
    {
        OUString aJsonString = OUString::fromUtf8(oJson->finishAndGetAsOString());
        aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_DSval "=\""
                      + HTMLOutFuncs::ConvertStringToHTML(aJsonString) + "\"");
    }
 
    if (pNumberFormat)
    {
        // 2 is a number format.
        oJson.emplace();
        oJson->put("1", static_cast<sal_Int32>(2));
        oJson->put("2", pNumberFormat->GetFormatstring());
        // The number format is for a number.
        oJson->put("3", static_cast<sal_Int32>(1));
        OUString aJsonString = OUString::fromUtf8(oJson->finishAndGetAsOString());
        aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_DSnum "=\""
                      + HTMLOutFuncs::ConvertStringToHTML(aJsonString) + "\"");
    }
 
    TAG_ON(aStrTD.makeStringAndClear());
 
    //write the note for this as the first thing in the tag
    ScPostIt* pNote = pDoc->HasNote(aPos) ? pDoc->GetNote(aPos) : nullptr;
    if (pNote)
    {
        //create the comment indicator
        OString aStr = OOO_STRING_SVTOOLS_HTML_anchor " "
            OOO_STRING_SVTOOLS_HTML_O_class "=\"comment-indicator\""_ostr;
        TAG_ON(aStr);
        TAG_OFF(OOO_STRING_SVTOOLS_HTML_anchor);
        OUT_LF();
 
        //create the element holding the contents
        //this is a bit naive, since it doesn't separate
        //lines into html breaklines yet
        TAG_ON(OOO_STRING_SVTOOLS_HTML_comment2);
        OUT_STR( pNote->GetText() );
        TAG_OFF(OOO_STRING_SVTOOLS_HTML_comment2);
        OUT_LF();
    }
 
    if ( bBold )        TAG_ON( OOO_STRING_SVTOOLS_HTML_bold );
    if ( bItalic )      TAG_ON( OOO_STRING_SVTOOLS_HTML_italic );
    if ( bUnderline )   TAG_ON( OOO_STRING_SVTOOLS_HTML_underline );
    if ( bCrossedOut )  TAG_ON( OOO_STRING_SVTOOLS_HTML_strikethrough );
 
    if ( bSetFont )
    {
        OStringBuffer aStr(OOO_STRING_SVTOOLS_HTML_font);
        if ( bSetFontName )
        {
            aStr.append(" " OOO_STRING_SVTOOLS_HTML_O_face "=\"");
 
            if (!rFontItem.GetFamilyName().isEmpty())
            {
                const OUString& rList = rFontItem.GetFamilyName();
                for (sal_Int32 nPos {0};;)
                {
                    OString aTmpStr = HTMLOutFuncs::ConvertStringToHTML(
                        o3tl::getToken( rList, 0, ';', nPos ),
                        &aNonConvertibleChars);
                    aStr.append(aTmpStr);
                    if (nPos<0)
                        break;
                    aStr.append(',');
                }
            }
 
            aStr.append('\"');
        }
        if ( nSetFontSizeNumber )
        {
            aStr.append(" " OOO_STRING_SVTOOLS_HTML_O_size "="
                + OString::number(static_cast<sal_Int32>(nSetFontSizeNumber)));
        }
        if ( bSetFontColor )
        {
            Color   aColor = rColorItem.GetValue();
 
            //  always export automatic text color as black
            if ( aColor == COL_AUTO )
                aColor = COL_BLACK;
 
            aStr.append(" " OOO_STRING_SVTOOLS_HTML_O_color "="
                + lcl_makeHTMLColorTriplet(aColor));
        }
        TAG_ON(aStr.makeStringAndClear());
    }
 
    OUString aURL;
    bool bWriteHyperLink(false);
    if (aCell.getType() == CELLTYPE_FORMULA)
    {
        ScFormulaCell* pFCell = aCell.getFormula();
        if (pFCell->IsHyperLinkCell())
        {
            OUString aCellText;
            pFCell->GetURLResult(aURL, aCellText);
            bWriteHyperLink = true;
        }
    }
 
    if (bWriteHyperLink)
    {
        OString aURLStr = HTMLOutFuncs::ConvertStringToHTML(aURL, &aNonConvertibleChars);
        OString aStr = OOO_STRING_SVTOOLS_HTML_anchor " " OOO_STRING_SVTOOLS_HTML_O_href "=\"" + aURLStr + "\"";
        TAG_ON(aStr);
    }
 
    OUString aStrOut;
    bool bFieldText = false;
 
    const Color* pColor;
    switch (aCell.getType())
    {
        case CELLTYPE_EDIT :
            bFieldText = WriteFieldText(aCell.getEditText());
            if ( bFieldText )
                break;
            [[fallthrough]];
        default:
            aStrOut = ScCellFormat::GetString(aCell, nFormat, &pColor, nullptr, *pDoc);
    }
 
    if ( !bFieldText )
    {
        if ( aStrOut.isEmpty() )
        {
            TAG_ON( OOO_STRING_SVTOOLS_HTML_linebreak ); // No completely empty line
        }
        else
        {
            sal_Int32 nPos = aStrOut.indexOf( '\n' );
            if ( nPos == -1 )
            {
                OUT_STR( aStrOut );
            }
            else
            {
                sal_Int32 nStartPos = 0;
                do
                {
                    OUString aSingleLine = aStrOut.copy( nStartPos, nPos - nStartPos );
                    OUT_STR( aSingleLine );
                    TAG_ON( OOO_STRING_SVTOOLS_HTML_linebreak );
                    nStartPos = nPos + 1;
                }
                while( ( nPos = aStrOut.indexOf( '\n', nStartPos ) ) != -1 );
                OUString aSingleLine = aStrOut.copy( nStartPos );
                OUT_STR( aSingleLine );
            }
        }
    }
    if ( pGraphEntry )
        WriteGraphEntry( pGraphEntry );
 
    if (bWriteHyperLink) { TAG_OFF(OOO_STRING_SVTOOLS_HTML_anchor); }
 
    if ( bSetFont )     TAG_OFF( OOO_STRING_SVTOOLS_HTML_font );
    if ( bCrossedOut )  TAG_OFF( OOO_STRING_SVTOOLS_HTML_strikethrough );
    if ( bUnderline )   TAG_OFF( OOO_STRING_SVTOOLS_HTML_underline );
    if ( bItalic )      TAG_OFF( OOO_STRING_SVTOOLS_HTML_italic );
    if ( bBold )        TAG_OFF( OOO_STRING_SVTOOLS_HTML_bold );
 
    TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_tabledata );
}
 
bool ScHTMLExport::WriteFieldText( const EditTextObject* pData )
{
    bool bFields = false;
    // text and anchor of URL fields, Doc-Engine is a ScFieldEditEngine
    EditEngine& rEngine = pDoc->GetEditEngine();
    rEngine.SetText( *pData );
    sal_Int32 nParas = rEngine.GetParagraphCount();
    if ( nParas )
    {
        ESelection aSel( 0, 0, nParas-1, rEngine.GetTextLen( nParas-1 ) );
        SfxItemSet aSet( rEngine.GetAttribs( aSel ) );
        SfxItemState eFieldState = aSet.GetItemState( EE_FEATURE_FIELD, false );
        if ( eFieldState == SfxItemState::INVALID || eFieldState == SfxItemState::SET )
            bFields = true;
    }
    if ( bFields )
    {
        bool bOldUpdateMode = rEngine.SetUpdateLayout( true );      // no portions if not formatted
        for ( sal_Int32 nPar=0; nPar < nParas; nPar++ )
        {
            if ( nPar > 0 )
                TAG_ON( OOO_STRING_SVTOOLS_HTML_linebreak );
            std::vector<sal_Int32> aPortions;
            rEngine.GetPortions( nPar, aPortions );
            sal_Int32 nStart = 0;
            for ( const sal_Int32 nEnd : aPortions )
            {
                ESelection aSel( nPar, nStart, nPar, nEnd );
                bool bUrl = false;
                // fields are single characters
                if ( nEnd == nStart+1 )
                {
                    SfxItemSet aSet = rEngine.GetAttribs( aSel );
                    if ( const SvxFieldItem* pFieldItem = aSet.GetItemIfSet( EE_FEATURE_FIELD, false ) )
                    {
                        const SvxFieldData* pField = pFieldItem->GetField();
                        if (const SvxURLField* pURLField = dynamic_cast<const SvxURLField*>(pField))
                        {
                            bUrl = true;
                            rStrm.WriteChar( '<' ).WriteOString( OOO_STRING_SVTOOLS_HTML_anchor ).WriteChar( ' ' ).WriteOString( OOO_STRING_SVTOOLS_HTML_O_href ).WriteOString( "=\"" );
                            OUT_STR( pURLField->GetURL() );
                            rStrm.WriteOString( "\">" );
                            OUT_STR( pURLField->GetRepresentation() );
                            rStrm.WriteOString( "</" ).WriteOString( OOO_STRING_SVTOOLS_HTML_anchor ).WriteChar( '>' );
                        }
                    }
                }
                if ( !bUrl )
                    OUT_STR( rEngine.GetText( aSel ) );
                nStart = nEnd;
            }
        }
        rEngine.SetUpdateLayout( bOldUpdateMode );
    }
    return bFields;
}
 
void ScHTMLExport::CopyLocalFileToINet( OUString& rFileNm,
        std::u16string_view rTargetNm )
{
    INetURLObject aFileUrl, aTargetUrl;
    aFileUrl.SetSmartURL( rFileNm );
    aTargetUrl.SetSmartURL( rTargetNm );
    if (!(INetProtocol::File == aFileUrl.GetProtocol()
            && (INetProtocol::Http == aTargetUrl.GetProtocol()
                || INetProtocol::Https == aTargetUrl.GetProtocol()
                || INetProtocol::VndSunStarWebdav == aTargetUrl.GetProtocol()
                || INetProtocol::Smb == aTargetUrl.GetProtocol()
                || INetProtocol::Sftp == aTargetUrl.GetProtocol()
                || INetProtocol::Cmis == aTargetUrl.GetProtocol())))
    {
        return;
    }
 
    if( pFileNameMap )
    {
        // Did we already move the file?
        std::map<OUString, OUString>::iterator it = pFileNameMap->find( rFileNm );
        if( it != pFileNameMap->end() )
        {
            rFileNm = it->second;
            return;
        }
    }
    else
    {
        pFileNameMap.reset( new std::map<OUString, OUString> );
    }
 
    bool bRet = false;
    SvFileStream aTmp( aFileUrl.PathToFileName(), StreamMode::READ );
 
    OUString aSrc = rFileNm;
    OUString aDest = aTargetUrl.GetPartBeforeLastName() + aFileUrl.GetLastName();
 
    SfxMedium aMedium( aDest, StreamMode::WRITE | StreamMode::SHARE_DENYNONE );
 
    {
        SvFileStream aCpy( aMedium.GetPhysicalName(), StreamMode::WRITE );
        aCpy.WriteStream( aTmp );
    }
 
    // Take over
    aMedium.Close();
    aMedium.Commit();
 
    bRet = ERRCODE_NONE == aMedium.GetErrorIgnoreWarning();
 
    if( bRet )
    {
        pFileNameMap->insert( std::make_pair( aSrc, aDest ) );
        rFileNm = aDest;
    }
}
 
void ScHTMLExport::IncIndent( short nVal )
{
    sIndent[nIndent] = '\t';
    nIndent = nIndent + nVal;
    if ( nIndent < 0 )
        nIndent = 0;
    else if ( nIndent > nIndentMax )
        nIndent = nIndentMax;
    sIndent[nIndent] = 0;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'PageDefaults' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.