/* -*- 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 <config_java.h>

#include <hintids.hxx>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <svl/urihelper.hxx>
#include <vcl/svapp.hxx>
#include <sfx2/frmhtml.hxx>
#include <sfx2/frmhtmlw.hxx>
#include <sfx2/frmdescr.hxx>
#include <sot/storage.hxx>
#include <svx/xoutbmp.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/lrspitem.hxx>
#include <svtools/htmlout.hxx>
#include <svtools/htmlkywd.hxx>
#include <svtools/htmltokn.h>
#include <comphelper/diagnose_ex.hxx>
#include <IDocumentContentOperations.hxx>
#include <SwAppletImpl.hxx>
#include <fmtornt.hxx>
#include <fmtfsize.hxx>
#include <fmtsrnd.hxx>
#include <fmtanchr.hxx>
#include <fmtcntnt.hxx>
#include <frmfmt.hxx>

#include <svl/ownlist.hxx>
#include <unotools/mediadescriptor.hxx>
#include <unotools/streamwrap.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <swerror.h>
#include <ndole.hxx>
#include <docsh.hxx>
#include "swhtml.hxx"
#include "wrthtml.hxx"
#include "htmlfly.hxx"
#include "swcss1.hxx"
#include "htmlreqifreader.hxx"
#include <unoframe.hxx>
#include <com/sun/star/embed/XClassifiedObject.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/io/XActiveDataStreamer.hpp>
#include <com/sun/star/embed/XEmbedPersist2.hpp>
#include <com/sun/star/lang/XInitialization.hpp>

#include <comphelper/embeddedobjectcontainer.hxx>
#include <comphelper/classids.hxx>
#include <rtl/uri.hxx>
#include <comphelper/storagehelper.hxx>
#include <vcl/graphicfilter.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <comphelper/propertysequence.hxx>
#include <filter/msfilter/msoleexp.hxx>
#include <comphelper/fileurl.hxx>
#include <o3tl/safeint.hxx>
#include <osl/file.hxx>
#include <comphelper/propertyvalue.hxx>
#include <svtools/HtmlWriter.hxx>

using namespace com::sun::star;


#define HTML_DFLT_EMBED_WIDTH (o3tl::toTwips(125, o3tl::Length::mm10))
#define HTML_DFLT_EMBED_HEIGHT (o3tl::toTwips(125, o3tl::Length::mm10))

#define HTML_DFLT_APPLET_WIDTH (o3tl::toTwips(125, o3tl::Length::mm10))
#define HTML_DFLT_APPLET_HEIGHT (o3tl::toTwips(125, o3tl::Length::mm10))


const HtmlFrmOpts HTML_FRMOPTS_EMBED_ALL      =
    HtmlFrmOpts::Alt |
    HtmlFrmOpts::Size |
    HtmlFrmOpts::Name;
const HtmlFrmOpts HTML_FRMOPTS_EMBED_CNTNR    =
    HTML_FRMOPTS_EMBED_ALL |
    HtmlFrmOpts::AbsSize;
const HtmlFrmOpts HTML_FRMOPTS_EMBED          =
    HTML_FRMOPTS_EMBED_ALL |
    HtmlFrmOpts::Align |
    HtmlFrmOpts::Space |
    HtmlFrmOpts::BrClear |
    HtmlFrmOpts::Name;
const HtmlFrmOpts HTML_FRMOPTS_HIDDEN_EMBED   =
    HtmlFrmOpts::Alt |
    HtmlFrmOpts::Name;

const HtmlFrmOpts HTML_FRMOPTS_APPLET_ALL     =
    HtmlFrmOpts::Alt |
    HtmlFrmOpts::Size;
const HtmlFrmOpts HTML_FRMOPTS_APPLET_CNTNR   =
    HTML_FRMOPTS_APPLET_ALL |
    HtmlFrmOpts::AbsSize;
const HtmlFrmOpts HTML_FRMOPTS_APPLET         =
    HTML_FRMOPTS_APPLET_ALL |
    HtmlFrmOpts::Align |
    HtmlFrmOpts::Space |
    HtmlFrmOpts::BrClear;

const HtmlFrmOpts HTML_FRMOPTS_IFRAME_ALL     =
    HtmlFrmOpts::Alt |
    HtmlFrmOpts::Size;
const HtmlFrmOpts HTML_FRMOPTS_IFRAME_CNTNR   =
    HTML_FRMOPTS_IFRAME_ALL |
    HtmlFrmOpts::AbsSize;
const HtmlFrmOpts HTML_FRMOPTS_IFRAME         =
    HTML_FRMOPTS_IFRAME_ALL |
    HtmlFrmOpts::Align |
    HtmlFrmOpts::Space |
    HtmlFrmOpts::Border |
    HtmlFrmOpts::BrClear;

const HtmlFrmOpts HTML_FRMOPTS_OLE_CSS1       =
    HtmlFrmOpts::SAlign |
    HtmlFrmOpts::SSpace;

namespace
{
/**
 * Calculates a filename for an image, provided the HTML file name, the image
 * itself and a wanted extension.
 */
OUString lcl_CalculateFileName(const OUString* pOrigFileName, const Graphic& rGraphic,
                               std::u16string_view rExtension)
{
    OUString aFileName;

    if (pOrigFileName)
        aFileName = *pOrigFileName;
    INetURLObject aURL(aFileName);
    OUString aName = aURL.getBase() + "_" +
        aURL.getExtension() + "_" +
        OUString::number(rGraphic.GetChecksum(), 16);
    aURL.setBase(aName);
    aURL.setExtension(rExtension);
    aFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);

    return aFileName;
}
}

void SwHTMLParser::SetFixSize( const Size& rPixSize,
                               const Size& rTwipDfltSize,
                               bool bPercentWidth, bool bPercentHeight,
                               SvxCSS1PropertyInfo const & rCSS1PropInfo,
                               SfxItemSet& rFlyItemSet )
{
    // convert absolute size values into Twip
    sal_uInt8 nPercentWidth = 0, nPercentHeight = 0;
    Size aTwipSz( bPercentWidth || USHRT_MAX==rPixSize.Width() ? 0 : rPixSize.Width(),
                  bPercentHeight || USHRT_MAX==rPixSize.Height() ? 0 : rPixSize.Height() );
    if( aTwipSz.Width() || aTwipSz.Height() )
    {
        aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip);
    }

    // process width
    if( SVX_CSS1_LTYPE_PERCENTAGE == rCSS1PropInfo.m_eWidthType )
    {
        nPercentWidth = static_cast<sal_uInt8>(rCSS1PropInfo.m_nWidth);
        aTwipSz.setWidth( rTwipDfltSize.Width() );
    }
    else if( SVX_CSS1_LTYPE_TWIP== rCSS1PropInfo.m_eWidthType )
    {
        aTwipSz.setWidth( rCSS1PropInfo.m_nWidth );
    }
    else if( bPercentWidth && rPixSize.Width() )
    {
        nPercentWidth = static_cast<sal_uInt8>(rPixSize.Width());
        if (nPercentWidth > 100 && nPercentWidth != SwFormatFrameSize::SYNCED)
            nPercentWidth = 100;

        aTwipSz.setWidth( rTwipDfltSize.Width() );
    }
    else if( USHRT_MAX==rPixSize.Width() )
    {
        aTwipSz.setWidth( rTwipDfltSize.Width() );
    }
    if( aTwipSz.Width() < MINFLY )
    {
        aTwipSz.setWidth( MINFLY );
    }

    // process height
    if( SVX_CSS1_LTYPE_PERCENTAGE == rCSS1PropInfo.m_eHeightType )
    {
        nPercentHeight = static_cast<sal_uInt8>(rCSS1PropInfo.m_nHeight);
        aTwipSz.setHeight( rTwipDfltSize.Height() );
    }
    else if( SVX_CSS1_LTYPE_TWIP== rCSS1PropInfo.m_eHeightType )
    {
        aTwipSz.setHeight( rCSS1PropInfo.m_nHeight );
    }
    else if( bPercentHeight && rPixSize.Height() )
    {
        nPercentHeight = static_cast<sal_uInt8>(rPixSize.Height());
        if (nPercentHeight > 100 && nPercentHeight != SwFormatFrameSize::SYNCED)
            nPercentHeight = 100;

        aTwipSz.setHeight( rTwipDfltSize.Height() );
    }
    else if( USHRT_MAX==rPixSize.Height() )
    {
        aTwipSz.setHeight( rTwipDfltSize.Height() );
    }
    if( aTwipSz.Height() < MINFLY )
    {
        aTwipSz.setHeight( MINFLY );
    }

    // set size
    SwFormatFrameSize aFrameSize( SwFrameSize::Fixed, aTwipSz.Width(), aTwipSz.Height() );
    aFrameSize.SetWidthPercent( nPercentWidth );
    aFrameSize.SetHeightPercent( nPercentHeight );
    rFlyItemSet.Put( aFrameSize );
}

