/* -*- 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 <com/sun/star/form/FormSubmitEncoding.hpp>
#include <com/sun/star/form/FormSubmitMethod.hpp>
#include <com/sun/star/form/FormButtonType.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/script/XEventAttacherManager.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/form/XFormsSupplier.hpp>
#include <com/sun/star/form/XForm.hpp>
#include <com/sun/star/form/FormComponentType.hpp>
#include <com/sun/star/awt/XTextLayoutConstrains.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <hintids.hxx>
#include <o3tl/any.hxx>
#include <rtl/math.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <svl/macitem.hxx>
#include <svtools/htmlout.hxx>
#include <svtools/htmlkywd.hxx>
#include <svl/urihelper.hxx>
#include <vcl/unohelp.hxx>
#include <svx/svdouno.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <osl/diagnose.h>
#include <docsh.hxx>
#include <fmtanchr.hxx>
#include <viewsh.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include "wrthtml.hxx"
#include "htmlfly.hxx"
#include "htmlform.hxx"
#include <frmfmt.hxx>
#include <frameformats.hxx>
#include <memory>
#include <unotxdoc.hxx>
 
using namespace ::com::sun::star;
 
const HtmlFrmOpts HTML_FRMOPTS_CONTROL   =
    HtmlFrmOpts::NONE;
const HtmlFrmOpts HTML_FRMOPTS_CONTROL_CSS1  =
    HtmlFrmOpts::SAlign |
    HtmlFrmOpts::SSize |
    HtmlFrmOpts::SSpace |
    HtmlFrmOpts::BrClear;
const HtmlFrmOpts HTML_FRMOPTS_IMG_CONTROL   =
    HtmlFrmOpts::Align |
    HtmlFrmOpts::BrClear;
const HtmlFrmOpts HTML_FRMOPTS_IMG_CONTROL_CSS1 =
    HtmlFrmOpts::SAlign |
    HtmlFrmOpts::SSpace;
 
static void lcl_html_outEvents( SvStream& rStrm,
                         const uno::Reference< form::XFormComponent >& rFormComp,
                         bool bCfgStarBasic )
{
    uno::Reference< uno::XInterface > xParentIfc = rFormComp->getParent();
    OSL_ENSURE( xParentIfc.is(), "lcl_html_outEvents: no parent interface" );
    if( !xParentIfc.is() )
        return;
    uno::Reference< container::XIndexAccess > xIndexAcc( xParentIfc, uno::UNO_QUERY );
    uno::Reference< script::XEventAttacherManager > xEventManager( xParentIfc,
                                                              uno::UNO_QUERY );
    if( !xIndexAcc.is() || !xEventManager.is() )
        return;
 
    // and search for the position of the ControlModel within
    sal_Int32 nCount = xIndexAcc->getCount(), nPos;
    for( nPos = 0 ; nPos < nCount; nPos++ )
    {
        uno::Any aTmp = xIndexAcc->getByIndex(nPos);
        if( auto x1 = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(aTmp) )
 
        {
            if( rFormComp == *x1 )
                break;
        }
        else if( auto x2 = o3tl::tryAccess<uno::Reference<form::XForm>>(aTmp) )
        {
            if( rFormComp == *x2 )
                break;
        }
        else
        {
            OSL_ENSURE( false, "lcl_html_outEvents: wrong reflection" );
        }
    }
 
    if( nPos == nCount )
        return;
 
    const uno::Sequence< script::ScriptEventDescriptor > aDescs =
            xEventManager->getScriptEvents( nPos );
    if( !aDescs.hasElements() )
        return;
 
    for( const script::ScriptEventDescriptor& rDesc : aDescs )
    {
        ScriptType eScriptType = EXTENDED_STYPE;
        OUString aScriptType( rDesc.ScriptType );
        if( aScriptType.equalsIgnoreAsciiCase(SVX_MACRO_LANGUAGE_JAVASCRIPT) )
            eScriptType = JAVASCRIPT;
        else if( aScriptType.equalsIgnoreAsciiCase(SVX_MACRO_LANGUAGE_STARBASIC ) )
            eScriptType = STARBASIC;
        if( JAVASCRIPT != eScriptType && !bCfgStarBasic )
            continue;
 
        OUString sListener( rDesc.ListenerType );
        if (!sListener.isEmpty())
        {
            const sal_Int32 nIdx { sListener.lastIndexOf('.')+1 };
            if (nIdx>0)
            {
                if (nIdx<sListener.getLength())
                {
                    sListener = sListener.copy(nIdx);
                }
                else
                {
                    sListener.clear();
                }
            }
        }
        OUString sMethod( rDesc.EventMethod );
 
        const char *pOpt = nullptr;
        for( int j=0; !aEventListenerTable[j].isEmpty(); j++ )
        {
            if( sListener == aEventListenerTable[j] &&
                sMethod == aEventMethodTable[j] )
            {
                pOpt = (STARBASIC==eScriptType ? aEventSDOptionTable
                                               : aEventOptionTable)[j];
                break;
            }
        }
 
        OString sOut = " "_ostr;
        if( pOpt && (EXTENDED_STYPE != eScriptType ||
                     rDesc.AddListenerParam.isEmpty()) )
            sOut += pOpt;
        else
        {
            sOut += OOO_STRING_SVTOOLS_HTML_O_sdevent +
                OUStringToOString(sListener, RTL_TEXTENCODING_ASCII_US) + "-" +
                OUStringToOString(sMethod, RTL_TEXTENCODING_ASCII_US);
        }
        sOut += "=\"";
        rStrm.WriteOString( sOut );
        HTMLOutFuncs::Out_String( rStrm, rDesc.ScriptCode );
        rStrm.WriteChar( '\"' );
        if( EXTENDED_STYPE == eScriptType &&
            !rDesc.AddListenerParam.isEmpty() )
        {
            sOut = " " OOO_STRING_SVTOOLS_HTML_O_sdaddparam +
                OUStringToOString(sListener, RTL_TEXTENCODING_ASCII_US) + "-" +
                OUStringToOString(sMethod, RTL_TEXTENCODING_ASCII_US) + "=\"";
            rStrm.WriteOString( sOut );
            HTMLOutFuncs::Out_String( rStrm, rDesc.AddListenerParam );
            rStrm.WriteChar( '\"' );
        }
    }
}
 
static bool lcl_html_isHTMLControl( sal_Int16 nClassId )
{
    bool bRet = false;
 
    switch( nClassId )
    {
    case form::FormComponentType::TEXTFIELD:
    case form::FormComponentType::COMMANDBUTTON:
    case form::FormComponentType::RADIOBUTTON:
    case form::FormComponentType::CHECKBOX:
    case form::FormComponentType::LISTBOX:
    case form::FormComponentType::IMAGEBUTTON:
    case form::FormComponentType::FILECONTROL:
        bRet = true;
        break;
    }
 
    return bRet;
}
 
bool SwHTMLWriter::HasControls() const
{
    SwNodeOffset nStartIdx = m_pCurrentPam->GetPoint()->GetNodeIndex();
    size_t i = 0;
 
    // Skip all controls in front of the current paragraph
    while ( i < m_aHTMLControls.size() && m_aHTMLControls[i]->nNdIdx < nStartIdx )
        ++i;
 
    return i < m_aHTMLControls.size() && m_aHTMLControls[i]->nNdIdx == nStartIdx;
}
 
void SwHTMLWriter::OutForm( bool bTag_On, const SwStartNode *pStartNd )
{
    if( m_bPreserveForm )   // we are in a table or an area with form spanned over it
        return;
 
    if( !bTag_On )
    {
        // end the form when all controls are output
        if( mxFormComps.is() &&
            mxFormComps->getCount() == m_nFormCntrlCnt )
        {
            OutForm( false, mxFormComps );
            mxFormComps.clear();
        }
        return;
    }
 
    uno::Reference< container::XIndexContainer > xNewFormComps;
    SwNodeOffset nStartIdx = pStartNd ? pStartNd->GetIndex()
                                    : m_pCurrentPam->GetPoint()->GetNodeIndex();
 
    // skip controls before the interesting area
    size_t i = 0;
    while ( i < m_aHTMLControls.size() && m_aHTMLControls[i]->nNdIdx < nStartIdx )
        ++i;
 
    if( !pStartNd )
    {
        // Check for a single node: there it's only interesting, if there is
        // a control for the node and to which form it belongs.
        if( i < m_aHTMLControls.size() &&
            m_aHTMLControls[i]->nNdIdx == nStartIdx )
            xNewFormComps = m_aHTMLControls[i]->xFormComps;
    }
    else
    {
        // we iterate over a table/an area: we're interested in:
        // - if there are controls with different start nodes
        // - if there is a form, with controls which aren't all in the table/area
 
        uno::Reference< container::XIndexContainer > xCurrentFormComps;// current form in table
        const SwStartNode *pCurrentStNd = nullptr; // and the start node of a Control
        sal_Int32 nCurrentCtrls = 0;   // and the found controls in it
        SwNodeOffset nEndIdx =  pStartNd->EndOfSectionIndex();
        for( ; i < m_aHTMLControls.size() &&
            m_aHTMLControls[i]->nNdIdx <= nEndIdx; i++ )
        {
            const SwStartNode *pCntrlStNd =
                m_pDoc->GetNodes()[m_aHTMLControls[i]->nNdIdx]->StartOfSectionNode();
 
            if( xCurrentFormComps.is() )
            {
                // already inside a form ...
                if( xCurrentFormComps==m_aHTMLControls[i]->xFormComps )
                {
                    // ... and the control is also inside ...
                    if( pCurrentStNd!=pCntrlStNd )
                    {
                        // ... but it's inside another cell:
                        // Then open a form above the table
                        xNewFormComps = xCurrentFormComps;
                        break;
                    }
                    nCurrentCtrls = nCurrentCtrls + m_aHTMLControls[i]->nCount;
                }
                else
                {
                    // ... but the Control is in another cell:
                    // There we act as if we open a new from and continue searching.
                    xCurrentFormComps = m_aHTMLControls[i]->xFormComps;
                    pCurrentStNd = pCntrlStNd;
                    nCurrentCtrls = m_aHTMLControls[i]->nCount;
                }
            }
            else
            {
                // We aren't in a form:
                // There we act as if we open a form.
                xCurrentFormComps = m_aHTMLControls[i]->xFormComps;
                pCurrentStNd = pCntrlStNd;
                nCurrentCtrls = m_aHTMLControls[i]->nCount;
            }
        }
        if( !xNewFormComps.is() && xCurrentFormComps.is() &&
            nCurrentCtrls != xCurrentFormComps->getCount() )
        {
            // A form should be opened in the table/area which isn't completely
            // inside the table. Then we must also now open the form.
            xNewFormComps = std::move(xCurrentFormComps);
        }
    }
 
    if( !(xNewFormComps.is() &&
        (!mxFormComps.is() || xNewFormComps != mxFormComps)) )
        return;
 
    // A form should be opened ...
    if( mxFormComps.is() )
    {
        // ... but a form is still open: That is in every case an error,
        // but we'll close the old form nevertheless.
        OutForm( false, mxFormComps );
 
        //!!!nWarn = 1; // Control will be assigned to wrong form
    }
 
    mxFormComps = std::move(xNewFormComps);
 
    OutForm( true, mxFormComps );
    uno::Reference< beans::XPropertySet >  xTmp;
    OutHiddenControls( mxFormComps, xTmp );
}
 
void SwHTMLWriter::OutHiddenForms()
{
    // Without DrawModel there can't be controls. Then you also can't access the
    // document via UNO, because otherwise a DrawModel would be created.
    if( !m_pDoc->getIDocumentDrawModelAccess().GetDrawModel() )
        return;
 
    SwDocShell *pDocSh = m_pDoc->GetDocShell();
    if( !pDocSh )
        return;
 
    rtl::Reference< SwXTextDocument > xDPSupp( pDocSh->GetBaseModel() );
    OSL_ENSURE( xDPSupp.is(), "XTextDocument not received from XModel" );
    uno::Reference< drawing::XDrawPage > xDrawPage = xDPSupp->getDrawPage();
 
    OSL_ENSURE( xDrawPage.is(), "XDrawPage not received" );
    if( !xDrawPage.is() )
        return;
 
    uno::Reference< form::XFormsSupplier > xFormsSupplier( xDrawPage, uno::UNO_QUERY );
    OSL_ENSURE( xFormsSupplier.is(),
            "XFormsSupplier not received from XDrawPage" );
 
    uno::Reference< container::XNameContainer > xTmp = xFormsSupplier->getForms();
    OSL_ENSURE( xTmp.is(), "XForms not received" );
    uno::Reference< container::XIndexContainer > xForms( xTmp, uno::UNO_QUERY );
    OSL_ENSURE( xForms.is(), "XForms without container::XIndexContainer?" );
 
    sal_Int32 nCount = xForms->getCount();
    for( sal_Int32 i=0; i<nCount; i++)
    {
        uno::Any aTmp = xForms->getByIndex( i );
        if( auto x = o3tl::tryAccess<uno::Reference<form::XForm>>(aTmp) )
            OutHiddenForm( *x );
        else
        {
            OSL_ENSURE( false, "OutHiddenForms: wrong reflection" );
        }
    }
}
 
void SwHTMLWriter::OutHiddenForm( const uno::Reference< form::XForm > & rForm )
{
    uno::Reference< container::XIndexContainer > xFormComps( rForm, uno::UNO_QUERY );
    if( !xFormComps.is() )
        return;
 
    sal_Int32 nCount = xFormComps->getCount();
    bool bHiddenOnly = nCount > 0, bHidden = false;
    for( sal_Int32 i=0; i<nCount; i++ )
    {
        uno::Any aTmp = xFormComps->getByIndex( i );
        auto xFormComp = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(
            aTmp);
        OSL_ENSURE( xFormComp, "OutHiddenForm: wrong reflection" );
        if( !xFormComp )
            continue;
 
        uno::Reference< form::XForm > xForm( *xFormComp, uno::UNO_QUERY );
        if( xForm.is() )
            OutHiddenForm( xForm );
 
        if( bHiddenOnly )
        {
            uno::Reference< beans::XPropertySet >  xPropSet( *xFormComp, uno::UNO_QUERY );
            OUString sPropName(u"ClassId"_ustr);
            if( xPropSet->getPropertySetInfo()->hasPropertyByName( sPropName ) )
            {
                uno::Any aAny2 = xPropSet->getPropertyValue( sPropName );
                if( auto n = o3tl::tryAccess<sal_Int16>(aAny2) )
                {
                    if( form::FormComponentType::HIDDENCONTROL == *n )
                        bHidden = true;
                    else if( lcl_html_isHTMLControl( *n ) )
                        bHiddenOnly = false;
                }
            }
        }
    }
 
    if( bHidden && bHiddenOnly )
    {
        OutForm( true, xFormComps );
        uno::Reference< beans::XPropertySet > xTmp;
        OutHiddenControls( xFormComps, xTmp );
        OutForm( false, xFormComps );
    }
}
 
void SwHTMLWriter::OutForm( bool bOn,
                const uno::Reference< container::XIndexContainer > & rFormComps )
{
    m_nFormCntrlCnt = 0;
 
    if( !bOn )
    {
        DecIndentLevel(); // indent content of form
        if (IsLFPossible())
            OutNewLine();
        HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_form), false );
        SetLFPossible(true);
 
        return;
    }
 
    // the new form is opened
    if (IsLFPossible())
        OutNewLine();
    OString sOut = "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_form;
 
    uno::Reference< beans::XPropertySet > xFormPropSet( rFormComps, uno::UNO_QUERY );
 
    uno::Any aTmp = xFormPropSet->getPropertyValue( u"Name"_ustr );
    if( auto s = o3tl::tryAccess<OUString>(aTmp) )
    {
        if( !s->isEmpty() )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\"";
            Strm().WriteOString( sOut );
            HTMLOutFuncs::Out_String( Strm(), *s );
            sOut = "\""_ostr;
        }
    }
 
    aTmp = xFormPropSet->getPropertyValue( u"TargetURL"_ustr );
    if( auto s = o3tl::tryAccess<OUString>(aTmp) )
    {
        if ( !s->isEmpty() )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_action "=\"";
            Strm().WriteOString( sOut );
            OUString aURL = normalizeURL(*s, false);
            HTMLOutFuncs::Out_String( Strm(), aURL );
            sOut = "\""_ostr;
        }
    }
 
    aTmp = xFormPropSet->getPropertyValue( u"SubmitMethod"_ustr );
    if( auto eMethod = o3tl::tryAccess<form::FormSubmitMethod>(aTmp) )
    {
        if( form::FormSubmitMethod_POST==*eMethod )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_method "=\""
                OOO_STRING_SVTOOLS_HTML_METHOD_post "\"";
        }
    }
    aTmp = xFormPropSet->getPropertyValue( u"SubmitEncoding"_ustr );
    if( auto eEncType = o3tl::tryAccess<form::FormSubmitEncoding>(aTmp) )
    {
        const char *pStr = nullptr;
        switch( *eEncType )
        {
        case form::FormSubmitEncoding_MULTIPART:
            pStr = OOO_STRING_SVTOOLS_HTML_ET_multipart;
            break;
        case form::FormSubmitEncoding_TEXT:
            pStr = OOO_STRING_SVTOOLS_HTML_ET_text;
            break;
        default:
            ;
        }
 
        if( pStr )
        {
            sOut += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_enctype "=\"") +
                pStr + "\"";
        }
    }
 
    aTmp = xFormPropSet->getPropertyValue( u"TargetFrame"_ustr );
    if( auto s = o3tl::tryAccess<OUString>(aTmp) )
    {
        if (!s->isEmpty() )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_target "=\"";
            Strm().WriteOString( sOut );
            HTMLOutFuncs::Out_String( Strm(), *s );
            sOut = "\""_ostr;
        }
    }
 
    Strm().WriteOString( sOut );
    uno::Reference< form::XFormComponent > xFormComp( rFormComps, uno::UNO_QUERY );
    lcl_html_outEvents( Strm(), xFormComp, m_bCfgStarBasic );
    Strm().WriteChar( '>' );
 
    IncIndentLevel(); // indent content of form
    SetLFPossible(true);
}
 
void SwHTMLWriter::OutHiddenControls(
        const uno::Reference< container::XIndexContainer > & rFormComps,
        const uno::Reference< beans::XPropertySet > & rPropSet )
{
    sal_Int32 nCount = rFormComps->getCount();
    sal_Int32 nPos = 0;
    if( rPropSet.is() )
    {
        bool bDone = false;
 
        uno::Reference< form::XFormComponent > xFC( rPropSet, uno::UNO_QUERY );
        for( nPos=0; !bDone && nPos < nCount; nPos++ )
        {
            uno::Any aTmp = rFormComps->getByIndex( nPos );
            auto x = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(aTmp);
            OSL_ENSURE( x,
                    "OutHiddenControls: wrong reflection" );
            bDone = x && *x == xFC;
        }
    }
 
    for( ; nPos < nCount; nPos++ )
    {
        uno::Any aTmp = rFormComps->getByIndex( nPos );
        auto xFC = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(aTmp);
        OSL_ENSURE( xFC,
                "OutHiddenControls: wrong reflection" );
        if( !xFC )
            continue;
        uno::Reference< beans::XPropertySet > xPropSet( *xFC, uno::UNO_QUERY );
 
        OUString sPropName = u"ClassId"_ustr;
        if( !xPropSet->getPropertySetInfo()->hasPropertyByName( sPropName ) )
            continue;
 
        aTmp = xPropSet->getPropertyValue( sPropName );
        auto n = o3tl::tryAccess<sal_Int16>(aTmp);
        if( !n )
            continue;
 
        if( form::FormComponentType::HIDDENCONTROL == *n )
        {
            if (IsLFPossible())
                OutNewLine( true );
            OString sOut = "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_input " "
                OOO_STRING_SVTOOLS_HTML_O_type "=\""
                OOO_STRING_SVTOOLS_HTML_IT_hidden "\"";
 
            aTmp = xPropSet->getPropertyValue( u"Name"_ustr );
            if( auto s = o3tl::tryAccess<OUString>(aTmp) )
            {
                if( !s->isEmpty() )
                {
                    sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\"";
                    Strm().WriteOString( sOut );
                    HTMLOutFuncs::Out_String( Strm(), *s );
                    sOut = "\""_ostr;
                }
            }
            aTmp = xPropSet->getPropertyValue( u"HiddenValue"_ustr );
            if( auto s = o3tl::tryAccess<OUString>(aTmp) )
            {
                if( !s->isEmpty() )
                {
                    sOut += " " OOO_STRING_SVTOOLS_HTML_O_value "=\"";
                    Strm().WriteOString( sOut );
                    HTMLOutFuncs::Out_String( Strm(), *s );
                    sOut = "\""_ostr;
                }
            }
            sOut += "/>";
            Strm().WriteOString( sOut );
 
            m_nFormCntrlCnt++;
        }
        else if( lcl_html_isHTMLControl( *n ) )
        {
            break;
        }
    }
}
 
// here are the output routines, thus the form::Forms are bundled:
 
const SdrObject *SwHTMLWriter::GetHTMLControl( const SwDrawFrameFormat& rFormat )
{
    // it must be a Draw-Format
    OSL_ENSURE( RES_DRAWFRMFMT == rFormat.Which(),
            "GetHTMLControl only allow for Draw-Formats" );
 
    // Look if a SdrObject exists for it
    const SdrObject *pObj = rFormat.FindSdrObject();
    if( !pObj || SdrInventor::FmForm != pObj->GetObjInventor() )
        return nullptr;
 
    const SdrUnoObj& rFormObj = dynamic_cast<const SdrUnoObj&>(*pObj);
    const uno::Reference< awt::XControlModel >&  xControlModel =
            rFormObj.GetUnoControlModel();
 
    OSL_ENSURE( xControlModel.is(), "UNO-Control without model" );
    if( !xControlModel.is() )
        return nullptr;
 
    uno::Reference< beans::XPropertySet >  xPropSet( xControlModel, uno::UNO_QUERY );
 
    OUString sPropName(u"ClassId"_ustr);
    if( !xPropSet->getPropertySetInfo()->hasPropertyByName( sPropName ) )
        return nullptr;
 
    uno::Any aTmp = xPropSet->getPropertyValue( sPropName );
    if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
    {
        if( lcl_html_isHTMLControl( *n ) )
        {
            return pObj;
        }
    }
 
    return nullptr;
}
 
static void GetControlSize(const SdrUnoObj& rFormObj, Size& rSz, SwDoc *pDoc)
{
    SwViewShell *pVSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
    if( !pVSh )
        return;
 
    uno::Reference< awt::XControl >  xControl;
    SdrView* pDrawView = pVSh->GetDrawView();
    OSL_ENSURE( pDrawView && pVSh->GetWin(), "no DrawView or window!" );
    if ( pDrawView && pVSh->GetWin() )
        xControl = rFormObj.GetUnoControl( *pDrawView, *pVSh->GetWin()->GetOutDev() );
    uno::Reference< awt::XTextLayoutConstrains > xLC( xControl, uno::UNO_QUERY );
    OSL_ENSURE( xLC.is(), "no XTextLayoutConstrains" );
    if( !xLC.is() )
        return;
 
    sal_Int16 nCols=0, nLines=0;
    xLC->getColumnsAndLines( nCols, nLines );
    rSz.setWidth( nCols );
    rSz.setHeight( nLines );
}
 
SwHTMLWriter& OutHTML_DrawFrameFormatAsControl( SwHTMLWriter& rWrt,
                                     const SwDrawFrameFormat& rFormat,
                                     const SdrUnoObj& rFormObj,
                                     bool bInCntnr )
{
    const uno::Reference< awt::XControlModel >& xControlModel =
        rFormObj.GetUnoControlModel();
 
    OSL_ENSURE( xControlModel.is(), "UNO-Control without model" );
    if( !xControlModel.is() )
        return rWrt;
 
    uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY );
    uno::Reference< beans::XPropertySetInfo > xPropSetInfo =
            xPropSet->getPropertySetInfo();
 
    rWrt.m_nFormCntrlCnt++;
 
    enum Tag { TAG_INPUT, TAG_SELECT, TAG_TEXTAREA, TAG_NONE };
    static char const * const TagNames[] = {
        OOO_STRING_SVTOOLS_HTML_input, OOO_STRING_SVTOOLS_HTML_select,
        OOO_STRING_SVTOOLS_HTML_textarea };
    Tag eTag = TAG_INPUT;
    enum Type {
        TYPE_TEXT, TYPE_PASSWORD, TYPE_CHECKBOX, TYPE_RADIO, TYPE_FILE,
        TYPE_SUBMIT, TYPE_IMAGE, TYPE_RESET, TYPE_BUTTON, TYPE_NONE };
    static char const * const TypeNames[] = {
        OOO_STRING_SVTOOLS_HTML_IT_text, OOO_STRING_SVTOOLS_HTML_IT_password,
        OOO_STRING_SVTOOLS_HTML_IT_checkbox, OOO_STRING_SVTOOLS_HTML_IT_radio,
        OOO_STRING_SVTOOLS_HTML_IT_file, OOO_STRING_SVTOOLS_HTML_IT_submit,
        OOO_STRING_SVTOOLS_HTML_IT_image, OOO_STRING_SVTOOLS_HTML_IT_reset,
        OOO_STRING_SVTOOLS_HTML_IT_button };
    Type eType = TYPE_NONE;
    OUString sValue;
    OString sOptions;
    bool bEmptyValue = false;
    uno::Any aTmp = xPropSet->getPropertyValue( u"ClassId"_ustr );
    sal_Int16 nClassId = *o3tl::doAccess<sal_Int16>(aTmp);
    HtmlFrmOpts nFrameOpts = HTML_FRMOPTS_CONTROL;
    switch( nClassId )
    {
    case form::FormComponentType::CHECKBOX:
    case form::FormComponentType::RADIOBUTTON:
        eType = (form::FormComponentType::CHECKBOX == nClassId
                    ? TYPE_CHECKBOX : TYPE_RADIO);
        aTmp = xPropSet->getPropertyValue( u"DefaultState"_ustr );
        if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
        {
            if ( TRISTATE_FALSE != *n )
            {
                sOptions += " " OOO_STRING_SVTOOLS_HTML_O_checked "=\""
                    OOO_STRING_SVTOOLS_HTML_O_checked
                    "\"";
            }
        }
 
        aTmp = xPropSet->getPropertyValue( u"RefValue"_ustr );
        if( auto rVal = o3tl::tryAccess<OUString>(aTmp) )
 
        {
            if( rVal->isEmpty() )
                bEmptyValue = true;
            else if( *rVal != OOO_STRING_SVTOOLS_HTML_on )
                sValue = *rVal;
        }
        break;
 
    case form::FormComponentType::COMMANDBUTTON:
        {
            form::FormButtonType eButtonType = form::FormButtonType_PUSH;
            aTmp = xPropSet->getPropertyValue( u"ButtonType"_ustr );
            if( auto t = o3tl::tryAccess<form::FormButtonType>(aTmp) )
                eButtonType = *t;
 
            switch( eButtonType )
            {
            case form::FormButtonType_RESET:
                eType = TYPE_RESET;
                break;
            case form::FormButtonType_SUBMIT:
                eType = TYPE_SUBMIT;
                break;
            case form::FormButtonType_PUSH:
            default:
                eType = TYPE_BUTTON;
            }
 
            aTmp = xPropSet->getPropertyValue( u"Label"_ustr );
            if( auto s = o3tl::tryAccess<OUString>(aTmp) )
            {
                if( !s->isEmpty() )
                {
                    sValue = *s;
                }
            }
        }
        break;
 
    case form::FormComponentType::LISTBOX:
        if (rWrt.IsLFPossible())
            rWrt.OutNewLine( true );
        eTag = TAG_SELECT;
        aTmp = xPropSet->getPropertyValue( u"Dropdown"_ustr );
        if( auto b1 = o3tl::tryAccess<bool>(aTmp) )
        {
            if( !*b1 )
            {
                Size aSz( 0, 0 );
                GetControlSize( rFormObj, aSz, rWrt.m_pDoc );
 
                // How many are visible ??
                if( aSz.Height() )
                {
                    sOptions += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" +
                        OString::number(static_cast<sal_Int32>(aSz.Height())) + "\"";
                }
 
                auto aTmp2 = xPropSet->getPropertyValue( u"MultiSelection"_ustr );
                if( auto b2 = o3tl::tryAccess<bool>(aTmp2) )
                {
                    if ( *b2 )
                    {
                        sOptions += " " OOO_STRING_SVTOOLS_HTML_O_multiple;
                    }
                }
            }
        }
        break;
 
    case form::FormComponentType::TEXTFIELD:
        {
            Size aSz( 0, 0 );
            GetControlSize( rFormObj, aSz, rWrt.m_pDoc );
 
            bool bMultiLine = false;
            OUString sMultiLine(u"MultiLine"_ustr);
            if( xPropSetInfo->hasPropertyByName( sMultiLine ) )
            {
                aTmp = xPropSet->getPropertyValue( sMultiLine );
                std::optional<const bool> b = o3tl::tryAccess<bool>(aTmp);
                bMultiLine = b.has_value() && *b;
            }
 
            if( bMultiLine )
            {
                if (rWrt.IsLFPossible())
                    rWrt.OutNewLine( true );
                eTag = TAG_TEXTAREA;
 
                if( aSz.Height() )
                {
                    sOptions += " " OOO_STRING_SVTOOLS_HTML_O_rows "=\"" +
                        OString::number(static_cast<sal_Int32>(aSz.Height())) + "\"";
                }
                if( aSz.Width() )
                {
                    sOptions += " " OOO_STRING_SVTOOLS_HTML_O_cols "=\"" +
                        OString::number(static_cast<sal_Int32>(aSz.Width())) + "\"";
                }
 
                aTmp = xPropSet->getPropertyValue( u"HScroll"_ustr );
                if( aTmp.getValueType() == cppu::UnoType<void>::get() ||
                    (aTmp.getValueType() == cppu::UnoType<bool>::get() &&
                    !*o3tl::forceAccess<bool>(aTmp)) )
                {
                    const char *pWrapStr = nullptr;
                    auto aTmp2 = xPropSet->getPropertyValue( u"HardLineBreaks"_ustr );
                    std::optional<const bool> b = o3tl::tryAccess<bool>(aTmp2);
                    pWrapStr = (b.has_value() && *b) ? OOO_STRING_SVTOOLS_HTML_WW_hard
                                         : OOO_STRING_SVTOOLS_HTML_WW_soft;
                    sOptions += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_wrap "=\"") +
                        pWrapStr + "\"";
                }
            }
            else
            {
                eType = TYPE_TEXT;
                OUString sEchoChar(u"EchoChar"_ustr);
                if( xPropSetInfo->hasPropertyByName( sEchoChar ) )
                {
                    aTmp = xPropSet->getPropertyValue( sEchoChar );
                    if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
                    {
                        if( *n != 0 )
                            eType = TYPE_PASSWORD;
                    }
                }
 
                if( aSz.Width() )
                {
                    sOptions += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" +
                        OString::number(static_cast<sal_Int32>(aSz.Width())) + "\"";
                }
 
                aTmp = xPropSet->getPropertyValue( u"MaxTextLen"_ustr );
                if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
                {
                    if( *n != 0 )
                    {
                        sOptions += " " OOO_STRING_SVTOOLS_HTML_O_maxlength "=\"" +
                            OString::number(static_cast<sal_Int32>(*n)) + "\"";
                    }
                }
 
                if( xPropSetInfo->hasPropertyByName( u"DefaultText"_ustr ) )
                {
                    aTmp = xPropSet->getPropertyValue( u"DefaultText"_ustr );
                    if( auto s = o3tl::tryAccess<OUString>(aTmp) )
                    {
                        if( !s->isEmpty() )
                        {
                            sValue = *s;
                        }
                    }
                }
            }
        }
        break;
 
    case form::FormComponentType::FILECONTROL:
        {
            Size aSz( 0, 0 );
            GetControlSize( rFormObj, aSz, rWrt.m_pDoc );
            eType = TYPE_FILE;
 
            if( aSz.Width() )
            {
                sOptions += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" +
                    OString::number(static_cast<sal_Int32>(aSz.Width())) + "\"";
            }
 
            // VALUE vim form: don't export because of security reasons
        }
        break;
 
    case form::FormComponentType::IMAGEBUTTON:
        eType = TYPE_IMAGE;
        nFrameOpts = HTML_FRMOPTS_IMG_CONTROL;
        break;
 
    default:                // doesn't know HTML
        eTag = TAG_NONE;    // therefore skip it
        break;
    }
 
    if( eTag == TAG_NONE )
        return rWrt;
 
    const OString tag = rWrt.GetNamespace() + TagNames[eTag];
    OString sOut = OString::Concat("<") + tag;
    if( eType != TYPE_NONE )
    {
        sOut += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_type "=\"") +
            TypeNames[eType] + "\"";
    }
 
    aTmp = xPropSet->getPropertyValue(u"Name"_ustr);
    if( auto s = o3tl::tryAccess<OUString>(aTmp) )
    {
        if( !s->isEmpty() )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\"";
            rWrt.Strm().WriteOString( sOut );
            HTMLOutFuncs::Out_String( rWrt.Strm(), *s );
            sOut = "\""_ostr;
        }
    }
 
    aTmp = xPropSet->getPropertyValue(u"Enabled"_ustr);
    if( auto b = o3tl::tryAccess<bool>(aTmp) )
    {
        if( !*b )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_disabled;
        }
    }
 
    if( !sValue.isEmpty() || bEmptyValue )
    {
        sOut += " " OOO_STRING_SVTOOLS_HTML_O_value "=\"";
        rWrt.Strm().WriteOString( sOut );
        HTMLOutFuncs::Out_String( rWrt.Strm(), sValue );
        sOut = "\""_ostr;
    }
 
    sOut += " " + sOptions;
 
    if( TYPE_IMAGE == eType )
    {
        aTmp = xPropSet->getPropertyValue( u"ImageURL"_ustr );
        if( auto s = o3tl::tryAccess<OUString>(aTmp) )
        {
            if( !s->isEmpty() )
            {
                sOut += " " OOO_STRING_SVTOOLS_HTML_O_src "=\"";
                rWrt.Strm().WriteOString( sOut );
 
                HTMLOutFuncs::Out_String(rWrt.Strm(), rWrt.normalizeURL(*s, false));
                sOut = "\""_ostr;
            }
        }
 
        Size aPixelSz(SwHTMLWriter::ToPixel(rFormObj.GetLogicRect().GetSize()));
 
        if( aPixelSz.Width() )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_width "=\"" +
                OString::number(static_cast<sal_Int32>(aPixelSz.Width())) + "\"";
        }
 
        if( aPixelSz.Height() )
        {
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_height "=\"" +
                OString::number(static_cast<sal_Int32>(aPixelSz.Height())) + "\"";
        }
    }
 
    aTmp = xPropSet->getPropertyValue( u"TabIndex"_ustr );
    if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
    {
        sal_Int16 nTabIndex = *n;
        if( nTabIndex > 0 )
        {
            if( nTabIndex >= 32767 )
                nTabIndex = 32767;
 
            sOut += " " OOO_STRING_SVTOOLS_HTML_O_tabindex "=\"" +
                OString::number(static_cast<sal_Int32>(nTabIndex)) + "\"";
        }
    }
 
    if( !sOut.isEmpty() )
        rWrt.Strm().WriteOString( sOut );
 
    OSL_ENSURE( !bInCntnr, "Container is not supported for Controls" );
    if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_DRAW ) && !bInCntnr )
    {
        // If Character-Objects can't be positioned absolutely,
        // then delete the corresponding flag.
        nFrameOpts |= (TYPE_IMAGE == eType
                            ? HTML_FRMOPTS_IMG_CONTROL_CSS1
                            : HTML_FRMOPTS_CONTROL_CSS1);
    }
    OString aEndTags;
    if( nFrameOpts != HtmlFrmOpts::NONE )
        aEndTags = rWrt.OutFrameFormatOptions(rFormat, u"", nFrameOpts);
 
    if( rWrt.m_bCfgOutStyles )
    {
        bool bEdit = TAG_TEXTAREA == eTag || TYPE_FILE == eType ||
                     TYPE_TEXT == eType;
 
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( rWrt.m_pDoc->GetAttrPool() );
        if( xPropSetInfo->hasPropertyByName( u"BackgroundColor"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"BackgroundColor"_ustr );
            if( auto n = o3tl::tryAccess<sal_Int32>(aTmp) )
            {
                Color aCol(ColorTransparency, *n);
                aItemSet.Put( SvxBrushItem( aCol, RES_CHRATR_BACKGROUND ) );
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"TextColor"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"TextColor"_ustr );
            if( auto n = o3tl::tryAccess<sal_Int32>(aTmp) )
            {
                Color aColor( ColorTransparency, *n );
                aItemSet.Put( SvxColorItem( aColor, RES_CHRATR_COLOR ) );
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"FontHeight"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"FontHeight"_ustr );
            if( auto nHeight = o3tl::tryAccess<float>(aTmp) )
 
            {
                if( *nHeight > 0  && (!bEdit || !rtl::math::approxEqual(*nHeight, 10.0)) )
                    aItemSet.Put( SvxFontHeightItem( sal_Int16(*nHeight * 20.), 100, RES_CHRATR_FONTSIZE ) );
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"FontName"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"FontName"_ustr );
            if( auto aFName = o3tl::tryAccess<OUString>(aTmp) )
            {
                if( !aFName->isEmpty() )
                {
                    vcl::Font aFixedFont( OutputDevice::GetDefaultFont(
                                        DefaultFontType::FIXED, LANGUAGE_ENGLISH_US,
                                        GetDefaultFontFlags::OnlyOne ) );
                    if( !bEdit || *aFName != aFixedFont.GetFamilyName() )
                    {
                        FontFamily eFamily = FAMILY_DONTKNOW;
                        if( xPropSetInfo->hasPropertyByName( u"FontFamily"_ustr ) )
                        {
                            auto aTmp2 = xPropSet->getPropertyValue( u"FontFamily"_ustr );
                            if( auto n = o3tl::tryAccess<sal_Int16>(aTmp2) )
                                eFamily = static_cast<FontFamily>(*n);
                        }
                        SvxFontItem aItem(eFamily, *aFName, OUString(), PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, RES_CHRATR_FONT);
                        aItemSet.Put( aItem );
                    }
                }
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"FontWeight"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"FontWeight"_ustr );
            if( auto x = o3tl::tryAccess<float>(aTmp) )
            {
                FontWeight eWeight =
                    vcl::unohelper::ConvertFontWeight( *x );
                if( eWeight != WEIGHT_DONTKNOW && eWeight != WEIGHT_NORMAL )
                    aItemSet.Put( SvxWeightItem( eWeight, RES_CHRATR_WEIGHT ) );
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"FontSlant"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"FontSlant"_ustr );
            if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
            {
                FontItalic eItalic = static_cast<FontItalic>(*n);
                if( eItalic != ITALIC_DONTKNOW && eItalic != ITALIC_NONE )
                    aItemSet.Put( SvxPostureItem( eItalic, RES_CHRATR_POSTURE ) );
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"FontLineStyle"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"FontLineStyle"_ustr );
            if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
            {
                FontLineStyle eUnderline = static_cast<FontLineStyle>(*n);
                if( eUnderline != LINESTYLE_DONTKNOW  &&
                    eUnderline != LINESTYLE_NONE )
                    aItemSet.Put( SvxUnderlineItem( eUnderline, RES_CHRATR_UNDERLINE ) );
            }
        }
        if( xPropSetInfo->hasPropertyByName( u"FontStrikeout"_ustr ) )
        {
            aTmp = xPropSet->getPropertyValue( u"FontStrikeout"_ustr );
            if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) )
            {
                FontStrikeout eStrikeout = static_cast<FontStrikeout>(*n);
                if( eStrikeout != STRIKEOUT_DONTKNOW &&
                    eStrikeout != STRIKEOUT_NONE )
                    aItemSet.Put( SvxCrossedOutItem( eStrikeout, RES_CHRATR_CROSSEDOUT ) );
            }
        }
 
        rWrt.OutCSS1_FrameFormatOptions( rFormat, nFrameOpts, &rFormObj,
                                        &aItemSet );
    }
 
    uno::Reference< form::XFormComponent >  xFormComp( xControlModel, uno::UNO_QUERY );
    lcl_html_outEvents( rWrt.Strm(), xFormComp, rWrt.m_bCfgStarBasic );
 
    rWrt.Strm().WriteChar( '>' );
 
    if( TAG_SELECT == eTag )
    {
        aTmp = xPropSet->getPropertyValue( u"StringItemList"_ustr );
        if( auto aList = o3tl::tryAccess<uno::Sequence<OUString>>(aTmp) )
        {
            rWrt.IncIndentLevel(); // the content of Select can be indented
            sal_Int32 nCnt = aList->getLength();
            const OUString *pStrings = aList->getConstArray();
 
            const OUString *pValues = nullptr;
            sal_Int32 nValCnt = 0;
            auto aTmp2 = xPropSet->getPropertyValue( u"ListSource"_ustr );
            uno::Sequence<OUString> aValList;
            if( auto s = o3tl::tryAccess<uno::Sequence<OUString>>(aTmp2) )
            {
                aValList = *s;
                nValCnt = aValList.getLength();
                pValues = aValList.getConstArray();
            }
 
            uno::Any aSelTmp = xPropSet->getPropertyValue( u"DefaultSelection"_ustr );
            const sal_Int16 *pSels = nullptr;
            sal_Int32 nSel = 0;
            sal_Int32 nSelCnt = 0;
            uno::Sequence<sal_Int16> aSelList;
            if( auto s = o3tl::tryAccess<uno::Sequence<sal_Int16>>(aSelTmp) )
            {
                aSelList = *s;
                nSelCnt = aSelList.getLength();
                pSels = aSelList.getConstArray();
            }
 
            for( sal_Int32 i = 0; i < nCnt; i++ )
            {
                OUString sVal;
                bool bSelected = false, bEmptyVal = false;
                if( i < nValCnt )
                {
                    const OUString& rVal = pValues[i];
                    if( rVal == "$$$empty$$$" )
                        bEmptyVal = true;
                    else
                        sVal = rVal;
                }
 
                bSelected = (nSel < nSelCnt) && pSels[nSel] == i;
                if( bSelected )
                    nSel++;
 
                rWrt.OutNewLine(); // every Option gets its own line
                sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_option;
                if( !sVal.isEmpty() || bEmptyVal )
                {
                    sOut += " " OOO_STRING_SVTOOLS_HTML_O_value "=\"";
                    rWrt.Strm().WriteOString( sOut );
                    HTMLOutFuncs::Out_String( rWrt.Strm(), sVal );
                    sOut = "\""_ostr;
                }
                if( bSelected )
                    sOut += " " OOO_STRING_SVTOOLS_HTML_O_selected;
 
                sOut += ">";
                rWrt.Strm().WriteOString( sOut );
 
                HTMLOutFuncs::Out_String( rWrt.Strm(), pStrings[i] );
            }
            HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_option), false );
 
            rWrt.DecIndentLevel();
            rWrt.OutNewLine();// the </SELECT> gets its own line
        }
    }
    else if( TAG_TEXTAREA == eTag )
    {
        // In TextAreas no additional spaces or LF may be exported!
        OUString sVal;
        aTmp = xPropSet->getPropertyValue( u"DefaultText"_ustr );
        if( auto s = o3tl::tryAccess<OUString>(aTmp) )
        {
            if( !s->isEmpty() )
            {
                sVal = *s;
            }
        }
        if( !sVal.isEmpty() )
        {
            sVal = convertLineEnd(sVal, LINEEND_LF);
            sal_Int32 nPos = 0;
            while ( nPos != -1 )
            {
                if( nPos )
                    rWrt.Strm().WriteOString( SAL_NEWLINE_STRING );
                OUString aLine = sVal.getToken( 0, 0x0A, nPos );
                HTMLOutFuncs::Out_String( rWrt.Strm(), aLine );
            }
        }
    }
    else if( TYPE_CHECKBOX == eType || TYPE_RADIO == eType )
    {
        aTmp = xPropSet->getPropertyValue(u"Label"_ustr);
        if( auto s = o3tl::tryAccess<OUString>(aTmp) )
        {
            if( !s->isEmpty() )
            {
                HTMLOutFuncs::Out_String( rWrt.Strm(), *s ).WriteChar( ' ' );
            }
        }
    }
    HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), tag, false);
 
    if( !aEndTags.isEmpty() )
        rWrt.Strm().WriteOString( aEndTags );
 
    // Controls aren't bound to a paragraph, therefore don't output LF anymore!
    rWrt.SetLFPossible(false);
 
    if( rWrt.mxFormComps.is() )
        rWrt.OutHiddenControls( rWrt.mxFormComps, xPropSet );
    return rWrt;
}
 
/**
 * Find out if a format belongs to a control and if yes return its form.
 */