void SwHTMLParser::SetSpace( const Size& rPixSpace,
                             SfxItemSet& rCSS1ItemSet,
                             SvxCSS1PropertyInfo& rCSS1PropInfo,
                             SfxItemSet& rFlyItemSet )
{
    sal_Int32 nLeftSpace = 0, nRightSpace = 0;
    sal_uInt16 nUpperSpace = 0, nLowerSpace = 0;
    if( rPixSpace.Width() || rPixSpace.Height() )
    {
        nLeftSpace = nRightSpace = o3tl::convert(rPixSpace.Width(), o3tl::Length::px, o3tl::Length::twip);
        nUpperSpace = nLowerSpace = o3tl::convert(rPixSpace.Height(), o3tl::Length::px, o3tl::Length::twip);
    }

    // set left/right margin
    // note: parser never creates SvxLeftMarginItem! must be converted
    if (const SvxTextLeftMarginItem *const pLeft = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT))
    {
        if( rCSS1PropInfo.m_bLeftMargin )
        {
            // should be SvxLeftMarginItem... "cast" it
            nLeftSpace = pLeft->ResolveTextLeft({});
            rCSS1PropInfo.m_bLeftMargin = false;
        }
        rCSS1ItemSet.ClearItem(RES_MARGIN_TEXTLEFT);
    }
    if (const SvxRightMarginItem *const pRight = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_RIGHT))
    {
        if( rCSS1PropInfo.m_bRightMargin )
        {
            nRightSpace = pRight->ResolveRight({});
            rCSS1PropInfo.m_bRightMargin = false;
        }
        rCSS1ItemSet.ClearItem(RES_MARGIN_RIGHT);
    }
    if( nLeftSpace > 0 || nRightSpace > 0 )
    {
        SvxLRSpaceItem aLRItem( RES_LR_SPACE );
        aLRItem.SetLeft(SvxIndentValue::twips(std::max<sal_Int32>(nLeftSpace, 0)));
        aLRItem.SetRight(SvxIndentValue::twips(std::max<sal_Int32>(nRightSpace, 0)));
        rFlyItemSet.Put( aLRItem );
        if( nLeftSpace )
        {
            const SwFormatHoriOrient& rHoriOri =
                rFlyItemSet.Get( RES_HORI_ORIENT );
            if( text::HoriOrientation::NONE == rHoriOri.GetHoriOrient() )
            {
                SwFormatHoriOrient aHoriOri( rHoriOri );
                aHoriOri.SetPos( aHoriOri.GetPos() + nLeftSpace );
                rFlyItemSet.Put( aHoriOri );
            }
        }
    }

    // set top/bottom margin
    if( const SvxULSpaceItem *pULItem = rCSS1ItemSet.GetItemIfSet( RES_UL_SPACE ) )
    {
        // if applicable remove the first line indent
        if( rCSS1PropInfo.m_bTopMargin )
        {
            nUpperSpace = pULItem->GetUpper();
            rCSS1PropInfo.m_bTopMargin = false;
        }
        if( rCSS1PropInfo.m_bBottomMargin )
        {
            nLowerSpace = pULItem->GetLower();
            rCSS1PropInfo.m_bBottomMargin = false;
        }
        rCSS1ItemSet.ClearItem( RES_UL_SPACE );
    }
    if( !(nUpperSpace || nLowerSpace) )
        return;

    SvxULSpaceItem aULItem( RES_UL_SPACE );
    aULItem.SetUpper( nUpperSpace );
    aULItem.SetLower( nLowerSpace );
    rFlyItemSet.Put( aULItem );
    if( nUpperSpace )
    {
        const SwFormatVertOrient& rVertOri =
            rFlyItemSet.Get( RES_VERT_ORIENT );
        if( text::VertOrientation::NONE == rVertOri.GetVertOrient() )
        {
            SwFormatVertOrient aVertOri( rVertOri );
            aVertOri.SetPos( aVertOri.GetPos() + nUpperSpace );
            rFlyItemSet.Put( aVertOri );
        }
    }
}

OUString SwHTMLParser::StripQueryFromPath(std::u16string_view rBase, const OUString& rPath)
{
    if (!comphelper::isFileUrl(rBase))
        return rPath;

    sal_Int32 nIndex = rPath.indexOf('?');

    if (nIndex != -1)
        return rPath.copy(0, nIndex);

    return rPath;
}

bool SwHTMLParser::InsertEmbed()
{
    OUString aURL, aType, aName, aAlt, aId, aStyle, aClass;
    OUString aData;
    Size aSize( USHRT_MAX, USHRT_MAX );
    Size aSpace( USHRT_MAX, USHRT_MAX );
    bool bPercentWidth = false, bPercentHeight = false, bHidden = false;
    sal_Int16 eVertOri = text::VertOrientation::NONE;
    sal_Int16 eHoriOri = text::HoriOrientation::NONE;
    SvCommandList aCmdLst;
    const HTMLOptions& rHTMLOptions = GetOptions();

    // The options are read forwards, because the plug-ins expect them in this
    // order. Still only the first value of an option may be regarded.
    for (const auto & rOption : rHTMLOptions)
    {
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::NAME:
            aName = rOption.GetString();
            break;
        case HtmlOptionId::SRC:
            if( aURL.isEmpty() )
                aURL = rOption.GetString();
            break;
        case HtmlOptionId::ALT:
            aAlt = rOption.GetString();
            break;
        case HtmlOptionId::TYPE:
            if( aType.isEmpty() )
                aType = rOption.GetString();
            break;
        case HtmlOptionId::ALIGN:
            if( eVertOri==text::VertOrientation::NONE && eHoriOri==text::HoriOrientation::NONE )
            {
                eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri );
                eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri );
            }
            break;
        case HtmlOptionId::WIDTH:
            if( USHRT_MAX==aSize.Width() )
            {
                bPercentWidth = (rOption.GetString().indexOf('%') != -1);
                aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            }
            break;
        case HtmlOptionId::HEIGHT:
            if( USHRT_MAX==aSize.Height() )
            {
                bPercentHeight = (rOption.GetString().indexOf('%') != -1);
                aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            }
            break;
        case HtmlOptionId::HSPACE:
            if( USHRT_MAX==aSpace.Width() )
                aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::VSPACE:
            if( USHRT_MAX==aSpace.Height() )
                aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::DATA:
            if (m_bXHTML && aURL.isEmpty())
                aData = rOption.GetString();
            break;
        case HtmlOptionId::UNKNOWN:
            if (rOption.GetTokenString().equalsIgnoreAsciiCase(
                        OOO_STRING_SW_HTML_O_Hidden))
            {
                bHidden = !rOption.GetString().equalsIgnoreAsciiCase(
                                "FALSE");
            }
            break;
        default: break;
        }

        // All parameters are passed to the plug-in.
        aCmdLst.Append( rOption.GetTokenString(), rOption.GetString() );
    }

    static const std::set<std::u16string_view> vAllowlist = {
        u"image/png",
        u"image/gif",
        u"image/x-MS-bmp",
        u"image/jpeg",
        u"image/x-wmf",
        u"image/svg+xml",
        u"image/tiff",
        u"image/x-emf",
        u"image/bmp",
        u"image/tif",
        u"image/wmf",
    };

    if (vAllowlist.find(aType) != vAllowlist.end() && m_aEmbeds.empty())
    {
        // Toplevel <object> for an image format -> that's an image, not an OLE object.
        m_aEmbeds.push(nullptr);
        return false;
    }

    SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
    SvxCSS1PropertyInfo aPropInfo;
    if( HasStyleOptions( aStyle, aId, aClass ) )
        (void)ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo );

    // Convert the default values (except height/width, which is done by SetFrameSize())
    if( eVertOri==text::VertOrientation::NONE && eHoriOri==text::HoriOrientation::NONE )
        eVertOri = text::VertOrientation::TOP;
    if( USHRT_MAX==aSpace.Width() )
        aSpace.setWidth( 0 );
    if( USHRT_MAX==aSpace.Height() )
        aSpace.setHeight( 0 );
    if( bHidden )
    {
        // Size (0,0) will be changed to (MINFLY, MINFLY) in SetFrameSize()
        aSize.setWidth( 0 ); aSize.setHeight( 0 );
        aSpace.setWidth( 0 ); aSpace.setHeight( 0 );
        bPercentWidth = bPercentHeight = false;
    }

    // prepare the URL
    INetURLObject aURLObj;
    bool bHasURL = !aURL.isEmpty() &&
                   aURLObj.SetURL(
                       URIHelper::SmartRel2Abs(
                           INetURLObject(m_sBaseURL), aURL,
                           URIHelper::GetMaybeFileHdl()) );
    bool bHasData = !aData.isEmpty();

    try
    {
        // Strip query and everything after that for file:// URLs, browsers do
        // the same.
        aURLObj.SetURL(rtl::Uri::convertRelToAbs(
            m_sBaseURL, SwHTMLParser::StripQueryFromPath(m_sBaseURL, aData)));
    }
    catch (const rtl::MalformedUriException& /*rException*/)
    {
        bHasData = false;
    }

    // do not insert plugin if it has neither URL nor type
    bool bHasType = !aType.isEmpty();
    if( !bHasURL && !bHasType && !bHasData )
        return true;

    if (!m_aEmbeds.empty())
    {
        // Nested XHTML <object> element: points to replacement graphic.
        SwOLENode* pOLENode = m_aEmbeds.top();
        if (!pOLENode)
        {
            // <object> is mapped to an image -> ignore replacement graphic.
            return true;
        }

        svt::EmbeddedObjectRef& rObj = pOLENode->GetOLEObj().GetObject();
        Graphic aGraphic;
        if (GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aURLObj) != ERRCODE_NONE)
            return true;

        rObj.SetGraphic(aGraphic, aType);

        // Set the size of the OLE frame to the size of the graphic.
        SwFrameFormat* pFormat = pOLENode->GetFlyFormat();
        if (!pFormat)
            return true;
        SwAttrSet aAttrSet(pFormat->GetAttrSet());
        aAttrSet.ClearItem(RES_CNTNT);
        Size aDefaultTwipSize(o3tl::convert(aGraphic.GetSizePixel(), o3tl::Length::px, o3tl::Length::twip));

        if (aSize.Width() == USHRT_MAX && bPercentHeight)
        {
            // Height is relative, width is not set: keep aspect ratio.
            aSize.setWidth(SwFormatFrameSize::SYNCED);
            bPercentWidth = true;
        }
        if (aSize.Height() == USHRT_MAX && bPercentWidth)
        {
            // Width is relative, height is not set: keep aspect ratio.
            aSize.setHeight(SwFormatFrameSize::SYNCED);
            bPercentHeight = true;
        }

        SetFixSize(aSize, aDefaultTwipSize, bPercentWidth, bPercentHeight, aPropInfo, aAttrSet);
        pOLENode->GetDoc().SetFlyFrameAttr(*pFormat, aAttrSet);
        return true;
    }

    // create the plug-in
    comphelper::EmbeddedObjectContainer aCnt;
    OUString aObjName;
    uno::Reference < embed::XEmbeddedObject > xObj;
    if (!bHasData)
    {
        xObj = aCnt.CreateEmbeddedObject( SvGlobalName( SO3_PLUGIN_CLASSID ).GetByteSequence(), aObjName );
        if ( svt::EmbeddedObjectRef::TryRunningState( xObj ) )
        {
            uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY );
            if ( xSet.is() )
            {
                if( bHasURL )
                    xSet->setPropertyValue(u"PluginURL"_ustr, uno::Any( aURL ) );
                if( bHasType )
                    xSet->setPropertyValue(u"PluginMimeType"_ustr, uno::Any( aType ) );

                uno::Sequence < beans::PropertyValue > aProps;
                aCmdLst.FillSequence( aProps );
                xSet->setPropertyValue(u"PluginCommands"_ustr, uno::Any( aProps ) );

            }
        }
    }
    else if (SwDocShell* pDocSh = m_xDoc->GetDocShell())
    {
        // Has non-empty data attribute in XHTML: map that to an OLE object.
        uno::Reference<embed::XStorage> xStorage = pDocSh->GetStorage();
        aCnt.SwitchPersistence(xStorage);
        aObjName = aCnt.CreateUniqueObjectName();
        {
            OUString aEmbedURL = aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
            SvFileStream aFileStream(aEmbedURL, StreamMode::READ);
            uno::Reference<io::XInputStream> xInStream;
            SvMemoryStream aMemoryStream;

            // Allow any MIME type that starts with magic, unless a set of allowed types are
            // specified.
            auto it = m_aAllowedRTFOLEMimeTypes.find(aType);
            if (m_aAllowedRTFOLEMimeTypes.empty() || it != m_aAllowedRTFOLEMimeTypes.end())
            {
                OString aMagic("{\\object"_ostr);
                OString aHeader(read_uInt8s_ToOString(aFileStream, aMagic.getLength()));
                aFileStream.Seek(0);
                if (aHeader == aMagic)
                {
                    // OLE2 wrapped in RTF: either own format or real OLE2 embedding.
                    bool bOwnFormat = false;
                    if (SwReqIfReader::ExtractOleFromRtf(aFileStream, aMemoryStream, bOwnFormat))
                    {
                        xInStream.set(new utl::OStreamWrapper(aMemoryStream));
                    }

                    if (bOwnFormat)
                    {
                        uno::Sequence<beans::PropertyValue> aMedium = comphelper::InitPropertySequence(
                            { { "InputStream", uno::Any(xInStream) },
                              { "URL", uno::Any(u"private:stream"_ustr) },
                              { "DocumentBaseURL", uno::Any(m_sBaseURL) } });
                        xObj = aCnt.InsertEmbeddedObject(aMedium, aName, &m_sBaseURL);
                    }
                    else
                    {
                        // The type is now an OLE2 container, not the original XHTML type.
                        aType = "application/vnd.sun.star.oleobject";
                    }
                }
            }

            if (!xInStream.is())
            {
                // Object data is neither OLE2 in RTF, nor an image. Then map this to an URL that
                // will be set on the inner image.
                m_aEmbedURL = aEmbedURL;
                // Signal success, so the outer object won't fall back to the image handler.
                return true;
            }

            if (!xObj.is())
            {
                uno::Reference<io::XStream> xOutStream
                    = xStorage->openStreamElement(aObjName, embed::ElementModes::READWRITE);
                if (aFileStream.IsOpen())
                    comphelper::OStorageHelper::CopyInputToOutput(xInStream,
                                                                  xOutStream->getOutputStream());

                if (!aType.isEmpty())
                {
                    // Set media type of the native data.
                    uno::Reference<beans::XPropertySet> xOutStreamProps(xOutStream, uno::UNO_QUERY);
                    if (xOutStreamProps.is())
                        xOutStreamProps->setPropertyValue(u"MediaType"_ustr, uno::Any(aType));
                }
            }
            xObj = aCnt.GetEmbeddedObject(aObjName);
        }
    }

    SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() );
    if( !IsNewDoc() )
        Reader::ResetFrameFormatAttrs( aFrameSet );

    // set the anchor
    if( !bHidden )
    {
        SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, aFrameSet );
    }
    else
    {
        SwFormatAnchor aAnchor( RndStdIds::FLY_AT_PARA );
        aAnchor.SetAnchor( m_pPam->GetPoint() );
        aFrameSet.Put( aAnchor );
        aFrameSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::LEFT, text::RelOrientation::FRAME) );
        aFrameSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH ) );
        aFrameSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::PRINT_AREA ) );
    }

    // and the size of the frame
    Size aDfltSz( HTML_DFLT_EMBED_WIDTH, HTML_DFLT_EMBED_HEIGHT );
    SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, aFrameSet );
    SetSpace( aSpace, aItemSet, aPropInfo, aFrameSet );

    // and insert into the document
    uno::Reference<lang::XInitialization> xObjInitialization(xObj, uno::UNO_QUERY);
    if (xObjInitialization.is())
    {
        // Request that the native data of the embedded object is not modified
        // during parsing.
        uno::Sequence<beans::PropertyValue> aValues{ comphelper::makePropertyValue(u"StreamReadOnly"_ustr,
                                                                                   true) };
        uno::Sequence<uno::Any> aArguments{ uno::Any(aValues) };
        xObjInitialization->initialize(aArguments);
    }

    SanitizeAnchor(aFrameSet);

    SwFrameFormat* pFlyFormat =
        m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam,
                ::svt::EmbeddedObjectRef(xObj, embed::Aspects::MSOLE_CONTENT),
                &aFrameSet);
    if (xObjInitialization.is())
    {
        uno::Sequence<beans::PropertyValue> aValues{ comphelper::makePropertyValue(u"StreamReadOnly"_ustr,
                                                                                   false) };
        uno::Sequence<uno::Any> aArguments{ uno::Any(aValues) };
        xObjInitialization->initialize(aArguments);
    }

    // set name at FrameFormat
    if( !aName.isEmpty() )
        pFlyFormat->SetFormatName( UIName(aName) );

    // set the alternative text
    SwNoTextNode *pNoTextNd =
        m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx()
                          ->GetIndex()+1 ]->GetNoTextNode();
    pNoTextNd->SetTitle( aAlt );

    // if applicable create frames and register auto-bound frames
    if( !bHidden )
    {
        // HIDDEN plug-ins should stay paragraph-bound. Since RegisterFlyFrame()
        // will change paragraph-bound frames with wrap-through into a
        // character-bound frame, here we must create the frames by hand.
        RegisterFlyFrame( pFlyFormat );
    }

    if (!bHasData)
        return true;

    SwOLENode* pOLENode = pNoTextNd->GetOLENode();
    if (!pOLENode)
        return true;

    m_aEmbeds.push(pOLENode);

    return true;
}