static void AddControl( HTMLControls& rControls,
                        const SdrUnoObj& rFormObj,
                        SwNodeOffset nNodeIdx )
{
    const uno::Reference< awt::XControlModel >& xControlModel =
            rFormObj.GetUnoControlModel();
    if( !xControlModel.is() )
        return;
 
    uno::Reference< form::XFormComponent >  xFormComp( xControlModel, uno::UNO_QUERY );
    uno::Reference< uno::XInterface >  xIfc = xFormComp->getParent();
    uno::Reference< form::XForm >  xForm(xIfc, uno::UNO_QUERY);
 
    OSL_ENSURE( xForm.is(), "Where is the form?" );
    if( xForm.is() )
    {
        uno::Reference< container::XIndexContainer >  xFormComps( xForm, uno::UNO_QUERY );
        std::unique_ptr<HTMLControl> pHCntrl(new HTMLControl( xFormComps, nNodeIdx ));
        auto itPair = rControls.insert( std::move(pHCntrl) );
        if (!itPair.second )
        {
            if( (*itPair.first)->xFormComps==xFormComps )
                (*itPair.first)->nCount++;
        }
    }
}
 
void SwHTMLWriter::GetControls()
{
    // Idea: first off collect the paragraph- and character-bound controls.
    // In the process for every control the paragraph position and VCForm are
    // saved in an array.
    // With that array it's possible to find out where form::Forms must be
    // opened and closed.
 
    // collect the paragraph-bound controls
    for( size_t i=0; i<m_aHTMLPosFlyFrames.size(); i++ )
    {
        const SwHTMLPosFlyFrame* pPosFlyFrame = m_aHTMLPosFlyFrames[ i ].get();
        if( HtmlOut::Control != pPosFlyFrame->GetOutFn() )
            continue;
 
        const SdrObject *pSdrObj = pPosFlyFrame->GetSdrObject();
        OSL_ENSURE( pSdrObj, "Where is the SdrObject?" );
        if( !pSdrObj )
            continue;
 
        AddControl( m_aHTMLControls, dynamic_cast<const SdrUnoObj&>(*pSdrObj),
                    pPosFlyFrame->GetNdIndex().GetIndex() );
    }
 
    // and now the ones in a character-bound frame
    for(sw::SpzFrameFormat* pSpz: *m_pDoc->GetSpzFrameFormats())
    {
        if( RES_DRAWFRMFMT != pSpz->Which() )
            continue;
 
        const SwFormatAnchor& rAnchor = pSpz->GetAnchor();
        const SwNode *pAnchorNode = rAnchor.GetAnchorNode();
        if ((RndStdIds::FLY_AS_CHAR != rAnchor.GetAnchorId()) || !pAnchorNode)
            continue;
 
        const SdrObject *pSdrObj =
            SwHTMLWriter::GetHTMLControl(*static_cast<SwDrawFrameFormat*>(pSpz) );
        if( !pSdrObj )
            continue;
 
        AddControl( m_aHTMLControls, dynamic_cast<const SdrUnoObj&>(*pSdrObj), pAnchorNode->GetIndex() );
    }
}
 
HTMLControl::HTMLControl(
        uno::Reference< container::XIndexContainer > _xFormComps,
        SwNodeOffset nIdx ) :
    xFormComps(std::move( _xFormComps )), nNdIdx( nIdx ), nCount( 1 )
{}
 
HTMLControl::~HTMLControl()
{}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1048 The 'nTabIndex' variable was assigned the same value.