#if HAVE_FEATURE_JAVA
void SwHTMLParser::NewObject()
{
    OUString aClassID;
    OUString aStandBy, aId, aStyle, aClass;
    Size aSize( USHRT_MAX, USHRT_MAX );
    Size aSpace( 0, 0 );
    sal_Int16 eVertOri = text::VertOrientation::TOP;
    sal_Int16 eHoriOri = text::HoriOrientation::NONE;

    bool bPercentWidth = false, bPercentHeight = false,
             bDeclare = false;
    // create a new Command list
    m_pAppletImpl.reset(new SwApplet_Impl( m_xDoc->GetAttrPool() ));

    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::DECLARE:
            bDeclare = true;
            break;
        case HtmlOptionId::CLASSID:
            aClassID = rOption.GetString();
            break;
        case HtmlOptionId::CODEBASE:
            break;
        case HtmlOptionId::DATA:
            break;
        case HtmlOptionId::TYPE:
            break;
        case HtmlOptionId::CODETYPE:
            break;
        case HtmlOptionId::ARCHIVE:
        case HtmlOptionId::UNKNOWN:
            break;
        case HtmlOptionId::STANDBY:
            aStandBy = rOption.GetString();
            break;
        case HtmlOptionId::WIDTH:
            bPercentWidth = (rOption.GetString().indexOf('%') != -1);
            aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::HEIGHT:
            bPercentHeight = (rOption.GetString().indexOf('%') != -1);
            aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::ALIGN:
            eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri );
            eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri );
            break;
        case HtmlOptionId::USEMAP:
            break;
        case HtmlOptionId::NAME:
            break;
        case HtmlOptionId::HSPACE:
            aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::VSPACE:
            aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::BORDER:
            break;

        case HtmlOptionId::SDONCLICK:
        case HtmlOptionId::ONCLICK:
        case HtmlOptionId::SDONMOUSEOVER:
        case HtmlOptionId::ONMOUSEOVER:
        case HtmlOptionId::SDONMOUSEOUT:
        case HtmlOptionId::ONMOUSEOUT:
            break;
        default: break;
        }
        // All parameters are passed to the applet.
        m_pAppletImpl->AppendParam( rOption.GetTokenString(),
                                  rOption.GetString() );

    }

    // Objects that are declared only are not evaluated. Moreover, only
    // Java applets are supported.
    bool bIsApplet = false;

    if( !bDeclare && aClassID.getLength() == 42 &&
        aClassID.startsWith("clsid:") )
    {
        aClassID = aClassID.copy(6);
        SvGlobalName aCID;
        if( aCID.MakeId( aClassID ) )
        {
            SvGlobalName aJavaCID( 0x8AD9C840UL, 0x044EU, 0x11D1U, 0xB3U, 0xE9U,
                                   0x00U, 0x80U, 0x5FU, 0x49U, 0x9DU, 0x93U );

            bIsApplet = aJavaCID == aCID;
        }
    }

    if( !bIsApplet )
    {
        m_pAppletImpl.reset();
        return;
    }

    m_pAppletImpl->SetAltText( aStandBy );

    SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
    SvxCSS1PropertyInfo aPropInfo;
    if( HasStyleOptions( aStyle, aId, aClass ) )
        (void)ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo );

    SfxItemSet& rFrameSet = m_pAppletImpl->GetItemSet();
    if( !IsNewDoc() )
        Reader::ResetFrameFormatAttrs( rFrameSet );

    // set the anchor and the adjustment
    SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, rFrameSet );

    // and still the size of the frame
    Size aDfltSz( HTML_DFLT_APPLET_WIDTH, HTML_DFLT_APPLET_HEIGHT );
    SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, rFrameSet );
    SetSpace( aSpace, aItemSet, aPropInfo, rFrameSet );
}
#endif

void SwHTMLParser::EndObject()
{
#if HAVE_FEATURE_JAVA
    if( !m_pAppletImpl )
        return;
    if( !m_pAppletImpl->CreateApplet( m_sBaseURL ) )
        return;

    m_pAppletImpl->FinishApplet();

    // and insert into the document
    SwFrameFormat* pFlyFormat =
        m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam,
                ::svt::EmbeddedObjectRef( m_pAppletImpl->GetApplet(), embed::Aspects::MSOLE_CONTENT ),
                &m_pAppletImpl->GetItemSet() );

    // set the alternative name
    SwNoTextNode *pNoTextNd =
        m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx()
                          ->GetIndex()+1 ]->GetNoTextNode();
    pNoTextNd->SetTitle( m_pAppletImpl->GetAltText() );

    // if applicable create frames and register auto-bound frames
    RegisterFlyFrame( pFlyFormat );

    m_pAppletImpl.reset();
#else
    (void) this;                // Silence loplugin:staticmethods
#endif
}

#if HAVE_FEATURE_JAVA
void SwHTMLParser::InsertApplet()
{
    OUString aCodeBase, aCode, aName, aAlt, aId, aStyle, aClass;
    Size aSize( USHRT_MAX, USHRT_MAX );
    Size aSpace( 0, 0 );
    bool bPercentWidth = false, bPercentHeight = false, bMayScript = false;
    sal_Int16 eVertOri = text::VertOrientation::TOP;
    sal_Int16 eHoriOri = text::HoriOrientation::NONE;

    // create a new Command list
    m_pAppletImpl.reset(new SwApplet_Impl( m_xDoc->GetAttrPool() ));

    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::CODEBASE:
            aCodeBase = rOption.GetString();
            break;
        case HtmlOptionId::CODE:
            aCode = rOption.GetString();
            break;
        case HtmlOptionId::NAME:
            aName = rOption.GetString();
            break;
        case HtmlOptionId::ALT:
            aAlt = rOption.GetString();
            break;
        case HtmlOptionId::ALIGN:
            eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri );
            eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri );
            break;
        case HtmlOptionId::WIDTH:
            bPercentWidth = (rOption.GetString().indexOf('%') != -1);
            aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::HEIGHT:
            bPercentHeight = (rOption.GetString().indexOf('%') != -1);
            aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::HSPACE:
            aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::VSPACE:
            aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::MAYSCRIPT:
            bMayScript = true;
            break;
        default: break;
        }

        // All parameters are passed to the applet.
        m_pAppletImpl->AppendParam( rOption.GetTokenString(),
                                  rOption.GetString() );
    }

    if( aCode.isEmpty() )
    {
        m_pAppletImpl.reset();
        return;
    }

    if ( !aCodeBase.isEmpty() )
        aCodeBase = INetURLObject::GetAbsURL( m_sBaseURL, aCodeBase );
    m_pAppletImpl->CreateApplet( aCode, aName, bMayScript, aCodeBase, m_sBaseURL );//, aAlt );
    m_pAppletImpl->SetAltText( aAlt );

    SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
    SvxCSS1PropertyInfo aPropInfo;
    if( HasStyleOptions( aStyle, aId, aClass ) )
        (void)ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo );

    SfxItemSet& rFrameSet = m_pAppletImpl->GetItemSet();
    if( !IsNewDoc() )
        Reader::ResetFrameFormatAttrs( rFrameSet );

    // set the anchor and the adjustment
    SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, rFrameSet );

    // and still the size or the frame
    Size aDfltSz( HTML_DFLT_APPLET_WIDTH, HTML_DFLT_APPLET_HEIGHT );
    SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, rFrameSet );
    SetSpace( aSpace, aItemSet, aPropInfo, rFrameSet );
}
#endif

void SwHTMLParser::EndApplet()
{
#if HAVE_FEATURE_JAVA
    if( !m_pAppletImpl )
        return;

    m_pAppletImpl->FinishApplet();

    // and insert into the document
    SwFrameFormat* pFlyFormat =
        m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam,
                    ::svt::EmbeddedObjectRef( m_pAppletImpl->GetApplet(), embed::Aspects::MSOLE_CONTENT ),
                    &m_pAppletImpl->GetItemSet());

    // set the alternative name
    SwNoTextNode *pNoTextNd =
        m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx()
                          ->GetIndex()+1 ]->GetNoTextNode();
    pNoTextNd->SetTitle( m_pAppletImpl->GetAltText() );

    // if applicable create frames and register auto-bound frames
    RegisterFlyFrame( pFlyFormat );

    m_pAppletImpl.reset();
#else
    (void) this;
#endif
}

void SwHTMLParser::InsertParam()
{
#if HAVE_FEATURE_JAVA
    if( !m_pAppletImpl )
        return;

    OUString aName, aValue;

    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::NAME:
            aName = rOption.GetString();
            break;
        case HtmlOptionId::VALUE:
            aValue = rOption.GetString();
            break;
        default: break;
        }
    }

    if( aName.isEmpty() )
        return;

    m_pAppletImpl->AppendParam( aName, aValue );
#else
    (void) this;
#endif
}

void SwHTMLParser::InsertFloatingFrame()
{
    OUString aAlt, aId, aStyle, aClass;
    Size aSize( USHRT_MAX, USHRT_MAX );
    Size aSpace( 0, 0 );
    bool bPercentWidth = false, bPercentHeight = false;
    sal_Int16 eVertOri = text::VertOrientation::TOP;
    sal_Int16 eHoriOri = text::HoriOrientation::NONE;

    const HTMLOptions& rHTMLOptions = GetOptions();

    // First fetch the options of the Writer-Frame-Format
    for (const auto & rOption : rHTMLOptions)
    {
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::ALT:
            aAlt = rOption.GetString();
            break;
        case HtmlOptionId::ALIGN:
            eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri );
            eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri );
            break;
        case HtmlOptionId::WIDTH:
            bPercentWidth = (rOption.GetString().indexOf('%') != -1);
            aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::HEIGHT:
            bPercentHeight = (rOption.GetString().indexOf('%') != -1);
            aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::HSPACE:
            aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::VSPACE:
            aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        default: break;
        }
    }

    // and now the ones for the SfxFrame
    SfxFrameDescriptor aFrameDesc;

    SfxFrameHTMLParser::ParseFrameOptions( &aFrameDesc, rHTMLOptions, m_sBaseURL );

    // create a floating frame
    comphelper::EmbeddedObjectContainer aCnt;
    OUString aObjName;
    uno::Reference < embed::XEmbeddedObject > xObj = aCnt.CreateEmbeddedObject( SvGlobalName( SO3_IFRAME_CLASSID ).GetByteSequence(), aObjName );

    try
    {
        // TODO/MBA: testing
        if ( svt::EmbeddedObjectRef::TryRunningState( xObj ) )
        {
            uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY );
            if ( xSet.is() )
            {
                const OUString& aName = aFrameDesc.GetName();
                ScrollingMode eScroll = aFrameDesc.GetScrollingMode();
                bool bHasBorder = aFrameDesc.HasFrameBorder();
                Size aMargin = aFrameDesc.GetMargin();

                OUString sHRef = aFrameDesc.GetURL().GetMainURL( INetURLObject::DecodeMechanism::NONE );

                if (INetURLObject(sHRef).IsExoticProtocol())
                    NotifyMacroEventRead();

                xSet->setPropertyValue(u"FrameURL"_ustr, uno::Any( sHRef ) );
                xSet->setPropertyValue(u"FrameName"_ustr, uno::Any( aName ) );

                if ( eScroll == ScrollingMode::Auto )
                    xSet->setPropertyValue(u"FrameIsAutoScroll"_ustr,
                        uno::Any( true ) );
                else
                    xSet->setPropertyValue(u"FrameIsScrollingMode"_ustr,
                        uno::Any( eScroll == ScrollingMode::Yes ) );

                xSet->setPropertyValue(u"FrameIsBorder"_ustr,
                        uno::Any( bHasBorder ) );

                xSet->setPropertyValue(u"FrameMarginWidth"_ustr,
                    uno::Any( sal_Int32( aMargin.Width() ) ) );

                xSet->setPropertyValue(u"FrameMarginHeight"_ustr,
                    uno::Any( sal_Int32( aMargin.Height() ) ) );
            }
        }
    }
    catch ( uno::Exception& )
    {
    }

    SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
    SvxCSS1PropertyInfo aPropInfo;
    if( HasStyleOptions( aStyle, aId, aClass ) )
        (void)ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo );

    // fetch the ItemSet
    SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() );
    if( !IsNewDoc() )
        Reader::ResetFrameFormatAttrs( aFrameSet );

    // set the anchor and the adjustment
    SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, aFrameSet );

    // and still the size of the frame
    Size aDfltSz( HTML_DFLT_APPLET_WIDTH, HTML_DFLT_APPLET_HEIGHT );
    SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, aFrameSet );
    SetSpace( aSpace, aItemSet, aPropInfo, aFrameSet );

    SanitizeAnchor(aFrameSet);

    // and insert into the document
    SwFrameFormat* pFlyFormat =
        m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam,
            ::svt::EmbeddedObjectRef(xObj, embed::Aspects::MSOLE_CONTENT),
            &aFrameSet);

    // set the alternative name
    SwNoTextNode *pNoTextNd =
        m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx()
                          ->GetIndex()+1 ]->GetNoTextNode();
    pNoTextNd->SetTitle( aAlt );

    // if applicable create frames and register auto-bound frames
    RegisterFlyFrame( pFlyFormat );

    m_bInFloatingFrame = true;

    ++m_nFloatingFrames;
}

SwHTMLFrameType SwHTMLWriter::GuessOLENodeFrameType( const SwNode& rNode )
{
    SwHTMLFrameType eType = HTML_FRMTYPE_OLE;

    SwOLENode* pOLENode = const_cast<SwOLENode*>(rNode.GetOLENode());
    assert(pOLENode && "must exist");
    SwOLEObj& rObj = pOLENode->GetOLEObj();

    uno::Reference < embed::XClassifiedObject > xClass = rObj.GetOleRef();
    SvGlobalName aClass( xClass->getClassID() );
    if( aClass == SvGlobalName( SO3_PLUGIN_CLASSID ) )
    {
        eType = HTML_FRMTYPE_PLUGIN;
    }
    else if( aClass == SvGlobalName( SO3_IFRAME_CLASSID ) )
    {
        eType = HTML_FRMTYPE_IFRAME;
    }
#if HAVE_FEATURE_JAVA
    else if( aClass == SvGlobalName( SO3_APPLET_CLASSID ) )
    {
        eType = HTML_FRMTYPE_APPLET;
    }
#endif

    return eType;
}

SwHTMLWriter& OutHTML_FrameFormatOLENode( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat,
                               bool bInCntnr )
{
    const SwFormatContent& rFlyContent = rFrameFormat.GetContent();
    SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1;
    SwOLENode *pOLENd = rWrt.m_pDoc->GetNodes()[ nStt ]->GetOLENode();

    OSL_ENSURE( pOLENd, "OLE-Node expected" );
    if( !pOLENd )
        return rWrt;

    SwOLEObj &rObj = pOLENd->GetOLEObj();

    uno::Reference < embed::XEmbeddedObject > xObj( rObj.GetOleRef() );
    if ( !svt::EmbeddedObjectRef::TryRunningState( xObj ) )
        return rWrt;

    uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY );
    bool bHiddenEmbed = false;

    if( !xSet.is() )
    {
        OSL_FAIL("Unknown Object" );
        return rWrt;
    }

    HtmlFrmOpts nFrameOpts;

    // if possible output a line break before the "object"
    if (rWrt.IsLFPossible())
        rWrt.OutNewLine( true );

    if( !rFrameFormat.GetName().isEmpty() )
        rWrt.OutImplicitMark( rFrameFormat.GetName().toString(),
                                  "ole" );
    uno::Any aAny;
    SvGlobalName aGlobName( xObj->getClassID() );
    OStringBuffer sOut("<");
    if( aGlobName == SvGlobalName( SO3_PLUGIN_CLASSID ) )
    {
        // first the plug-in specifics
        sOut.append(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_embed);

        OUString aStr;
        OUString aURL;
        aAny = xSet->getPropertyValue(u"PluginURL"_ustr);
        if( (aAny >>= aStr) && !aStr.isEmpty() )
        {
            aURL = rWrt.normalizeURL(aStr, false);
        }

        if( !aURL.isEmpty() )
        {
            sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_src "=\"");
            rWrt.Strm().WriteOString( sOut );
            sOut.setLength(0);
            HTMLOutFuncs::Out_String( rWrt.Strm(), aURL );
            sOut.append('\"');
        }

        OUString aType;
        aAny = xSet->getPropertyValue(u"PluginMimeType"_ustr);
        if( (aAny >>= aType) && !aType.isEmpty() )
        {
            sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_type "=\"");
            rWrt.Strm().WriteOString( sOut );
            sOut.setLength(0);
            HTMLOutFuncs::Out_String( rWrt.Strm(), aType );
            sOut.append('\"');
        }

        if ((RndStdIds::FLY_AT_PARA == rFrameFormat.GetAnchor().GetAnchorId()) &&
            css::text::WrapTextMode_THROUGH == rFrameFormat.GetSurround().GetSurround() )
        {
            // A HIDDEN plug-in
            sOut.append(" " OOO_STRING_SW_HTML_O_Hidden);
            nFrameOpts = HTML_FRMOPTS_HIDDEN_EMBED;
            bHiddenEmbed = true;
        }
        else
        {
            nFrameOpts = bInCntnr ? HTML_FRMOPTS_EMBED_CNTNR
                                : HTML_FRMOPTS_EMBED;
        }
    }
    else if( aGlobName == SvGlobalName( SO3_APPLET_CLASSID ) )
    {
        // or the applet specifics

        sOut.append(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_applet);

        // CODEBASE
        OUString aCd;
        aAny = xSet->getPropertyValue(u"AppletCodeBase"_ustr);
        if( (aAny >>= aCd) && !aCd.isEmpty() )
        {
            OUString sCodeBase(rWrt.normalizeURL(aCd, false));
            if( !sCodeBase.isEmpty() )
            {
                sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_codebase "=\"");
                rWrt.Strm().WriteOString( sOut );
                sOut.setLength(0);
                HTMLOutFuncs::Out_String( rWrt.Strm(), sCodeBase );
                sOut.append('\"');
            }
        }

        // CODE
        OUString aClass;
        aAny = xSet->getPropertyValue(u"AppletCode"_ustr);
        aAny >>= aClass;
        sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_code "=\"");
        rWrt.Strm().WriteOString( sOut );
        sOut.setLength(0);
        HTMLOutFuncs::Out_String( rWrt.Strm(), aClass );
        sOut.append('\"');

        // NAME
        OUString aAppletName;
        aAny = xSet->getPropertyValue(u"AppletName"_ustr);
        aAny >>= aAppletName;
        if( !aAppletName.isEmpty() )
        {
            sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_name "=\"");
            rWrt.Strm().WriteOString( sOut );
            sOut.setLength(0);
            HTMLOutFuncs::Out_String( rWrt.Strm(), aAppletName );
            sOut.append('\"');
        }

        bool bScript = false;
        aAny = xSet->getPropertyValue(u"AppletIsScript"_ustr);
        aAny >>= bScript;
        if( bScript )
            sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_mayscript);

        nFrameOpts = bInCntnr ? HTML_FRMOPTS_APPLET_CNTNR
                            : HTML_FRMOPTS_APPLET;
    }
    else
    {
        // or the Floating-Frame specifics

        sOut.append(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_iframe);
        rWrt.Strm().WriteOString( sOut );
        sOut.setLength(0);

        SfxFrameHTMLWriter::Out_FrameDescriptor( rWrt.Strm(), rWrt.GetBaseURL(),
                                        xSet );

        nFrameOpts = bInCntnr ? HTML_FRMOPTS_IFRAME_CNTNR
                            : HTML_FRMOPTS_IFRAME;
    }

    rWrt.Strm().WriteOString( sOut );
    sOut.setLength(0);

    // ALT, WIDTH, HEIGHT, HSPACE, VSPACE, ALIGN
    if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bHiddenEmbed )
        nFrameOpts |= HTML_FRMOPTS_OLE_CSS1;
    OString aEndTags = rWrt.OutFrameFormatOptions( rFrameFormat, pOLENd->GetTitle(), nFrameOpts );
    if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bHiddenEmbed )
        rWrt.OutCSS1_FrameFormatOptions( rFrameFormat, nFrameOpts );

    if( aGlobName == SvGlobalName( SO3_APPLET_CLASSID ) )
    {
        // output the parameters of applets as separate tags
        // and write a </APPLET>

        uno::Sequence < beans::PropertyValue > aProps;
        aAny = xSet->getPropertyValue(u"AppletCommands"_ustr);
        aAny >>= aProps;

        SvCommandList aCommands;
        aCommands.FillFromSequence( aProps );
        std::vector<sal_uLong> aParams;
        size_t i = aCommands.size();
        while( i > 0 )
        {
            const SvCommand& rCommand = aCommands[ --i ];
            const OUString& rName = rCommand.GetCommand();
            SwHtmlOptType nType = SwApplet_Impl::GetOptionType( rName, true );
            if( SwHtmlOptType::TAG == nType )
            {
                const OUString& rValue = rCommand.GetArgument();
                rWrt.Strm().WriteChar( ' ' );
                HTMLOutFuncs::Out_String( rWrt.Strm(), rName );
                rWrt.Strm().WriteOString( "=\"" );
                HTMLOutFuncs::Out_String( rWrt.Strm(), rValue ).WriteChar( '\"' );
            }
            else if( SwHtmlOptType::PARAM == nType )
            {
                aParams.push_back( i );
            }
        }

        rWrt.Strm().WriteChar( '>' );

        rWrt.IncIndentLevel(); // indent the applet content

        size_t ii = aParams.size();
        while( ii > 0  )
        {
            const SvCommand& rCommand = aCommands[ aParams[--ii] ];
            const OUString& rName = rCommand.GetCommand();
            const OUString& rValue = rCommand.GetArgument();
            rWrt.OutNewLine();
            sOut.append(
                "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_param
                " " OOO_STRING_SVTOOLS_HTML_O_name
                "=\"");
            rWrt.Strm().WriteOString( sOut );
            sOut.setLength(0);
            HTMLOutFuncs::Out_String( rWrt.Strm(), rName );
            sOut.append("\" " OOO_STRING_SVTOOLS_HTML_O_value "=\"");
            rWrt.Strm().WriteOString( sOut );
            sOut.setLength(0);
            HTMLOutFuncs::Out_String( rWrt.Strm(), rValue ).WriteOString( "\">" );
        }

        rWrt.DecIndentLevel(); // indent the applet content
        if( aCommands.size() )
            rWrt.OutNewLine();
        HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_applet), false );
    }
    else if( aGlobName == SvGlobalName( SO3_PLUGIN_CLASSID ) )
    {
        // write plug-ins parameters as options

        uno::Sequence < beans::PropertyValue > aProps;
        aAny = xSet->getPropertyValue(u"PluginCommands"_ustr);
        aAny >>= aProps;

        SvCommandList aCommands;
        aCommands.FillFromSequence( aProps );
        for( size_t i = 0; i < aCommands.size(); i++ )
        {
            const SvCommand& rCommand = aCommands[ i ];
            const OUString& rName = rCommand.GetCommand();

            if( SwApplet_Impl::GetOptionType( rName, false ) == SwHtmlOptType::TAG )
            {
                const OUString& rValue = rCommand.GetArgument();
                rWrt.Strm().WriteChar( ' ' );
                HTMLOutFuncs::Out_String( rWrt.Strm(), rName );
                rWrt.Strm().WriteOString( "=\"" );
                HTMLOutFuncs::Out_String( rWrt.Strm(), rValue ).WriteChar( '\"' );
            }
        }
        rWrt.Strm().WriteChar( '>' );
    }
    else
    {
        // and for Floating-Frames just output another </IFRAME>

        rWrt.Strm().WriteChar( '>' );
        HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_iframe), false );
    }

    if( !aEndTags.isEmpty() )
        rWrt.Strm().WriteOString( aEndTags );

    return rWrt;
}

static void OutHTMLGraphic(SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, const SwOLENode* pOLENd,
                           const Graphic& rGraphic, bool bObjectOpened, bool bInCntnr)
{
    OUString aGraphicURL;
    OUString aMimeType;
    if (!rWrt.mbEmbedImages)
    {
        const OUString* pTempFileName = rWrt.GetOrigFileName();
        if (pTempFileName)
            aGraphicURL = *pTempFileName;

        OUString aFilterName(u"JPG"_ustr);
        XOutFlags nFlags = XOutFlags::UseGifIfPossible | XOutFlags::UseNativeIfPossible;

        if (bObjectOpened)
        {
            aFilterName = u"PNG"_ustr;
            nFlags = XOutFlags::NONE;
            aMimeType = u"image/png"_ustr;

            if (rGraphic.GetType() == GraphicType::NONE)
            {
                // The OLE Object has no replacement image, write a stub.
                aGraphicURL = lcl_CalculateFileName(rWrt.GetOrigFileName(), rGraphic, u"png");
                osl::File aFile(aGraphicURL);
                aFile.open(osl_File_OpenFlag_Create);
                aFile.close();
            }
        }

        ErrCode nErr = XOutBitmap::WriteGraphic(rGraphic, aGraphicURL, aFilterName, nFlags);
        if (nErr) // error, don't write anything
        {
            rWrt.m_nWarn = WARN_SWG_POOR_LOAD;
            if (bObjectOpened) // Still at least close the tag.
                rWrt.Strm().WriteOString(
                    Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">"));
            return;
        }
        aGraphicURL = URIHelper::SmartRel2Abs(INetURLObject(rWrt.GetBaseURL()), aGraphicURL,
                                              URIHelper::GetMaybeFileHdl());
    }
    HtmlFrmOpts nFlags = bInCntnr ? HtmlFrmOpts::GenImgAllMask : HtmlFrmOpts::GenImgMask;
    if (bObjectOpened)
        nFlags |= HtmlFrmOpts::Replacement;
    HtmlWriter aHtml(rWrt.Strm(), rWrt.GetNamespace());
    OutHTML_ImageStart(aHtml, rWrt, rFrameFormat, aGraphicURL, rGraphic, pOLENd->GetTitle(),
                       pOLENd->GetTwipSize(), nFlags, "ole", nullptr, aMimeType, true);
    OutHTML_ImageEnd(aHtml, rWrt);
}

static void OutHTMLStartObject(SwHTMLWriter& rWrt, const OUString& rFileName, const OUString& rFileType)
{
    // OutHTMLStartObject is only for own objects
    OUString aFileName = rWrt.normalizeURL(rFileName, true);

    if (rWrt.IsLFPossible())
        rWrt.OutNewLine();
    rWrt.Strm().WriteOString(
        Concat2View("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object));
    rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + "\""));
    if (!rFileType.isEmpty())
        rWrt.Strm().WriteOString(Concat2View(" type=\"" + rFileType.toUtf8() + "\""));
    rWrt.Strm().WriteOString(">");
    rWrt.SetLFPossible(true);
}

static void OutHTMLEndObject(SwHTMLWriter& rWrt)
{
    rWrt.Strm().WriteOString(
        Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">"));
}

static bool TrySaveFormulaAsPDF(SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat,
                                const SwOLENode* pOLENd, bool bWriteReplacementGraphic, bool bInCntnr)
{
    if (!rWrt.mbReqIF)
        return false;
    if (!rWrt.m_bExportFormulasAsPDF)
        return false;

    auto xTextContent = SwXTextEmbeddedObject::CreateXTextEmbeddedObject(
        *rWrt.m_pDoc, const_cast<SwFrameFormat*>(&rFrameFormat));
    uno::Reference<frame::XStorable> xStorable(xTextContent->getEmbeddedObject(), uno::UNO_QUERY);
    uno::Reference<lang::XServiceInfo> xServiceInfo(xStorable, uno::UNO_QUERY);
    if (!xServiceInfo)
        return false;
    if (!xServiceInfo->supportsService(u"com.sun.star.formula.FormulaProperties"_ustr))
        return false;

    Graphic aGraphic(xTextContent->getReplacementGraphic());
    OUString aFileName = lcl_CalculateFileName(rWrt.GetOrigFileName(), aGraphic, u"pdf");

    utl::MediaDescriptor aDescr;
    aDescr[u"FilterName"_ustr] <<= u"math_pdf_Export"_ustr;
    // Properties from starmath/inc/unomodel.hxx
    aDescr[u"FilterData"_ustr] <<= comphelper::InitPropertySequence({
        { u"TitleRow"_ustr, css::uno::Any(false) },
        { u"FormulaText"_ustr, css::uno::Any(false) },
        { u"Border"_ustr, css::uno::Any(false) },
        { u"PrintFormat"_ustr, css::uno::Any(sal_Int32(1)) }, // PRINT_SIZE_SCALED
    });
    xStorable->storeToURL(aFileName, aDescr.getAsConstPropertyValueList());

    OutHTMLStartObject(rWrt, aFileName, u"application/pdf"_ustr);

    if (bWriteReplacementGraphic)
        OutHTMLGraphic(rWrt, rFrameFormat, pOLENd, aGraphic, true, bInCntnr);

    OutHTMLEndObject(rWrt);

    return true;
}

SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat,
                                  bool bInCntnr, bool bWriteReplacementGraphic )
{
    const SwFormatContent& rFlyContent = rFrameFormat.GetContent();
    SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1;
    SwOLENode *pOLENd = rWrt.m_pDoc->GetNodes()[ nStt ]->GetOLENode();

    OSL_ENSURE( pOLENd, "OLE-Node expected" );
    if( !pOLENd )
        return rWrt;

    if (rWrt.mbSkipImages)
    {
        // If we skip images, embedded objects would be completely lost.
        // Instead, try to use the HTML export of the embedded object.
        auto xTextContent = SwXTextEmbeddedObject::CreateXTextEmbeddedObject(*rWrt.m_pDoc, const_cast<SwFrameFormat*>(&rFrameFormat));
        uno::Reference<frame::XStorable> xStorable(xTextContent->getEmbeddedObject(), uno::UNO_QUERY);
        SAL_WARN_IF(!xStorable.is(), "sw.html", "OutHTML_FrameFormatOLENodeGrf: no embedded object");

        // Figure out what is the filter name of the embedded object.
        OUString aFilter;
        if (uno::Reference<lang::XServiceInfo> xServiceInfo{ xStorable, uno::UNO_QUERY })
        {
            if (xServiceInfo->supportsService(u"com.sun.star.sheet.SpreadsheetDocument"_ustr))
                aFilter = "HTML (StarCalc)";
            else if (xServiceInfo->supportsService(u"com.sun.star.text.TextDocument"_ustr))
                aFilter = "HTML (StarWriter)";
        }

        if (!aFilter.isEmpty())
        {
            try
            {
                // FIXME: exception for the simplest test document, too
                SvMemoryStream aStream;
                uno::Reference<io::XOutputStream> xOutputStream(new utl::OStreamWrapper(aStream));
                utl::MediaDescriptor aMediaDescriptor;
                aMediaDescriptor[u"FilterName"_ustr] <<= aFilter;
                aMediaDescriptor[u"FilterOptions"_ustr] <<= u"SkipHeaderFooter"_ustr;
                aMediaDescriptor[u"OutputStream"_ustr] <<= xOutputStream;
                xStorable->storeToURL(u"private:stream"_ustr, aMediaDescriptor.getAsConstPropertyValueList());
                SAL_WARN_IF(aStream.GetSize()>=o3tl::make_unsigned(SAL_MAX_INT32), "sw.html", "Stream can't fit in OString");
                OString aData(static_cast<const char*>(aStream.GetData()), static_cast<sal_Int32>(aStream.GetSize()));
                // Wrap output in a <span> tag to avoid 'HTML parser error: Unexpected end tag: p'
                HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span));
                rWrt.Strm().WriteOString(aData);
                HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false);
            }
            catch ( uno::Exception& )
            {
            }
        }

        return rWrt;
    }

    if (TrySaveFormulaAsPDF(rWrt, rFrameFormat, pOLENd, bWriteReplacementGraphic, bInCntnr))
        return rWrt;

    // Missing fallback graphic must not give up exporting the document, and also must produce
    // valid output (see OutHTMLGraphic)
    const Graphic* pFallbackGraphic = pOLENd->GetGraphic();
    Graphic aGraphic(pFallbackGraphic ? *pFallbackGraphic : Graphic());

    SwDocShell* pDocSh = rWrt.m_pDoc->GetDocShell();
    bool bObjectOpened = false;
    OUString aRTFType = u"text/rtf"_ustr;
    if (!rWrt.m_aRTFOLEMimeType.isEmpty())
    {
        aRTFType = rWrt.m_aRTFOLEMimeType;
    }

    if (rWrt.mbXHTML && pDocSh)
    {
        // Map native data to an outer <object> element.

        // Calculate the file name, which is meant to be the same as the
        // replacement image, just with a .ole extension.
        OUString aFileName = lcl_CalculateFileName(rWrt.GetOrigFileName(), aGraphic, u"ole");

        // Write the data.
        SwOLEObj& rOLEObj = pOLENd->GetOLEObj();
        uno::Reference<embed::XEmbeddedObject> xEmbeddedObject = rOLEObj.GetOleRef();
        OUString aFileType;
        SvFileStream aOutStream(aFileName, StreamMode::WRITE);
        uno::Reference<io::XActiveDataStreamer> xStreamProvider;
        uno::Reference<embed::XEmbedPersist2> xOwnEmbedded;
        if (xEmbeddedObject.is())
        {
            xStreamProvider.set(xEmbeddedObject, uno::UNO_QUERY);
            xOwnEmbedded.set(xEmbeddedObject, uno::UNO_QUERY);
        }
        if (xStreamProvider.is())
        {
            // Real OLE2 case: OleEmbeddedObject.
            uno::Reference<io::XInputStream> xStream(xStreamProvider->getStream(), uno::UNO_QUERY);
            if (xStream.is())
            {
                std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xStream));
                if (SwReqIfReader::WrapOleInRtf(*pStream, aOutStream, *pOLENd, rFrameFormat))
                {
                    // Data always wrapped in RTF.
                    aFileType = aRTFType;
                }
            }
        }
        else if (xOwnEmbedded.is())
        {
            // Our own embedded object: OCommonEmbeddedObject.
            SvxMSExportOLEObjects aOLEExp(0);
            // Trigger the load of the OLE object if needed, otherwise we can't
            // export it.
            pOLENd->GetTwipSize();
            SvMemoryStream aMemory;
            rtl::Reference<SotStorage> pStorage = new SotStorage(aMemory);
            aOLEExp.ExportOLEObject(rOLEObj.GetObject(), *pStorage);
            pStorage->Commit();
            aMemory.Seek(0);
            if (SwReqIfReader::WrapOleInRtf(aMemory, aOutStream, *pOLENd, rFrameFormat))
            {
                // Data always wrapped in RTF.
                aFileType = aRTFType;
            }
        }
        else
        {
            // Otherwise the native data is just a grab-bag: ODummyEmbeddedObject.
            const OUString& aStreamName = rOLEObj.GetCurrentPersistName();
            uno::Reference<embed::XStorage> xStorage = pDocSh->GetStorage();
            uno::Reference<io::XStream> xInStream;
            try
            {
                // Even the native data may be missing.
                xInStream = xStorage->openStreamElement(aStreamName, embed::ElementModes::READ);
            } catch (const uno::Exception&)
            {
                TOOLS_WARN_EXCEPTION("sw.html", "OutHTML_FrameFormatOLENodeGrf: failed to open stream element");
            }
            if (xInStream.is())
            {
                uno::Reference<io::XStream> xOutStream(new utl::OStreamWrapper(aOutStream));
                comphelper::OStorageHelper::CopyInputToOutput(xInStream->getInputStream(),
                                                              xOutStream->getOutputStream());
            }

            uno::Reference<beans::XPropertySet> xOutStreamProps(xInStream, uno::UNO_QUERY);
            if (xOutStreamProps.is())
                xOutStreamProps->getPropertyValue(u"MediaType"_ustr) >>= aFileType;
            if (!aRTFType.isEmpty())
            {
                aFileType = aRTFType;
            }
        }

        // Refer to this data.
        OutHTMLStartObject(rWrt, aFileName, aFileType);
        bObjectOpened = true;
    }

    if (!bObjectOpened || bWriteReplacementGraphic)
        OutHTMLGraphic(rWrt, rFrameFormat, pOLENd, aGraphic, bObjectOpened, bInCntnr);

    if (bObjectOpened)
        // Close native data.
        OutHTMLEndObject(rWrt);

    return rWrt;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
