/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <sal/config.h>
 
#include <algorithm>
#include <memory>
#include <config_java.h>
 
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <comphelper/string.hxx>
#include <o3tl/safeint.hxx>
#include <rtl/ustrbuf.hxx>
#include <svx/svxids.hrc>
#include <svx/svdotext.hxx>
#if OSL_DEBUG_LEVEL > 0
#include <stdlib.h>
#endif
#include <hintids.hxx>
 
#include <utility>
#include <vcl/errinf.hxx>
#include <svl/stritem.hxx>
#include <vcl/imap.hxx>
#include <svtools/htmltokn.h>
#include <svtools/htmlkywd.hxx>
#include <svtools/ctrltool.hxx>
#include <unotools/pathoptions.hxx>
#include <vcl/svapp.hxx>
#include <sfx2/event.hxx>
#include <sfx2/docfile.hxx>
 
#include <sfx2/linkmgr.hxx>
#include <editeng/kernitem.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/blinkitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/protitem.hxx>
#include <editeng/flstitem.hxx>
#include <svx/unobrushitemhelper.hxx>
 
#include <frmatr.hxx>
#include <charatr.hxx>
#include <fmtfld.hxx>
#include <fmtpdsc.hxx>
#include <fmtanchr.hxx>
#include <fmtsrnd.hxx>
#include <fmtfsize.hxx>
#include <fmtclds.hxx>
#include <fchrfmt.hxx>
#include <fmtinfmt.hxx>
#include <fmtfollowtextflow.hxx>
#include <fmtornt.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentLinksAdministration.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <IDocumentStatistics.hxx>
#include <IDocumentState.hxx>
#include <pam.hxx>
#include <ndtxt.hxx>
#include <mdiexp.hxx>
#include <poolfmt.hxx>
#include <pagedesc.hxx>
#include <IMark.hxx>
#include <docsh.hxx>
#include <editsh.hxx>
#include <docufld.hxx>
#include "swcss1.hxx"
#include <fltini.hxx>
#include <htmltbl.hxx>
#include "htmlnum.hxx"
#include "swhtml.hxx"
#include "wrthtml.hxx"
#include <linkenum.hxx>
#include <breakit.hxx>
#include <SwAppletImpl.hxx>
#include <swdll.hxx>
#include <txatbase.hxx>
 
#include <sfx2/viewfrm.hxx>
#include <svx/svdobj.hxx>
#include <officecfg/Office/Writer.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <comphelper/sequence.hxx>
#include <officecfg/Office/Common.hxx>
 
#include <swerror.h>
#include <ndole.hxx>
#include <unoframe.hxx>
#include "css1atr.hxx"
#include <frameformats.hxx>
 
#define FONTSIZE_MASK           7
 
#define HTML_ESC_PROP 80
#define HTML_ESC_SUPER DFLT_ESC_SUPER
#define HTML_ESC_SUB DFLT_ESC_SUB
 
#define HTML_SPTYPE_BLOCK 1
#define HTML_SPTYPE_HORI 2
#define HTML_SPTYPE_VERT 3
 
using editeng::SvxBorderLine;
using namespace ::com::sun::star;
 
// <P ALIGN=xxx>, <Hn ALIGN=xxx>, <TD ALIGN=xxx> etc.
HTMLOptionEnum<SvxAdjust> const aHTMLPAlignTable[] =
{
    { OOO_STRING_SVTOOLS_HTML_AL_left,    SvxAdjust::Left     },
    { OOO_STRING_SVTOOLS_HTML_AL_center,  SvxAdjust::Center   },
    { OOO_STRING_SVTOOLS_HTML_AL_middle,  SvxAdjust::Center   }, // Netscape
    { OOO_STRING_SVTOOLS_HTML_AL_right,   SvxAdjust::Right    },
    { OOO_STRING_SVTOOLS_HTML_AL_justify, SvxAdjust::Block    },
    { OOO_STRING_SVTOOLS_HTML_AL_char,    SvxAdjust::Left     },
    { nullptr,                            SvxAdjust(0)        }
};
 
// <SPACER TYPE=...>
HTMLOptionEnum<sal_uInt16> const aHTMLSpacerTypeTable[] =
{
    { OOO_STRING_SVTOOLS_HTML_SPTYPE_block,      HTML_SPTYPE_BLOCK       },
    { OOO_STRING_SVTOOLS_HTML_SPTYPE_horizontal, HTML_SPTYPE_HORI        },
    { OOO_STRING_SVTOOLS_HTML_SPTYPE_vertical,   HTML_SPTYPE_VERT        },
    { nullptr,                                   0                       }
};
 
HTMLReader::HTMLReader()
{
    m_bTemplateBrowseMode = true;
}
 
OUString HTMLReader::GetTemplateName(SwDoc& rDoc) const
{
    if (!rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE))
        // HTML import into Writer, avoid loading the Writer/Web template.
        return OUString();
 
    static constexpr OUString sTemplateWithoutExt(u"internal/html"_ustr);
    SvtPathOptions aPathOpt;
 
    // first search for OpenDocument Writer/Web template
    // OpenDocument Writer/Web template (extension .oth)
    OUString sTemplate( sTemplateWithoutExt + ".oth" );
    if (aPathOpt.SearchFile( sTemplate, SvtPathOptions::Paths::Template ))
        return sTemplate;
 
        // no OpenDocument Writer/Web template found.
        // search for OpenOffice.org Writer/Web template
    sTemplate = sTemplateWithoutExt + ".stw";
    if (aPathOpt.SearchFile( sTemplate, SvtPathOptions::Paths::Template ))
        return sTemplate;
 
    OSL_ENSURE( false, "The default HTML template cannot be found in the defined template directories!");
 
    return OUString();
}
 
bool HTMLReader::SetStrmStgPtr()
{
    assert(m_pMedium && "Where is the medium??");
 
    if( m_pMedium->IsRemote() || !m_pMedium->IsStorage() )
    {
        m_pStream = m_pMedium->GetInStream();
        return true;
    }
    return false;
 
}
 
// Call for the general Reader-Interface
ErrCodeMsg HTMLReader::Read( SwDoc &rDoc, const OUString& rBaseURL, SwPaM &rPam, const OUString & rName )
{
    SetupFilterOptions();
 
    if( !m_pStream )
    {
        OSL_ENSURE( m_pStream, "HTML-Read without stream" );
        return ERR_SWG_READ_ERROR;
    }
 
    if( !m_bInsertMode )
    {
        Reader::ResetFrameFormats( rDoc );
 
        // Set the HTML page style, when it isn't a HTML document,
        // otherwise it's already set.
        if( !rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE) && m_aNamespace != "reqif-xhtml" )
        {
            rDoc.getIDocumentContentOperations().InsertPoolItem( rPam, SwFormatPageDesc(
                rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false )) );
        }
    }
 
    // so nobody steals the document!
    rtl::Reference<SwDoc> xHoldAlive(&rDoc);
    ErrCodeMsg nRet = ERRCODE_NONE;
    tools::SvRef<SwHTMLParser> xParser = new SwHTMLParser( &rDoc, rPam, *m_pStream,
                                            rName, rBaseURL, !m_bInsertMode, m_pMedium,
                                            IsReadUTF8(),
                                            m_bIgnoreHTMLComments, m_aNamespace );
 
    SvParserState eState = xParser->CallParser();
 
    if( SvParserState::Pending == eState )
        m_pStream->ResetError();
    else if( SvParserState::Accepted != eState )
    {
        const OUString sErr(OUString::number(static_cast<sal_Int32>(xParser->GetLineNr()))
            + "," + OUString::number(static_cast<sal_Int32>(xParser->GetLinePos())));
 
        // use the stream as transport for error number
        nRet = ErrCodeMsg( ERR_FORMAT_ROWCOL, sErr,
                                    DialogMask::ButtonsOk | DialogMask::MessageError );
    }
 
    return nRet;
}
 
SwHTMLParser::SwHTMLParser( SwDoc* pD, SwPaM& rCursor, SvStream& rIn,
                            OUString aPath,
                            OUString aBaseURL,
                            bool bReadNewDoc,
                            SfxMedium* pMed, bool bReadUTF8,
                            bool bNoHTMLComments,
                            const OUString& rNamespace )
    : SfxHTMLParser( rIn, bReadNewDoc, pMed ),
    m_aPathToFile(std::move( aPath )),
    m_sBaseURL(std::move( aBaseURL )),
    m_xAttrTab(std::make_shared<HTMLAttrTable>()),
    m_pNumRuleInfo( new SwHTMLNumRuleInfo ),
    m_xDoc( pD ),
    m_pActionViewShell( nullptr ),
    m_pSttNdIdx( nullptr ),
    m_pFormImpl( nullptr ),
    m_pImageMap( nullptr ),
    m_nBaseFontStMin( 0 ),
    m_nFontStMin( 0 ),
    m_nDefListDeep( 0 ),
    m_nFontStHeadStart( 0 ),
    m_nSBModuleCnt( 0 ),
    m_nMissingImgMaps( 0 ),
    m_nParaCnt( 5 ),
    // #i83625#
    m_nContextStMin( 0 ),
    m_nContextStAttrMin( 0 ),
    m_nSelectEntryCnt( 0 ),
    m_nOpenParaToken( HtmlTokenId::NONE ),
    m_eJumpTo( JumpToMarks::NONE ),
#ifdef DBG_UTIL
    m_nContinue( 0 ),
#endif
    m_eParaAdjust( SvxAdjust::End ),
    m_bDocInitialized( false ),
    m_bSetModEnabled( false ),
    m_bInFloatingFrame( false ),
    m_bInField( false ),
    m_bKeepUnknown( false ),
    m_bCallNextToken( false ),
    m_bIgnoreRawData( false ),
    m_bLBEntrySelected ( false ),
    m_bTAIgnoreNewPara ( false ),
    m_bFixMarqueeWidth ( false ),
    m_bNoParSpace( false ),
    m_bInNoEmbed( false ),
    m_bInTitle( false ),
    m_bUpdateDocStat( false ),
    m_bFixSelectWidth( false ),
    m_bTextArea( false ),
    m_bSelect( false ),
    m_bInFootEndNoteAnchor( false ),
    m_bInFootEndNoteSymbol( false ),
    m_bIgnoreHTMLComments( bNoHTMLComments ),
    m_bRemoveHidden( false ),
    m_bBodySeen( false ),
    m_bReadingHeaderOrFooter( false ),
    m_bNotifyMacroEventRead( false ),
    m_isInTableStructure(false),
    m_nTableDepth( 0 ),
    m_nFloatingFrames( 0 ),
    m_nListItems( 0 ),
    m_pTempViewFrame(nullptr)
{
    // If requested explicitly, then force ignoring of comments (don't create postits for them).
    if (!bFuzzing)
    {
        if (officecfg::Office::Writer::Filter::Import::HTML::IgnoreComments::get())
            m_bIgnoreHTMLComments = true;
        m_bKeepUnknown = officecfg::Office::Common::Filter::HTML::Import::UnknownTag::get();
    }
 
    m_nEventId = nullptr;
    m_bUpperSpace = m_bViewCreated = m_bChkJumpMark = false;
 
    m_eScriptLang = HTMLScriptLanguage::Unknown;
 
    rCursor.DeleteMark();
    m_pPam = &rCursor; // re-use existing cursor: avoids spurious ~SwContentIndexReg assert
    memset(m_xAttrTab.get(), 0, sizeof(HTMLAttrTable));
 
    // Read the font sizes 1-7 from the INI file
    if (!bFuzzing)
    {
        m_aFontHeights[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20;
        m_aFontHeights[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20;
        m_aFontHeights[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20;
        m_aFontHeights[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20;
        m_aFontHeights[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20;
        m_aFontHeights[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20;
        m_aFontHeights[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20;
    }
    else
    {
        m_aFontHeights[0] = m_aFontHeights[1] = m_aFontHeights[2] = m_aFontHeights[3] =
        m_aFontHeights[4] = m_aFontHeights[5] = m_aFontHeights[6] = 12 * 20;
    }
 
    if(bReadNewDoc)
    {
        //CJK has different defaults, so a different object should be used for this
        //RES_CHARTR_CJK_FONTSIZE is a valid value
        SvxFontHeightItem aFontHeight(m_aFontHeights[2], 100, RES_CHRATR_FONTSIZE);
        m_xDoc->SetDefault( aFontHeight );
        SvxFontHeightItem aFontHeightCJK(m_aFontHeights[2], 100, RES_CHRATR_CJK_FONTSIZE);
        m_xDoc->SetDefault( aFontHeightCJK );
        SvxFontHeightItem aFontHeightCTL(m_aFontHeights[2], 100, RES_CHRATR_CTL_FONTSIZE);
        m_xDoc->SetDefault( aFontHeightCTL );
 
        // #i18732# - adjust default of option 'FollowTextFlow'
        // TODO: not sure what the appropriate default for HTML should be?
        m_xDoc->SetDefault( SwFormatFollowTextFlow(true) );
    }
 
    // Change to HTML mode during the import, so that the right styles are created
    m_bOldIsHTMLMode = m_xDoc->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE);
    m_xDoc->getIDocumentSettingAccess().set(DocumentSettingId::HTML_MODE, true);
 
    m_pCSS1Parser.reset(new SwCSS1Parser(m_xDoc.get(), *this, m_aFontHeights, m_sBaseURL, IsNewDoc()));
    if (!bFuzzing)
        m_pCSS1Parser->SetIgnoreFontFamily( officecfg::Office::Common::Filter::HTML::Import::FontSetting::get() );
 
    if( bReadUTF8 )
    {
        SetSrcEncoding( RTL_TEXTENCODING_UTF8 );
    }
    else
    {
        SwDocShell *pDocSh = m_xDoc->GetDocShell();
        SvKeyValueIterator *pHeaderAttrs =
            pDocSh->GetHeaderAttributes();
        if( pHeaderAttrs )
            SetEncodingByHTTPHeader( pHeaderAttrs );
    }
    m_pCSS1Parser->SetDfltEncoding( osl_getThreadTextEncoding() );
 
    SwDocShell* pDocSh = m_xDoc->GetDocShell();
    if( pDocSh )
    {
        m_bViewCreated = true;  // not, load synchronous
 
        // a jump mark is present
 
        if( pMed )
        {
            m_sJmpMark = pMed->GetURLObject().GetMark();
            if( !m_sJmpMark.isEmpty() )
            {
                m_eJumpTo = JumpToMarks::Mark;
                sal_Int32 nLastPos = m_sJmpMark.lastIndexOf( cMarkSeparator );
                sal_Int32 nPos =  nLastPos != -1 ? nLastPos : 0;
 
                OUString sCmp;
                if (nPos)
                {
                    sCmp = m_sJmpMark.copy(nPos + 1).replaceAll(" ", "");
                }
 
                if( !sCmp.isEmpty() )
                {
                    sCmp = sCmp.toAsciiLowerCase();
                    if( sCmp == "region" )
                        m_eJumpTo = JumpToMarks::Region;
                    else if( sCmp == "table" )
                        m_eJumpTo = JumpToMarks::Table;
                    else if( sCmp == "graphic" )
                        m_eJumpTo = JumpToMarks::Graphic;
                    else if( sCmp == "outline" ||
                            sCmp == "text" ||
                            sCmp == "frame" )
                        m_eJumpTo = JumpToMarks::NONE;  // this is nothing valid!
                    else
                        // otherwise this is a normal (book)mark
                        nPos = -1;
                }
                else
                    nPos = -1;
 
                if( nPos != -1 )
                    m_sJmpMark = m_sJmpMark.copy( 0, nPos );
                if( m_sJmpMark.isEmpty() )
                    m_eJumpTo = JumpToMarks::NONE;
            }
        }
    }
 
    if (!rNamespace.isEmpty())
    {
        SetNamespace(rNamespace);
        m_bXHTML = true;
        if (rNamespace == "reqif-xhtml")
            m_bReqIF = true;
    }
 
    // Extract load parameters which are specific to this filter.
    if (!pMed)
    {
        return;
    }
 
    comphelper::SequenceAsHashMap aLoadMap(pMed->GetArgs());
    auto it = aLoadMap.find(u"AllowedRTFOLEMimeTypes"_ustr);
    if (it == aLoadMap.end())
    {
        return;
    }
 
    uno::Sequence<OUString> aTypes;
    it->second >>= aTypes;
    m_aAllowedRTFOLEMimeTypes = comphelper::sequenceToContainer<std::set<OUString>>(aTypes);
}
 
SwHTMLParser::~SwHTMLParser()
{
#ifdef DBG_UTIL
    OSL_ENSURE( !m_nContinue, "DTOR in continue!" );
#endif
 
    OSL_ENSURE(m_aContexts.empty(), "There are still contexts on the stack");
    OSL_ENSURE(!m_nContextStMin, "There are protected contexts");
    m_nContextStMin = 0;
    while (!m_aContexts.empty())
    {
        std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
        ClearContext(xCntxt.get());
    }
 
    bool bAsync = m_xDoc->IsInLoadAsynchron();
    m_xDoc->SetInLoadAsynchron( false );
    m_xDoc->getIDocumentSettingAccess().set(DocumentSettingId::HTML_MODE, m_bOldIsHTMLMode);
 
    if( m_xDoc->GetDocShell() && m_nEventId )
        Application::RemoveUserEvent( m_nEventId );
 
    // the DocumentDetected maybe can delete the DocShells, therefore fetch again
    if (SwDocShell* pShell = m_xDoc->GetDocShell())
    {
        // update linked sections
        sal_uInt16 nLinkMode = m_xDoc->getIDocumentSettingAccess().getLinkUpdateMode( true );
        if( nLinkMode != NEVER && bAsync &&
            SfxObjectCreateMode::INTERNAL != pShell->GetCreateMode() )
        {
            SfxMedium * medium = pShell->GetMedium();
            m_xDoc->getIDocumentLinksAdministration().GetLinkManager().UpdateAllLinks( nLinkMode == MANUAL, false, nullptr, medium == nullptr ? OUString() : medium->GetName() );
        }
 
        if ( pShell->IsLoading() )
        {
            // #i59688#
            pShell->LoadingFinished();
        }
    }
 
    delete m_pSttNdIdx;
 
    if( !m_aSetAttrTab.empty() )
    {
        OSL_ENSURE( m_aSetAttrTab.empty(),"There are still attributes on the stack" );
        for ( const auto& rpAttr : m_aSetAttrTab )
            delete rpAttr;
        m_aSetAttrTab.clear();
    }
 
    m_pCSS1Parser.reset();
    m_pNumRuleInfo.reset();
    DeleteFormImpl();
    m_pFootEndNoteImpl.reset();
 
    OSL_ENSURE(!m_xTable, "It exists still an open table");
    m_pImageMaps.reset();
 
    OSL_ENSURE( m_vPendingStack.empty(),
            "SwHTMLParser::~SwHTMLParser: Here should not be Pending-Stack anymore" );
    m_vPendingStack.clear();
 
    m_xDoc.clear();
 
    if ( m_pTempViewFrame )
    {
        m_pTempViewFrame->DoClose();
 
        // the temporary view frame is hidden, so the hidden flag might need to be removed
        SwDocShell* pShell = m_xDoc.is() ? m_xDoc->GetDocShell() : nullptr;
        if ( m_bRemoveHidden && pShell && pShell->GetMedium() )
            pShell->GetMedium()->GetItemSet().ClearItem( SID_HIDDEN );
    }
}
 
IMPL_LINK_NOARG( SwHTMLParser, AsyncCallback, void*, void )
{
    m_nEventId=nullptr;
 
    // #i47907# - If the document has already been destructed,
    // the parser should be aware of this:
    SwDocShell* pShell = m_xDoc->GetDocShell();
    if( ( pShell && pShell->IsAbortingImport() )
        || 1 == m_xDoc->getReferenceCount() )
    {
        // was the import aborted by SFX?
        eState = SvParserState::Error;
    }
 
    GetAsynchCallLink().Call(nullptr);
}
 
SvParserState SwHTMLParser::CallParser()
{
    // create temporary index on position 0, so it won't be moved!
    m_pSttNdIdx = new SwNodeIndex( m_xDoc->GetNodes() );
    if( !IsNewDoc() )       // insert into existing document ?
    {
        const SwPosition* pPos = m_pPam->GetPoint();
 
        m_xDoc->getIDocumentContentOperations().SplitNode( *pPos, false );
 
        *m_pSttNdIdx = pPos->GetNodeIndex()-1;
        m_xDoc->getIDocumentContentOperations().SplitNode( *pPos, false );
 
        SwPaM aInsertionRangePam( *pPos );
 
        m_pPam->Move( fnMoveBackward );
 
        // split any redline over the insertion point
        aInsertionRangePam.SetMark();
        *aInsertionRangePam.GetPoint() = *m_pPam->GetPoint();
        aInsertionRangePam.Move( fnMoveBackward );
        m_xDoc->getIDocumentRedlineAccess().SplitRedline( aInsertionRangePam );
 
        if (SwAttrSet const*const pAttrs = pPos->GetNode().GetTextNode()->GetpSwAttrSet())
        {
            m_pTargetCharAttrs.reset(new SfxItemSet(*pAttrs->GetPool(),
                        svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END-1>));
            m_pTargetCharAttrs->Put(*pAttrs);
        }
 
        m_xDoc->SetTextFormatColl( *m_pPam,
                m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD ));
    }
 
    if( GetMedium() )
    {
        if( !m_bViewCreated )
        {
            m_nEventId = Application::PostUserEvent( LINK( this, SwHTMLParser, AsyncCallback ) );
        }
        else
        {
            m_bViewCreated = true;
            m_nEventId = nullptr;
        }
    }
    else // show progress bar
    {
        rInput.Seek(STREAM_SEEK_TO_END);
        rInput.ResetError();
 
        m_xProgress.reset(new ImportProgress(m_xDoc->GetDocShell(), 0, rInput.Tell()));
 
        rInput.Seek(STREAM_SEEK_TO_BEGIN);
        rInput.ResetError();
    }
 
    StartListening(m_xDoc->GetPageDesc( 0 ).GetNotifier());
 
    SvParserState eRet = HTMLParser::CallParser();
    return eRet;
}
 
bool SwHTMLParser::CanRemoveNode(SwNodeOffset nNodeIdx) const
{
    const SwNode *pPrev = m_xDoc->GetNodes()[nNodeIdx - 1];
    return pPrev->IsContentNode() || (pPrev->IsEndNode() && pPrev->StartOfSectionNode()->IsSectionNode());
}
 
void SwHTMLParser::Continue( HtmlTokenId nToken )
{
#ifdef DBG_UTIL
    OSL_ENSURE(!m_nContinue, "Continue in Continue - not supposed to happen");
    m_nContinue++;
#endif
 
    // When the import (of SFX) is aborted, an error will be set but
    // we still continue, so that we clean up properly.
    OSL_ENSURE( SvParserState::Error!=eState,
            "SwHTMLParser::Continue: already set an error" );
    SwDocShell* pShell = m_xDoc->GetDocShell();
    if( pShell && pShell->IsAbortingImport() )
        eState = SvParserState::Error;
 
    // Fetch SwViewShell from document, save it and set as current.
    SwViewShell *pInitVSh = CallStartAction();
 
    if( SvParserState::Error != eState && GetMedium() && !m_bViewCreated )
    {
        // At first call first return, show document and wait for callback
        // time.
        // At this point in CallParser only one digit was read and
        // a SaveState(0) was called.
        eState = SvParserState::Pending;
        m_bViewCreated = true;
        m_xDoc->SetInLoadAsynchron( true );
 
#ifdef DBG_UTIL
        m_nContinue--;
#endif
 
        return;
    }
 
    m_bSetModEnabled = false;
    if( pShell )
    {
        m_bSetModEnabled = pShell->IsEnableSetModified();
        if( m_bSetModEnabled )
        {
            pShell->EnableSetModified( false );
        }
    }
 
    // during import don't call OLE-Modified
    Link<bool,void> aOLELink( m_xDoc->GetOle2Link() );
    m_xDoc->SetOle2Link( Link<bool,void>() );
 
    bool bModified = m_xDoc->getIDocumentState().IsModified();
    bool const bWasUndo = m_xDoc->GetIDocumentUndoRedo().DoesUndo();
    m_xDoc->GetIDocumentUndoRedo().DoUndo(false);
 
    // When the import will be aborted, don't call Continue anymore.
    // If a Pending-Stack exists make sure the stack is ended with a call
    // of NextToken.
    if( SvParserState::Error == eState )
    {
        OSL_ENSURE( m_vPendingStack.empty() || m_vPendingStack.back().nToken != HtmlTokenId::NONE,
                "SwHTMLParser::Continue: Pending-Stack without Token" );
        if( !m_vPendingStack.empty() && m_vPendingStack.back().nToken != HtmlTokenId::NONE )
            NextToken( m_vPendingStack.back().nToken );
        OSL_ENSURE( m_vPendingStack.empty(),
                "SwHTMLParser::Continue: There is again a Pending-Stack" );
    }
    else
    {
        HTMLParser::Continue( !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : nToken );
    }
 
    // disable progress bar again
    m_xProgress.reset();
 
    bool bLFStripped = false;
    if( SvParserState::Pending != GetStatus() )
    {
        // set the last attributes yet
        {
            if( !m_aScriptSource.isEmpty() )
            {
                SwScriptFieldType *pType =
                    static_cast<SwScriptFieldType*>(m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Script ));
 
                SwScriptField aField( pType, m_aScriptType, m_aScriptSource,
                                    false );
                InsertAttr( SwFormatField( aField ), false );
            }
 
            if( m_pAppletImpl )
            {
                if( m_pAppletImpl->GetApplet().is() )
                    EndApplet();
                else
                    EndObject();
            }
 
            // maybe remove an existing LF after the last paragraph
            if( IsNewDoc() )
                bLFStripped = StripTrailingLF() > 0;
 
            // close still open numbering
            while( GetNumInfo().GetNumRule() )
                EndNumberBulletList();
 
            OSL_ENSURE( !m_nContextStMin, "There are protected contexts" );
            // try this twice, first normally to let m_nContextStMin decrease
            // naturally and get contexts popped in desired order, and if that
            // fails force it
            for (int i = 0; i < 2; ++i)
            {
                while (m_aContexts.size() > m_nContextStMin)
                {
                    std::unique_ptr<HTMLAttrContext> xCntxt(PopContext());
                    if (xCntxt)
                        EndContext(xCntxt.get());
                }
                if (!m_nContextStMin)
                    break;
                OSL_ENSURE(!m_nContextStMin, "There are still protected contexts");
                m_nContextStMin = 0;
            }
 
            m_aParaAttrs.clear();
 
            SetAttr( false );
 
            // set the first delayed styles
            m_pCSS1Parser->SetDelayedStyles();
        }
 
        // again correct the start
        if( !IsNewDoc() && m_pSttNdIdx->GetIndex() )
        {
            SwTextNode* pTextNode = m_pSttNdIdx->GetNode().GetTextNode();
            SwNodeIndex aNxtIdx( *m_pSttNdIdx );
            if( pTextNode && pTextNode->CanJoinNext( &aNxtIdx ))
            {
                const sal_Int32 nStt = pTextNode->GetText().getLength();
                // when the cursor is still in the node, then set him at the end
                if( m_pPam->GetPoint()->GetNode() == aNxtIdx.GetNode() )
                {
                    m_pPam->GetPoint()->Assign( *pTextNode, nStt );
                }
 
#if OSL_DEBUG_LEVEL > 0
// !!! shouldn't be possible, or ??
                OSL_ENSURE( m_pSttNdIdx->GetIndex()+1 != m_pPam->GetBound().GetNodeIndex(),
                    "Pam.Bound1 is still in the node" );
                OSL_ENSURE( m_pSttNdIdx->GetIndex()+1 != m_pPam->GetBound( false ).GetNodeIndex(),
                    "Pam.Bound2 is still in the node" );
 
                if( m_pSttNdIdx->GetIndex()+1 == m_pPam->GetBound().GetNodeIndex() )
                {
                    const sal_Int32 nCntPos = m_pPam->GetBound().GetContentIndex();
                    m_pPam->GetBound().SetContent(
                                    pTextNode->GetText().getLength() + nCntPos );
                }
                if( m_pSttNdIdx->GetIndex()+1 == m_pPam->GetBound( false ).GetNodeIndex() )
                {
                    const sal_Int32 nCntPos = m_pPam->GetBound( false ).GetContentIndex();
                    m_pPam->GetBound( false ).SetContent(
                                    pTextNode->GetText().getLength() + nCntPos );
                }
#endif
                // Keep character attribute!
                SwTextNode* pDelNd = aNxtIdx.GetNode().GetTextNode();
                if (pTextNode->GetText().getLength())
                    pDelNd->FormatToTextAttr( pTextNode );
                else
                    pTextNode->ChgFormatColl( pDelNd->GetTextColl() );
                pTextNode->JoinNext();
            }
        }
    }
 
    if( SvParserState::Accepted == eState )
    {
        if( m_nMissingImgMaps )
        {
            // Some Image-Map relations are still missing.
            // Maybe now the Image-Maps are there?
            ConnectImageMaps();
        }
 
        // now remove the last useless paragraph
        SwPosition* pPos = m_pPam->GetPoint();
        if( !pPos->GetContentIndex() && !bLFStripped )
        {
            SwTextNode* pCurrentNd;
            SwNodeOffset nNodeIdx = pPos->GetNodeIndex();
 
            bool bHasFlysOrMarks =
                HasCurrentParaFlys() || HasCurrentParaBookmarks( true );
 
            if( IsNewDoc() )
            {
                if (!m_pPam->GetPoint()->GetContentIndex() && CanRemoveNode(nNodeIdx))
                {
                    SwContentNode* pCNd = m_pPam->GetPointContentNode();
                    if( pCNd && pCNd->StartOfSectionIndex()+2 <
                        pCNd->EndOfSectionIndex() && !bHasFlysOrMarks )
                    {
                        SwViewShell *pVSh = CheckActionViewShell();
                        SwCursorShell *pCursorSh = dynamic_cast<SwCursorShell *>( pVSh );
                        if( pCursorSh &&
                            pCursorSh->GetCursor()->GetPoint()
                                   ->GetNodeIndex() == nNodeIdx )
                        {
                            pCursorSh->MovePara(GoPrevPara, fnParaEnd );
                            pCursorSh->SetMark();
                            pCursorSh->ClearMark();
                        }
                        SwNode& rDelNode = m_pPam->GetPoint()->GetNode();
                        // move so we don't have a dangling SwContentIndex to the deleted node
                        m_pPam->GetPoint()->Adjust(SwNodeOffset(1));
                        if (m_pPam->HasMark())
                            m_pPam->GetMark()->Adjust(SwNodeOffset(1));
                        m_xDoc->GetNodes().Delete( rDelNode );
                    }
                }
            }
            else if( nullptr != ( pCurrentNd = m_xDoc->GetNodes()[ nNodeIdx ]->GetTextNode()) && !bHasFlysOrMarks )
            {
                if( pCurrentNd->CanJoinNext( pPos ))
                {
                    SwTextNode* pNextNd = pPos->GetNode().GetTextNode();
                    m_pPam->SetMark(); m_pPam->DeleteMark();
                    pNextNd->JoinPrev();
                }
                else if (pCurrentNd->GetText().isEmpty())
                {
                    m_pPam->SetMark(); m_pPam->DeleteMark();
                    SwNode& rDelNode = pPos->GetNode();
                    // move so we don't have a dangling SwContentIndex to the deleted node
                    m_pPam->GetPoint()->Adjust(SwNodeOffset(+1));
                    m_xDoc->GetNodes().Delete( rDelNode );
                    m_pPam->Move( fnMoveBackward );
                }
            }
        }
 
        // annul the SplitNode from the beginning
        else if( !IsNewDoc() )
        {
            if( pPos->GetContentIndex() )                 // then there was no <p> at the end
                m_pPam->Move( fnMoveForward, GoInNode );    // therefore to the next
            SwTextNode* pTextNode = pPos->GetNode().GetTextNode();
            SwNodeIndex aPrvIdx( pPos->GetNode() );
            if( pTextNode && pTextNode->CanJoinPrev( &aPrvIdx ) &&
                *m_pSttNdIdx <= aPrvIdx )
            {
                // Normally here should take place a JoinNext, but all cursors and
                // so are registered in pTextNode, so that it MUST remain.
 
                // Convert paragraph to character attribute, from Prev adopt
                // the paragraph attribute and the template!
                SwTextNode* pPrev = aPrvIdx.GetNode().GetTextNode();
                pTextNode->ChgFormatColl( pPrev->GetTextColl() );
                pTextNode->FormatToTextAttr( pPrev );
                pTextNode->ResetAllAttr();
 
                if( pPrev->HasSwAttrSet() )
                    pTextNode->SetAttr( *pPrev->GetpSwAttrSet() );
 
                if( &m_pPam->GetBound().GetNode() == pPrev )
                    m_pPam->GetBound().nContent.Assign( pTextNode, 0 );
                if( &m_pPam->GetBound(false).GetNode() == pPrev )
                    m_pPam->GetBound(false).nContent.Assign( pTextNode, 0 );
 
                pTextNode->JoinPrev();
            }
        }
 
        // adjust AutoLoad in DocumentProperties
        if (!bFuzzing && IsNewDoc())
        {
            SwDocShell *pDocShell(m_xDoc->GetDocShell());
            OSL_ENSURE(pDocShell, "no SwDocShell");
            if (pDocShell) {
                uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                    pDocShell->GetModel(), uno::UNO_QUERY_THROW);
                uno::Reference<document::XDocumentProperties> xDocProps(
                    xDPS->getDocumentProperties());
                OSL_ENSURE(xDocProps.is(), "DocumentProperties is null");
                if ( xDocProps.is() && (xDocProps->getAutoloadSecs() > 0) &&
                     (xDocProps->getAutoloadURL().isEmpty()) )
                {
                    xDocProps->setAutoloadURL(m_aPathToFile);
                }
            }
        }
 
        if( m_bUpdateDocStat )
        {
            m_xDoc->getIDocumentStatistics().UpdateDocStat( false, true );
        }
    }
 
    if( SvParserState::Pending != GetStatus() )
    {
        delete m_pSttNdIdx;
        m_pSttNdIdx = nullptr;
    }
 
    // should the parser be the last one who hold the document, then nothing
    // has to be done anymore, document will be destroyed shortly!
    if( 1 < m_xDoc->getReferenceCount() )
    {
        if( bWasUndo )
        {
            m_xDoc->GetIDocumentUndoRedo().DelAllUndoObj();
            m_xDoc->GetIDocumentUndoRedo().DoUndo(true);
        }
        else if( !pInitVSh )
        {
            // When at the beginning of Continue no Shell was available,
            // it's possible in the meantime one was created.
            // In that case the bWasUndo flag is wrong and we must
            // enable Undo.
            SwViewShell *pTmpVSh = CheckActionViewShell();
            if( pTmpVSh )
            {
                m_xDoc->GetIDocumentUndoRedo().DoUndo(true);
            }
        }
 
        m_xDoc->SetOle2Link( aOLELink );
        if( !bModified )
            m_xDoc->getIDocumentState().ResetModified();
        if( m_bSetModEnabled && pShell )
        {
            pShell->EnableSetModified();
            m_bSetModEnabled = false; // this is unnecessary here
        }
    }
 
    // When the Document-SwVievShell still exists and an Action is open
    // (doesn't have to be by abort), end the Action, disconnect from Shell
    // and finally reconstruct the old Shell.
    CallEndAction( true );
 
#ifdef DBG_UTIL
    m_nContinue--;
#endif
}
 
void SwHTMLParser::Notify(const SfxHint& rHint)
{
    if(rHint.GetId() == SfxHintId::Dying)
    {
        EndListeningAll();
        ReleaseRef();
    }
}
 
void SwHTMLParser::DocumentDetected()
{
    OSL_ENSURE( !m_bDocInitialized, "DocumentDetected called multiple times" );
    m_bDocInitialized = true;
    if( IsNewDoc() )
    {
        if( IsInHeader() )
            FinishHeader();
 
        CallEndAction( true );
 
        m_xDoc->GetIDocumentUndoRedo().DoUndo(false);
        // For DocumentDetected in general a SwViewShell is created.
        // But it also can be created later, in case the UI is captured.
        CallStartAction();
    }
}
 
// is called for every token that is recognised in CallParser
void SwHTMLParser::NextToken( HtmlTokenId nToken )
{
    SwDocShell* pShell = m_xDoc->GetDocShell();
    if( ( pShell && pShell->IsAbortingImport() )
        || 1 == m_xDoc->getReferenceCount() )
    {
        // Was the import cancelled by SFX? If a pending stack
        // exists, clean it.
        eState = SvParserState::Error;
        OSL_ENSURE( m_vPendingStack.empty() || m_vPendingStack.back().nToken != HtmlTokenId::NONE,
                "SwHTMLParser::NextToken: Pending-Stack without token" );
        if( 1 == m_xDoc->getReferenceCount() || m_vPendingStack.empty() )
            return ;
    }
 
#if OSL_DEBUG_LEVEL > 0
    if( !m_vPendingStack.empty() )
    {
        switch( nToken )
        {
            // tables are read by recursive method calls
        case HtmlTokenId::TABLE_ON:
            // For CSS declarations we might have to wait
            // for a file download to finish
        case HtmlTokenId::LINK:
            // For controls we might have to set the size.
        case HtmlTokenId::INPUT:
        case HtmlTokenId::TEXTAREA_ON:
        case HtmlTokenId::SELECT_ON:
        case HtmlTokenId::SELECT_OFF:
            break;
        default:
            OSL_ENSURE( m_vPendingStack.empty(), "Unknown token for Pending-Stack" );
            break;
        }
    }
#endif
 
    // The following special cases have to be treated before the
    // filter detection, because Netscape doesn't reference the content
    // of the title for filter detection either.
    if( m_vPendingStack.empty() )
    {
        if( m_bInTitle )
        {
            switch( nToken )
            {
            case HtmlTokenId::TITLE_OFF:
            {
                OUString sTitle = m_sTitle.makeStringAndClear();
                if( IsNewDoc() && !sTitle.isEmpty() )
                {
                    if (pShell)
                    {
                        uno::Reference<document::XDocumentPropertiesSupplier>
                            xDPS(pShell->GetModel(),
                            uno::UNO_QUERY_THROW);
                        uno::Reference<document::XDocumentProperties> xDocProps(
                            xDPS->getDocumentProperties());
                        OSL_ENSURE(xDocProps.is(), "no DocumentProperties");
                        if (xDocProps.is()) {
                            xDocProps->setTitle(sTitle);
                        }
 
                        pShell->SetTitle(sTitle);
                    }
                }
                m_bInTitle = false;
                break;
            }
 
            case HtmlTokenId::NONBREAKSPACE:
                m_sTitle.append(" ");
                break;
 
            case HtmlTokenId::SOFTHYPH:
                m_sTitle.append("-");
                break;
 
            case HtmlTokenId::TEXTTOKEN:
                m_sTitle.append(aToken);
                break;
 
            default:
                m_sTitle.append("<");
                if( (nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken) )
                    m_sTitle.append("/");
                m_sTitle.append(sSaveToken);
                if( !aToken.isEmpty() )
                {
                    m_sTitle.append(" ");
                    m_sTitle.append(aToken);
                }
                m_sTitle.append(">");
                break;
            }
 
            return;
        }
    }
 
    // Find out what type of document it is if we don't know already.
    // For Controls this has to be finished before the control is inserted
    // because for inserting a View is needed.
    if( !m_bDocInitialized )
        DocumentDetected();
 
    bool bGetIDOption = false, bInsertUnknown = false;
    bool bUpperSpaceSave = m_bUpperSpace;
    m_bUpperSpace = false;
 
    // The following special cases may or have to be treated after the
    // filter detection
    if( m_vPendingStack.empty() )
    {
        if( m_bInFloatingFrame )
        {
            // <SCRIPT> is ignored here (from us), because it is ignored in
            // Applets as well
            if( HtmlTokenId::IFRAME_OFF == nToken )
            {
                m_bCallNextToken = false;
                m_bInFloatingFrame = false;
            }
 
            return;
        }
        else if( m_bInNoEmbed )
        {
            switch( nToken )
            {
            case HtmlTokenId::NOEMBED_OFF:
                m_aContents = convertLineEnd(m_aContents, GetSystemLineEnd());
                InsertComment( m_aContents, OOO_STRING_SVTOOLS_HTML_noembed );
                m_aContents.clear();
                m_bCallNextToken = false;
                m_bInNoEmbed = false;
                break;
 
            case HtmlTokenId::RAWDATA:
                InsertCommentText( OOO_STRING_SVTOOLS_HTML_noembed );
                break;
 
            default:
                OSL_ENSURE( false, "SwHTMLParser::NextToken: invalid tag" );
                break;
            }
 
            return;
        }
        else if( m_pAppletImpl )
        {
            // in an applet only <PARAM> tags and the </APPLET> tag
            // are of interest for us (for the moment)
            // <SCRIPT> is ignored here (from Netscape)!
 
            switch( nToken )
            {
            case HtmlTokenId::APPLET_OFF:
                m_bCallNextToken = false;
                EndApplet();
                break;
            case HtmlTokenId::OBJECT_OFF:
                m_bCallNextToken = false;
                EndObject();
                break;
            case HtmlTokenId::PARAM:
                InsertParam();
                break;
            default: break;
            }
 
            return;
        }
        else if( m_bTextArea )
        {
            // in a TextArea everything up to </TEXTAREA> is inserted as text.
            // <SCRIPT> is ignored here (from Netscape)!
 
            switch( nToken )
            {
            case HtmlTokenId::TEXTAREA_OFF:
                m_bCallNextToken = false;
                EndTextArea();
                break;
 
            default:
                InsertTextAreaText( nToken );
                break;
            }
 
            return;
        }
        else if( m_bSelect )
        {
            // HAS to be treated after bNoScript!
            switch( nToken )
            {
            case HtmlTokenId::SELECT_OFF:
                m_bCallNextToken = false;
                EndSelect();
                return;
 
            case HtmlTokenId::OPTION:
                InsertSelectOption();
                return;
 
            case HtmlTokenId::TEXTTOKEN:
                InsertSelectText();
                return;
 
            case HtmlTokenId::INPUT:
            case HtmlTokenId::SCRIPT_ON:
            case HtmlTokenId::SCRIPT_OFF:
            case HtmlTokenId::NOSCRIPT_ON:
            case HtmlTokenId::NOSCRIPT_OFF:
            case HtmlTokenId::RAWDATA:
                // treat in normal switch
                break;
 
            default:
                // ignore
                return;
            }
        }
        else if( m_pMarquee )
        {
            // in a TextArea everything up to </TEXTAREA> is inserted as text.
            // The <SCRIPT> tags are ignored from MS-IE, we ignore the whole
            // script.
            switch( nToken )
            {
            case HtmlTokenId::MARQUEE_OFF:
                m_bCallNextToken = false;
                EndMarquee();
                break;
 
            case HtmlTokenId::TEXTTOKEN:
                InsertMarqueeText();
                break;
            default: break;
            }
 
            return;
        }
        else if( m_bInField )
        {
            switch( nToken )
            {
            case HtmlTokenId::SDFIELD_OFF:
                m_bCallNextToken = false;
                EndField();
                break;
 
            case HtmlTokenId::TEXTTOKEN:
                InsertFieldText();
                break;
            default: break;
            }
 
            return;
        }
        else if( m_bInFootEndNoteAnchor || m_bInFootEndNoteSymbol )
        {
            switch( nToken )
            {
            case HtmlTokenId::ANCHOR_OFF:
                EndAnchor();
                m_bCallNextToken = false;
                break;
 
            case HtmlTokenId::TEXTTOKEN:
                InsertFootEndNoteText();
                break;
            default: break;
            }
            return;
        }
        else if( !m_aUnknownToken.isEmpty() )
        {
            // Paste content of unknown tags.
            // (but surely if we are not in the header section) fdo#36080 fdo#34666
            if (!aToken.isEmpty() && !IsInHeader() )
            {
                if( !m_bDocInitialized )
                    DocumentDetected();
                m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, aToken.toString());
 
                // if there are temporary paragraph attributes and the
                // paragraph isn't empty then the paragraph attributes
                // are final.
                m_aParaAttrs.clear();
 
                SetAttr();
            }
 
            // Unknown token in the header are only closed by a matching
            // end-token, </HEAD> or <BODY>. Text inside is ignored.
            switch( nToken )
            {
            case HtmlTokenId::UNKNOWNCONTROL_OFF:
                if( m_aUnknownToken != sSaveToken )
                    return;
                [[fallthrough]];
            case HtmlTokenId::FRAMESET_ON:
            case HtmlTokenId::HEAD_OFF:
            case HtmlTokenId::BODY_ON:
            case HtmlTokenId::IMAGE:        // Don't know why Netscape acts this way.
                m_aUnknownToken.clear();
                break;
            case HtmlTokenId::TEXTTOKEN:
                return;
            default:
                m_aUnknownToken.clear();
                break;
            }
        }
    }
 
    switch( nToken )
    {
    case HtmlTokenId::BODY_ON:
        if (!m_bBodySeen)
        {
            m_bBodySeen = true;
            if( !m_aStyleSource.isEmpty() )
            {
                m_pCSS1Parser->ParseStyleSheet( m_aStyleSource );
                m_aStyleSource.clear();
            }
            if( IsNewDoc() )
            {
                InsertBodyOptions();
                // If there is a template for the first or the right page,
                // it is set here.
                const SwPageDesc *pPageDesc = nullptr;
                if( m_pCSS1Parser->IsSetFirstPageDesc() )
                    pPageDesc = m_pCSS1Parser->GetFirstPageDesc();
                else if( m_pCSS1Parser->IsSetRightPageDesc() )
                    pPageDesc = m_pCSS1Parser->GetRightPageDesc();
 
                if( pPageDesc )
                {
                    m_xDoc->getIDocumentContentOperations().InsertPoolItem( *m_pPam, SwFormatPageDesc( pPageDesc ) );
                }
            }
        }
        break;
 
    case HtmlTokenId::LINK:
        InsertLink();
        break;
 
    case HtmlTokenId::BASE:
        {
            const HTMLOptions& rHTMLOptions = GetOptions();
            for (size_t i = rHTMLOptions.size(); i; )
            {
                const HTMLOption& rOption = rHTMLOptions[--i];
                switch( rOption.GetToken() )
                {
                case HtmlOptionId::HREF:
                    m_sBaseURL = rOption.GetString();
                    break;
                case HtmlOptionId::TARGET:
                    if( IsNewDoc() )
                    {
                        SwDocShell *pDocShell(m_xDoc->GetDocShell());
                        OSL_ENSURE(pDocShell, "no SwDocShell");
                        if (pDocShell) {
                            uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
                                pDocShell->GetModel(), uno::UNO_QUERY_THROW);
                            uno::Reference<document::XDocumentProperties>
                                xDocProps(xDPS->getDocumentProperties());
                            OSL_ENSURE(xDocProps.is(),"no DocumentProperties");
                            if (xDocProps.is()) {
                                xDocProps->setDefaultTarget(
                                    rOption.GetString());
                            }
                        }
                    }
                    break;
                default: break;
                }
            }
        }
        break;
 
    case HtmlTokenId::META:
        {
            SvKeyValueIterator *pHTTPHeader = nullptr;
            if( IsNewDoc() )
            {
                SwDocShell *pDocSh = m_xDoc->GetDocShell();
                if( pDocSh )
                    pHTTPHeader = pDocSh->GetHeaderAttributes();
            }
            SwDocShell *pDocShell(m_xDoc->GetDocShell());
            OSL_ENSURE(pDocShell, "no SwDocShell");
            if (pDocShell)
            {
                uno::Reference<document::XDocumentProperties> xDocProps;
                if (IsNewDoc())
                {
                    const uno::Reference<document::XDocumentPropertiesSupplier>
                        xDPS( pDocShell->GetModel(), uno::UNO_QUERY_THROW );
                    xDocProps = xDPS->getDocumentProperties();
                    OSL_ENSURE(xDocProps.is(), "DocumentProperties is null");
                }
                ParseMetaOptions( xDocProps, pHTTPHeader );
            }
        }
        break;
 
    case HtmlTokenId::TITLE_ON:
        m_bInTitle = true;
        break;
 
    case HtmlTokenId::SCRIPT_ON:
        NewScript();
        break;
 
    case HtmlTokenId::SCRIPT_OFF:
        EndScript();
        break;
 
    case HtmlTokenId::NOSCRIPT_ON:
    case HtmlTokenId::NOSCRIPT_OFF:
        bInsertUnknown = true;
        break;
 
    case HtmlTokenId::STYLE_ON:
        NewStyle();
        break;
 
    case HtmlTokenId::STYLE_OFF:
        EndStyle();
        break;
 
    case HtmlTokenId::RAWDATA:
        if( !m_bIgnoreRawData )
        {
            if( IsReadScript() )
            {
                AddScriptSource();
            }
            else if( IsReadStyle() )
            {
                if( !m_aStyleSource.isEmpty() )
                    m_aStyleSource += "\n";
                m_aStyleSource += aToken;
            }
        }
        break;
 
    case HtmlTokenId::OBJECT_ON:
        if (m_bXHTML)
        {
            if (!InsertEmbed())
                InsertImage();
            break;
        }
#if HAVE_FEATURE_JAVA
        NewObject();
        m_bCallNextToken = m_pAppletImpl!=nullptr && m_xTable;
#endif
        break;
 
    case HtmlTokenId::OBJECT_OFF:
        if (!m_aEmbeds.empty())
            m_aEmbeds.pop();
        break;
 
    case HtmlTokenId::APPLET_ON:
#if HAVE_FEATURE_JAVA
        InsertApplet();
        m_bCallNextToken = m_pAppletImpl!=nullptr && m_xTable;
#endif
        break;
 
    case HtmlTokenId::IFRAME_ON:
        if (bFuzzing && m_nFloatingFrames > 64)
            SAL_WARN("sw.html", "Not importing any more FloatingFrames for fuzzing performance");
        else
        {
            InsertFloatingFrame();
            m_bCallNextToken = m_bInFloatingFrame && m_xTable;
        }
        break;
 
    case HtmlTokenId::LINEBREAK:
        if( !IsReadPRE() )
        {
            InsertLineBreak();
            break;
        }
        else
            bGetIDOption = true;
            // <BR>s in <PRE> resemble true LFs, hence no break
        [[fallthrough]];
 
    case HtmlTokenId::NEWPARA:
        // CR in PRE/LISTING/XMP
        {
            if( HtmlTokenId::NEWPARA==nToken ||
                m_pPam->GetPoint()->GetContentIndex() )
            {
                AppendTextNode(); // there is no LF at this place
                                 // therefore it will cause no problems
                SetTextCollAttrs();
            }
            // progress bar
            if (m_xProgress)
                m_xProgress->Update(rInput.Tell());
        }
        break;
 
    case HtmlTokenId::NONBREAKSPACE:
        m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, OUString(CHAR_HARDBLANK) );
        break;
 
    case HtmlTokenId::SOFTHYPH:
        m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, OUString(CHAR_SOFTHYPHEN) );
        break;
 
    case HtmlTokenId::LINEFEEDCHAR:
        if( m_pPam->GetPoint()->GetContentIndex() )
            AppendTextNode();
        if (!m_xTable && !m_xDoc->IsInHeaderFooter(m_pPam->GetPoint()->GetNode()))
        {
            NewAttr(m_xAttrTab, &m_xAttrTab->pBreak, SvxFormatBreakItem(SvxBreak::PageBefore, RES_BREAK));
            EndAttr( m_xAttrTab->pBreak, false );
        }
        break;
 
    case HtmlTokenId::TEXTTOKEN:
    case HtmlTokenId::CDATA:
        // insert string without spanning attributes at the end.
        if (!aToken.isEmpty() && ' ' == aToken[0] && !IsReadPRE() && !GetPreserveSpaces())
        {
            sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex();
            const SwTextNode* pTextNode = nPos ? m_pPam->GetPoint()->GetNode().GetTextNode() : nullptr;
            if (pTextNode)
            {
                const OUString& rText = pTextNode->GetText();
                sal_Unicode cLast = rText[--nPos];
                if( ' ' == cLast || '\x0a' == cLast)
                    aToken.remove(0, 1);
            }
            else
                aToken.remove(0, 1);
 
            if( aToken.isEmpty() )
            {
                m_bUpperSpace = bUpperSpaceSave;
                break;
            }
        }
 
        if( !aToken.isEmpty() )
        {
            if( !m_bDocInitialized )
                DocumentDetected();
 
            if (!m_aEmbeds.empty())
            {
                // The text token is inside an OLE object, which means
                // alternate text.
                SwOLENode* pOLENode = m_aEmbeds.top();
                if (!pOLENode)
                {
                    // <object> is mapped to an image -> ignore.
                    break;
                }
 
                if (SwFlyFrameFormat* pFormat
                    = dynamic_cast<SwFlyFrameFormat*>(pOLENode->GetFlyFormat()))
                {
                    if (SdrObject* pObject = SwXFrame::GetOrCreateSdrObject(*pFormat))
                    {
                        pObject->SetTitle(pObject->GetTitle() + aToken);
                        break;
                    }
                }
            }
 
            m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, aToken.toString());
 
            // if there are temporary paragraph attributes and the
            // paragraph isn't empty then the paragraph attributes
            // are final.
            m_aParaAttrs.clear();
 
            SetAttr();
        }
        break;
 
    case HtmlTokenId::HORZRULE:
        InsertHorzRule();
        break;
 
    case HtmlTokenId::IMAGE:
        InsertImage();
        // if only the parser references the doc, we can break and set
        // an error code
        if( 1 == m_xDoc->getReferenceCount() )
        {
            eState = SvParserState::Error;
        }
        break;
 
    case HtmlTokenId::SPACER:
        InsertSpacer();
        break;
 
    case HtmlTokenId::EMBED:
        InsertEmbed();
        break;
 
    case HtmlTokenId::NOEMBED_ON:
        m_bInNoEmbed = true;
        m_bCallNextToken = bool(m_xTable);
        ReadRawData( u"" OOO_STRING_SVTOOLS_HTML_noembed ""_ustr );
        break;
 
    case HtmlTokenId::DEFLIST_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        NewDefList();
        break;
    case HtmlTokenId::DEFLIST_OFF:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        EndDefListItem( HtmlTokenId::NONE );
        EndDefList();
        break;
 
    case HtmlTokenId::DD_ON:
    case HtmlTokenId::DT_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        EndDefListItem();// close <DD>/<DT> and set no template
        NewDefListItem( nToken );
        break;
 
    case HtmlTokenId::DD_OFF:
    case HtmlTokenId::DT_OFF:
        // c.f. HtmlTokenId::LI_OFF
        // Actually we should close a DD/DT now.
        // But neither Netscape nor Microsoft do this and so don't we.
        EndDefListItem( nToken );
        break;
 
    // divisions
    case HtmlTokenId::DIVISION_ON:
    case HtmlTokenId::CENTER_ON:
        if (!m_isInTableStructure)
        {
            if (m_nOpenParaToken != HtmlTokenId::NONE)
            {
                if (IsReadPRE())
                    m_nOpenParaToken = HtmlTokenId::NONE;
                else
                    EndPara();
            }
            NewDivision( nToken );
        }
        break;
 
    case HtmlTokenId::DIVISION_OFF:
    case HtmlTokenId::CENTER_OFF:
        if (!m_isInTableStructure)
        {
            if (m_nOpenParaToken != HtmlTokenId::NONE)
            {
                if (IsReadPRE())
                    m_nOpenParaToken = HtmlTokenId::NONE;
                else
                    EndPara();
            }
            EndDivision();
        }
        break;
 
    case HtmlTokenId::MULTICOL_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        NewMultiCol();
        break;
 
    case HtmlTokenId::MULTICOL_OFF:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        EndTag( HtmlTokenId::MULTICOL_ON );
        break;
 
    case HtmlTokenId::MARQUEE_ON:
        NewMarquee();
        m_bCallNextToken = m_pMarquee!=nullptr && m_xTable;
        break;
 
    case HtmlTokenId::FORM_ON:
        NewForm();
        break;
    case HtmlTokenId::FORM_OFF:
        EndForm();
        break;
 
    // templates
    case HtmlTokenId::PARABREAK_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara( true );
        NewPara();
        break;
 
    case HtmlTokenId::PARABREAK_OFF:
        EndPara( true );
        break;
 
    case HtmlTokenId::ADDRESS_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        NewTextFormatColl(HtmlTokenId::ADDRESS_ON, RES_POOLCOLL_SEND_ADDRESS);
        break;
 
    case HtmlTokenId::ADDRESS_OFF:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        EndTextFormatColl( HtmlTokenId::ADDRESS_OFF );
        break;
 
    case HtmlTokenId::BLOCKQUOTE_ON:
    case HtmlTokenId::BLOCKQUOTE30_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        NewTextFormatColl( HtmlTokenId::BLOCKQUOTE_ON, RES_POOLCOLL_HTML_BLOCKQUOTE );
        break;
 
    case HtmlTokenId::BLOCKQUOTE_OFF:
    case HtmlTokenId::BLOCKQUOTE30_OFF:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        EndTextFormatColl( HtmlTokenId::BLOCKQUOTE_ON );
        break;
 
    case HtmlTokenId::PREFORMTXT_ON:
    case HtmlTokenId::LISTING_ON:
    case HtmlTokenId::XMP_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        NewTextFormatColl( nToken, RES_POOLCOLL_HTML_PRE );
        break;
 
    case HtmlTokenId::PREFORMTXT_OFF:
        m_bNoParSpace = true; // the last PRE-paragraph gets a spacing
        EndTextFormatColl( HtmlTokenId::PREFORMTXT_OFF );
        break;
 
    case HtmlTokenId::LISTING_OFF:
    case HtmlTokenId::XMP_OFF:
        EndTextFormatColl( nToken );
        break;
 
    case HtmlTokenId::HEAD1_ON:
    case HtmlTokenId::HEAD2_ON:
    case HtmlTokenId::HEAD3_ON:
    case HtmlTokenId::HEAD4_ON:
    case HtmlTokenId::HEAD5_ON:
    case HtmlTokenId::HEAD6_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
        {
            if( IsReadPRE() )
                m_nOpenParaToken = HtmlTokenId::NONE;
            else
                EndPara();
        }
        NewHeading( nToken );
        break;
 
    case HtmlTokenId::HEAD1_OFF:
    case HtmlTokenId::HEAD2_OFF:
    case HtmlTokenId::HEAD3_OFF:
    case HtmlTokenId::HEAD4_OFF:
    case HtmlTokenId::HEAD5_OFF:
    case HtmlTokenId::HEAD6_OFF:
        EndHeading();
        break;
 
    case HtmlTokenId::TABLE_ON:
        if( !m_vPendingStack.empty() )
            BuildTable( SvxAdjust::End );
        else
        {
            if( m_nOpenParaToken != HtmlTokenId::NONE )
                EndPara();
            OSL_ENSURE(!m_xTable, "table in table not allowed here");
            if( !m_xTable && (IsNewDoc() || !m_pPam->GetPointNode().FindTableNode()) &&
                (m_pPam->GetPoint()->GetNodeIndex() >
                            m_xDoc->GetNodes().GetEndOfExtras().GetIndex() ||
                !m_pPam->GetPointNode().FindFootnoteStartNode() ) )
            {
                if ( m_nParaCnt < 5 )
                    Show();     // show what we have up to here
 
                SvxAdjust eAdjust = m_xAttrTab->pAdjust
                    ? static_cast<const SvxAdjustItem&>(m_xAttrTab->pAdjust->GetItem()).
                                             GetAdjust()
                    : SvxAdjust::End;
                BuildTable( eAdjust );
            }
            else
                bInsertUnknown = m_bKeepUnknown;
        }
        break;
 
    // lists
    case HtmlTokenId::DIRLIST_ON:
    case HtmlTokenId::MENULIST_ON:
    case HtmlTokenId::ORDERLIST_ON:
    case HtmlTokenId::UNORDERLIST_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        NewNumberBulletList( nToken );
        break;
 
    case HtmlTokenId::DIRLIST_OFF:
    case HtmlTokenId::MENULIST_OFF:
    case HtmlTokenId::ORDERLIST_OFF:
    case HtmlTokenId::UNORDERLIST_OFF:
        if( m_nOpenParaToken != HtmlTokenId::NONE )
            EndPara();
        EndNumberBulletListItem( HtmlTokenId::NONE, true );
        EndNumberBulletList( nToken );
        break;
 
    case HtmlTokenId::LI_ON:
    case HtmlTokenId::LISTHEADER_ON:
        if( m_nOpenParaToken != HtmlTokenId::NONE &&
            (m_pPam->GetPoint()->GetContentIndex()
            || HtmlTokenId::PARABREAK_ON==m_nOpenParaToken) )
        {
            // only finish paragraph for <P><LI>, not for <DD><LI>
            EndPara();
        }
 
        if (bFuzzing && m_nListItems > 1024)
        {
            SAL_WARN("sw.html", "skipping remaining bullet import for performance during fuzzing");
        }
        else
        {
            EndNumberBulletListItem( HtmlTokenId::NONE, false );// close <LI>/<LH> and don't set a template
            NewNumberBulletListItem( nToken );
        }
 
        ++m_nListItems;
 
        break;
    case HtmlTokenId::LI_OFF:
    case HtmlTokenId::LISTHEADER_OFF:
        EndNumberBulletListItem( nToken, false );
        break;
 
    // Attribute :
    case HtmlTokenId::ITALIC_ON:
        {
            SvxPostureItem aPosture( ITALIC_NORMAL, RES_CHRATR_POSTURE );
            SvxPostureItem aPostureCJK( ITALIC_NORMAL, RES_CHRATR_CJK_POSTURE );
            SvxPostureItem aPostureCTL( ITALIC_NORMAL, RES_CHRATR_CTL_POSTURE );
            NewStdAttr( HtmlTokenId::ITALIC_ON,
                           &m_xAttrTab->pItalic, aPosture,
                           &m_xAttrTab->pItalicCJK, &aPostureCJK,
                           &m_xAttrTab->pItalicCTL, &aPostureCTL );
        }
        break;
 
    case HtmlTokenId::BOLD_ON:
        {
            SvxWeightItem aWeight( WEIGHT_BOLD, RES_CHRATR_WEIGHT );
            SvxWeightItem aWeightCJK( WEIGHT_BOLD, RES_CHRATR_CJK_WEIGHT );
            SvxWeightItem aWeightCTL( WEIGHT_BOLD, RES_CHRATR_CTL_WEIGHT );
            NewStdAttr( HtmlTokenId::BOLD_ON,
                        &m_xAttrTab->pBold, aWeight,
                        &m_xAttrTab->pBoldCJK, &aWeightCJK,
                        &m_xAttrTab->pBoldCTL, &aWeightCTL );
        }
        break;
 
    case HtmlTokenId::STRIKE_ON:
    case HtmlTokenId::STRIKETHROUGH_ON:
        {
            NewStdAttr( HtmlTokenId::STRIKE_ON, &m_xAttrTab->pStrike,
                        SvxCrossedOutItem(STRIKEOUT_SINGLE, RES_CHRATR_CROSSEDOUT) );
        }
        break;
 
    case HtmlTokenId::UNDERLINE_ON:
        {
            NewStdAttr( HtmlTokenId::UNDERLINE_ON, &m_xAttrTab->pUnderline,
                        SvxUnderlineItem(LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE) );
        }
        break;
 
    case HtmlTokenId::SUPERSCRIPT_ON:
        {
            NewStdAttr( HtmlTokenId::SUPERSCRIPT_ON, &m_xAttrTab->pEscapement,
                        SvxEscapementItem(HTML_ESC_SUPER,HTML_ESC_PROP, RES_CHRATR_ESCAPEMENT) );
        }
        break;
 
    case HtmlTokenId::SUBSCRIPT_ON:
        {
            NewStdAttr( HtmlTokenId::SUBSCRIPT_ON, &m_xAttrTab->pEscapement,
                        SvxEscapementItem(HTML_ESC_SUB,HTML_ESC_PROP, RES_CHRATR_ESCAPEMENT) );
        }
        break;
 
    case HtmlTokenId::BLINK_ON:
        {
            NewStdAttr( HtmlTokenId::BLINK_ON, &m_xAttrTab->pBlink,
                        SvxBlinkItem( true, RES_CHRATR_BLINK ) );
        }
        break;
 
    case HtmlTokenId::SPAN_ON:
        NewStdAttr( HtmlTokenId::SPAN_ON );
        break;
 
    case HtmlTokenId::ITALIC_OFF:
    case HtmlTokenId::BOLD_OFF:
    case HtmlTokenId::STRIKE_OFF:
    case HtmlTokenId::UNDERLINE_OFF:
    case HtmlTokenId::SUPERSCRIPT_OFF:
    case HtmlTokenId::SUBSCRIPT_OFF:
    case HtmlTokenId::BLINK_OFF:
    case HtmlTokenId::SPAN_OFF:
        EndTag( nToken );
        break;
 
    case HtmlTokenId::STRIKETHROUGH_OFF:
        EndTag( HtmlTokenId::STRIKE_OFF );
        break;
 
    case HtmlTokenId::BASEFONT_ON:
        NewBasefontAttr();
        break;
    case HtmlTokenId::BASEFONT_OFF:
        EndBasefontAttr();
        break;
    case HtmlTokenId::FONT_ON:
    case HtmlTokenId::BIGPRINT_ON:
    case HtmlTokenId::SMALLPRINT_ON:
        NewFontAttr( nToken );
        break;
    case HtmlTokenId::FONT_OFF:
    case HtmlTokenId::BIGPRINT_OFF:
    case HtmlTokenId::SMALLPRINT_OFF:
        EndFontAttr( nToken );
        break;
 
    case HtmlTokenId::EMPHASIS_ON:
    case HtmlTokenId::CITATION_ON:
    case HtmlTokenId::STRONG_ON:
    case HtmlTokenId::CODE_ON:
    case HtmlTokenId::SAMPLE_ON:
    case HtmlTokenId::KEYBOARD_ON:
    case HtmlTokenId::VARIABLE_ON:
    case HtmlTokenId::DEFINSTANCE_ON:
    case HtmlTokenId::SHORTQUOTE_ON:
    case HtmlTokenId::LANGUAGE_ON:
    case HtmlTokenId::AUTHOR_ON:
    case HtmlTokenId::PERSON_ON:
    case HtmlTokenId::ACRONYM_ON:
    case HtmlTokenId::ABBREVIATION_ON:
    case HtmlTokenId::INSERTEDTEXT_ON:
    case HtmlTokenId::DELETEDTEXT_ON:
 
    case HtmlTokenId::TELETYPE_ON:
        NewCharFormat( nToken );
        break;
 
    case HtmlTokenId::SDFIELD_ON:
        NewField();
        m_bCallNextToken = m_bInField && m_xTable;
        break;
 
    case HtmlTokenId::EMPHASIS_OFF:
    case HtmlTokenId::CITATION_OFF:
    case HtmlTokenId::STRONG_OFF:
    case HtmlTokenId::CODE_OFF:
    case HtmlTokenId::SAMPLE_OFF:
    case HtmlTokenId::KEYBOARD_OFF:
    case HtmlTokenId::VARIABLE_OFF:
    case HtmlTokenId::DEFINSTANCE_OFF:
    case HtmlTokenId::SHORTQUOTE_OFF:
    case HtmlTokenId::LANGUAGE_OFF:
    case HtmlTokenId::AUTHOR_OFF:
    case HtmlTokenId::PERSON_OFF:
    case HtmlTokenId::ACRONYM_OFF:
    case HtmlTokenId::ABBREVIATION_OFF:
    case HtmlTokenId::INSERTEDTEXT_OFF:
    case HtmlTokenId::DELETEDTEXT_OFF:
 
    case HtmlTokenId::TELETYPE_OFF:
        EndTag( nToken );
        break;
 
    case HtmlTokenId::HEAD_OFF:
        if( !m_aStyleSource.isEmpty() )
        {
            m_pCSS1Parser->ParseStyleSheet( m_aStyleSource );
            m_aStyleSource.clear();
        }
        break;
 
    case HtmlTokenId::DOCTYPE:
    case HtmlTokenId::BODY_OFF:
    case HtmlTokenId::HTML_OFF:
    case HtmlTokenId::HEAD_ON:
    case HtmlTokenId::TITLE_OFF:
        break;      // don't evaluate further???
    case HtmlTokenId::HTML_ON:
        {
            const HTMLOptions& rHTMLOptions = GetOptions();
            for (size_t i = rHTMLOptions.size(); i; )
            {
                const HTMLOption& rOption = rHTMLOptions[--i];
                if( HtmlOptionId::DIR == rOption.GetToken() )
                {
                    const OUString& rDir = rOption.GetString();
                    SfxItemSet aItemSet( m_xDoc->GetAttrPool(),
                                         m_pCSS1Parser->GetWhichMap() );
                    SvxCSS1PropertyInfo aPropInfo;
                    OUString aDummy;
                    ParseStyleOptions( aDummy, aDummy, aDummy, aItemSet,
                                       aPropInfo, nullptr, &rDir );
 
                    m_pCSS1Parser->SetPageDescAttrs( nullptr, &aItemSet );
                    break;
                }
            }
        }
        break;
 
    case HtmlTokenId::INPUT:
        InsertInput();
        break;
 
    case HtmlTokenId::TEXTAREA_ON:
        NewTextArea();
        m_bCallNextToken = m_bTextArea && m_xTable;
        break;
 
    case HtmlTokenId::SELECT_ON:
        NewSelect();
        m_bCallNextToken = m_bSelect && m_xTable;
        break;
 
    case HtmlTokenId::ANCHOR_ON:
        NewAnchor();
        break;
 
    case HtmlTokenId::ANCHOR_OFF:
        EndAnchor();
        break;
 
    case HtmlTokenId::COMMENT:
        if( ( aToken.getLength() > 5 ) && ( ! m_bIgnoreHTMLComments ) )
        {
            // insert as Post-It
            // If there are no space characters right behind
            // the <!-- and on front of the -->, leave the comment untouched.
            if( ' ' == aToken[ 3 ] &&
                ' ' == aToken[ aToken.getLength()-3 ] )
            {
                std::u16string_view aComment( aToken.subView( 3, aToken.getLength()-5 ) );
                InsertComment(OUString(comphelper::string::strip(aComment, ' ')));
            }
            else
            {
                OUString aComment = "<" + aToken + ">";
                InsertComment( aComment );
            }
        }
        break;
 
    case HtmlTokenId::MAP_ON:
        // Image Maps are read asynchronously: At first only an image map is created
        // Areas are processed later. Nevertheless the
        // ImageMap is inserted into the IMap-Array, because it might be used
        // already.
        m_pImageMap = new ImageMap;
        if( ParseMapOptions( m_pImageMap) )
        {
            if (!m_pImageMaps)
                m_pImageMaps.reset( new ImageMaps );
            m_pImageMaps->push_back(std::unique_ptr<ImageMap>(m_pImageMap));
        }
        else
        {
            delete m_pImageMap;
            m_pImageMap = nullptr;
        }
        break;
 
    case HtmlTokenId::MAP_OFF:
        // there is no ImageMap anymore (don't delete IMap, because it's
        // already contained in the array!)
        m_pImageMap = nullptr;
        break;
 
    case HtmlTokenId::AREA:
        if( m_pImageMap )
            ParseAreaOptions( m_pImageMap, m_sBaseURL, SvMacroItemId::OnMouseOver,
                                         SvMacroItemId::OnMouseOut );
        break;
 
    case HtmlTokenId::FRAMESET_ON:
        bInsertUnknown = m_bKeepUnknown;
        break;
 
    case HtmlTokenId::NOFRAMES_ON:
        if( IsInHeader() )
            FinishHeader();
        bInsertUnknown = m_bKeepUnknown;
        break;
 
    case HtmlTokenId::UNKNOWNCONTROL_ON:
        // Ignore content of unknown token in the header, if the token
        // does not start with a '!'.
        // (but judging from the code, also if does not start with a '%')
        // (and also if we're not somewhere we consider PRE)
        if( IsInHeader() && !IsReadPRE() && m_aUnknownToken.isEmpty() &&
            !sSaveToken.isEmpty() && '!' != sSaveToken[0] &&
            '%' != sSaveToken[0] )
            m_aUnknownToken = sSaveToken;
        [[fallthrough]];
 
    default:
        bInsertUnknown = m_bKeepUnknown;
        break;
    }
 
    if( bGetIDOption )
        InsertIDOption();
 
    if( bInsertUnknown )
    {
        OUStringBuffer aComment("HTML: <");
        if( (nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken) )
            aComment.append("/");
        aComment.append(sSaveToken);
        if( !aToken.isEmpty() )
        {
            UnescapeToken();
            aComment.append(" " + aToken);
        }
        aComment.append(">");
        InsertComment( aComment.makeStringAndClear() );
    }
 
    // if there are temporary paragraph attributes and the
    // paragraph isn't empty then the paragraph attributes are final.
    if( !m_aParaAttrs.empty() && m_pPam->GetPoint()->GetContentIndex() )
        m_aParaAttrs.clear();
}
 
static void lcl_swhtml_getItemInfo( const HTMLAttr& rAttr,
                                 bool& rScriptDependent,
                                 sal_uInt16& rScriptType )
{
    switch( rAttr.GetItem().Which() )
    {
    case RES_CHRATR_FONT:
    case RES_CHRATR_FONTSIZE:
    case RES_CHRATR_LANGUAGE:
    case RES_CHRATR_POSTURE:
    case RES_CHRATR_WEIGHT:
        rScriptType = i18n::ScriptType::LATIN;
        rScriptDependent = true;
        break;
    case RES_CHRATR_CJK_FONT:
    case RES_CHRATR_CJK_FONTSIZE:
    case RES_CHRATR_CJK_LANGUAGE:
    case RES_CHRATR_CJK_POSTURE:
    case RES_CHRATR_CJK_WEIGHT:
        rScriptType = i18n::ScriptType::ASIAN;
        rScriptDependent = true;
        break;
    case RES_CHRATR_CTL_FONT:
    case RES_CHRATR_CTL_FONTSIZE:
    case RES_CHRATR_CTL_LANGUAGE:
    case RES_CHRATR_CTL_POSTURE:
    case RES_CHRATR_CTL_WEIGHT:
        rScriptType = i18n::ScriptType::COMPLEX;
        rScriptDependent = true;
        break;
    default:
        rScriptDependent = false;
        break;
    }
}
 
bool SwHTMLParser::AppendTextNode( SwHTMLAppendMode eMode, bool bUpdateNum )
{
    // A hard line break at the end always must be removed.
    // A second one we replace with paragraph spacing.
    sal_Int32 nLFStripped = StripTrailingLF();
    if( (AM_NOSPACE==eMode || AM_SOFTNOSPACE==eMode) && nLFStripped > 1 )
        eMode = AM_SPACE;
 
    // the hard attributes of this paragraph will never be invalid again
    m_aParaAttrs.clear();
 
    SwTextNode *pTextNode = (AM_SPACE==eMode || AM_NOSPACE==eMode) ?
        m_pPam->GetPoint()->GetNode().GetTextNode() : nullptr;
 
    if (pTextNode)
    {
        const SvxULSpaceItem& rULSpace =
            pTextNode->SwContentNode::GetAttr( RES_UL_SPACE );
 
        bool bChange = AM_NOSPACE==eMode ? rULSpace.GetLower() > 0
                                         : rULSpace.GetLower() == 0;
 
        if( bChange )
        {
            const SvxULSpaceItem& rCollULSpace =
                pTextNode->GetAnyFormatColl().GetULSpace();
 
            bool bMayReset = AM_NOSPACE==eMode ? rCollULSpace.GetLower() == 0
                                               : rCollULSpace.GetLower() > 0;
 
            if( bMayReset &&
                rCollULSpace.GetUpper() == rULSpace.GetUpper() )
            {
                pTextNode->ResetAttr( RES_UL_SPACE );
            }
            else
            {
                pTextNode->SetAttr(
                    SvxULSpaceItem( rULSpace.GetUpper(),
                         AM_NOSPACE==eMode ? 0 : HTML_PARSPACE, RES_UL_SPACE ) );
            }
        }
    }
    m_bNoParSpace = AM_NOSPACE==eMode || AM_SOFTNOSPACE==eMode;
 
    SwPosition aOldPos( *m_pPam->GetPoint() );
 
    bool bRet = m_xDoc->getIDocumentContentOperations().AppendTextNode( *m_pPam->GetPoint() );
 
    // split character attributes and maybe set none,
    // which are set for the whole paragraph
    const sal_Int32 nEndCnt = aOldPos.GetContentIndex();
    const SwPosition& rPos = *m_pPam->GetPoint();
 
    HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get());
    for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes)
    {
        HTMLAttr *pAttr = *pHTMLAttributes;
        if( pAttr && pAttr->GetItem().Which() < RES_PARATR_BEGIN )
        {
            bool bWholePara = false;
 
            while( pAttr )
            {
                HTMLAttr *pNext = pAttr->GetNext();
                if( pAttr->GetStartParagraphIdx() < aOldPos.GetNodeIndex() ||
                    (!bWholePara &&
                     pAttr->GetStartParagraph() == aOldPos.GetNode() &&
                     pAttr->GetStartContent() != nEndCnt) )
                {
                    bWholePara =
                        pAttr->GetStartParagraph() == aOldPos.GetNode() &&
                        pAttr->GetStartContent() == 0;
 
                    sal_Int32 nStt = pAttr->m_nStartContent;
                    bool bScript = false;
                    sal_uInt16 nScriptItem;
                    bool bInsert = true;
                    lcl_swhtml_getItemInfo( *pAttr, bScript,
                                            nScriptItem );
                        // set previous part
                    if( bScript )
                    {
                        const SwTextNode *pTextNd =
                            pAttr->GetStartParagraph().GetNode().GetTextNode();
                        OSL_ENSURE( pTextNd, "No text node" );
                        if( pTextNd )
                        {
                            const OUString& rText = pTextNd->GetText();
                            sal_uInt16 nScriptText =
                                g_pBreakIt->GetBreakIter()->getScriptType(
                                            rText, pAttr->GetStartContent() );
                            sal_Int32 nScriptEnd = g_pBreakIt->GetBreakIter()
                                    ->endOfScript( rText, nStt, nScriptText );
                            while (nScriptEnd < nEndCnt && nScriptEnd != -1)
                            {
                                if( nScriptItem == nScriptText )
                                {
                                    HTMLAttr *pSetAttr =
                                        pAttr->Clone( aOldPos.GetNode(), nScriptEnd );
                                    pSetAttr->m_nStartContent = nStt;
                                    pSetAttr->ClearPrev();
                                    if( !pNext || bWholePara )
                                    {
                                        if (pSetAttr->m_bInsAtStart)
                                            m_aSetAttrTab.push_front( pSetAttr );
                                        else
                                            m_aSetAttrTab.push_back( pSetAttr );
                                    }
                                    else
                                        pNext->InsertPrev( pSetAttr );
                                }
                                nStt = nScriptEnd;
                                nScriptText = g_pBreakIt->GetBreakIter()->getScriptType(
                                                rText, nStt );
                                nScriptEnd = g_pBreakIt->GetBreakIter()
                                    ->endOfScript( rText, nStt, nScriptText );
                            }
                            bInsert = nScriptItem == nScriptText;
                        }
                    }
                    if( bInsert )
                    {
                        HTMLAttr *pSetAttr =
                            pAttr->Clone( aOldPos.GetNode(), nEndCnt );
                        pSetAttr->m_nStartContent = nStt;
 
                        // When the attribute is for the whole paragraph, the outer
                        // attributes aren't effective anymore. Hence it may not be inserted
                        // in the Prev-List of an outer attribute, because that won't be
                        // set. That leads to shifting when fields are used.
                        if( !pNext || bWholePara )
                        {
                            if (pSetAttr->m_bInsAtStart)
                                m_aSetAttrTab.push_front( pSetAttr );
                            else
                                m_aSetAttrTab.push_back( pSetAttr );
                        }
                        else
                            pNext->InsertPrev( pSetAttr );
                    }
                    else
                    {
                        HTMLAttr *pPrev = pAttr->GetPrev();
                        if( pPrev )
                        {
                            // the previous attributes must be set anyway
                            if( !pNext || bWholePara )
                            {
                                if (pPrev->m_bInsAtStart)
                                    m_aSetAttrTab.push_front( pPrev );
                                else
                                    m_aSetAttrTab.push_back( pPrev );
                            }
                            else
                                pNext->InsertPrev( pPrev );
                        }
                    }
                    pAttr->ClearPrev();
                }
 
                pAttr->SetStart( rPos );
                pAttr = pNext;
            }
        }
    }
 
    if( bUpdateNum )
    {
        if( GetNumInfo().GetDepth() )
        {
            sal_uInt8 nLvl = GetNumInfo().GetLevel();
            SetNodeNum( nLvl );
        }
        else
            m_pPam->GetPointNode().GetTextNode()->ResetAttr( RES_PARATR_NUMRULE );
    }
 
    // We must set the attribute of the paragraph before now (because of JavaScript)
    SetAttr();
 
    // Now it is time to get rid of all script dependent hints that are
    // equal to the settings in the style
    SwTextNode *pTextNd = aOldPos.GetNode().GetTextNode();
    OSL_ENSURE( pTextNd, "There is the txt node" );
    size_t nCntAttr = (pTextNd  && pTextNd->GetpSwpHints())
                            ? pTextNd->GetSwpHints().Count() : 0;
    if( nCntAttr )
    {
        // These are the end position of all script dependent hints.
        // If we find a hint that starts before the current end position,
        // we have to set it. If we find a hint that start behind or at
        // that position, we have to take the hint value into account.
        // If it is equal to the style, or in fact the paragraph value
        // for that hint, the hint is removed. Otherwise its end position
        // is remembered.
        sal_Int32 aEndPos[15] =
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        SwpHints& rHints = pTextNd->GetSwpHints();
        for( size_t i=0; i < nCntAttr; i++ )
        {
            SwTextAttr *pHt = rHints.Get( i );
            sal_uInt16 nWhich = pHt->Which();
            sal_Int16 nIdx = 0;
            bool bFont = false;
            switch( nWhich )
            {
                case RES_CHRATR_FONT:
                    nIdx = 0;
                    bFont = true;
                    break;
                case RES_CHRATR_FONTSIZE:
                    nIdx = 1;
                    break;
                case RES_CHRATR_LANGUAGE:
                    nIdx = 2;
                    break;
                case RES_CHRATR_POSTURE:
                    nIdx = 3;
                    break;
                case RES_CHRATR_WEIGHT:
                    nIdx = 4;
                    break;
                case RES_CHRATR_CJK_FONT:
                    nIdx = 5;
                    bFont = true;
                    break;
                case RES_CHRATR_CJK_FONTSIZE:
                    nIdx = 6;
                    break;
                case RES_CHRATR_CJK_LANGUAGE:
                    nIdx = 7;
                    break;
                case RES_CHRATR_CJK_POSTURE:
                    nIdx = 8;
                    break;
                case RES_CHRATR_CJK_WEIGHT:
                    nIdx = 9;
                    break;
                case RES_CHRATR_CTL_FONT:
                    nIdx = 10;
                    bFont = true;
                    break;
                case RES_CHRATR_CTL_FONTSIZE:
                    nIdx = 11;
                    break;
                case RES_CHRATR_CTL_LANGUAGE:
                    nIdx = 12;
                    break;
                case RES_CHRATR_CTL_POSTURE:
                    nIdx = 13;
                    break;
                case RES_CHRATR_CTL_WEIGHT:
                    nIdx = 14;
                    break;
                default:
                    // Skip to next attribute
                    continue;
            }
            const sal_Int32 nStt = pHt->GetStart();
            if( nStt >= aEndPos[nIdx] )
            {
                const SfxPoolItem& rItem =
                    static_cast<const SwContentNode *>(pTextNd)->GetAttr( nWhich );
                if( bFont ? swhtml_css1atr_equalFontItems(rItem,pHt->GetAttr())
                            : rItem == pHt->GetAttr() )
                {
                    // The hint is the same as set in the paragraph and
                    // therefore, it can be deleted
                    // CAUTION!!! This WILL delete the hint and it MAY
                    // also delete the SwpHints!!! To avoid any trouble
                    // we leave the loop immediately if this is the last
                    // hint.
                    pTextNd->DeleteAttribute( pHt );
                    if( 1 == nCntAttr )
                        break;
                    i--;
                    nCntAttr--;
                }
                else
                {
                    // The hint is different. Therefore all hints within that
                    // hint have to be ignored.
                    aEndPos[nIdx] = pHt->GetEnd() ? *pHt->GetEnd() : nStt;
                }
            }
            else
            {
                // The hint starts before another one ends.
                // The hint in this case is not deleted
                OSL_ENSURE( pHt->GetEnd() && *pHt->GetEnd() <= aEndPos[nIdx],
                        "hints aren't nested properly!" );
            }
        }
    }
 
    if (!m_xTable && !--m_nParaCnt)
        Show();
 
    return bRet;
}
 
void SwHTMLParser::AddParSpace()
{
    //If it already has ParSpace, return
    if( !m_bNoParSpace )
        return;
 
    m_bNoParSpace = false;
 
    SwNodeOffset nNdIdx = m_pPam->GetPoint()->GetNodeIndex() - 1;
 
    SwTextNode *pTextNode = m_xDoc->GetNodes()[nNdIdx]->GetTextNode();
    if( !pTextNode )
        return;
 
    SvxULSpaceItem rULSpace =
        pTextNode->SwContentNode::GetAttr( RES_UL_SPACE );
    if( rULSpace.GetLower() )
        return;
 
    const SvxULSpaceItem& rCollULSpace =
        pTextNode->GetAnyFormatColl().GetULSpace();
    if( rCollULSpace.GetLower() &&
        rCollULSpace.GetUpper() == rULSpace.GetUpper() )
    {
        pTextNode->ResetAttr( RES_UL_SPACE );
    }
    else
    {
        //What I do here, is that I examine the attributes, and if
        //I find out, that it's CJK/CTL, then I set the paragraph space
        //to the value set in HTML_CJK_PARSPACE/HTML_CTL_PARSPACE.
 
        bool bIsCJK = false;
        bool bIsCTL = false;
 
        const size_t nCntAttr = pTextNode->GetpSwpHints()
                        ? pTextNode->GetSwpHints().Count() : 0;
 
        for(size_t i = 0; i < nCntAttr; ++i)
        {
            SwTextAttr *const pHt = pTextNode->GetSwpHints().Get(i);
            sal_uInt16 const nWhich = pHt->Which();
            if (RES_CHRATR_CJK_FONT == nWhich ||
                RES_CHRATR_CJK_FONTSIZE == nWhich ||
                RES_CHRATR_CJK_LANGUAGE == nWhich ||
                RES_CHRATR_CJK_POSTURE == nWhich ||
                RES_CHRATR_CJK_WEIGHT == nWhich)
            {
                bIsCJK = true;
                break;
            }
            if (RES_CHRATR_CTL_FONT == nWhich ||
                RES_CHRATR_CTL_FONTSIZE == nWhich ||
                RES_CHRATR_CTL_LANGUAGE == nWhich ||
                RES_CHRATR_CTL_POSTURE == nWhich ||
                RES_CHRATR_CTL_WEIGHT == nWhich)
            {
                bIsCTL = true;
                break;
            }
        }
 
        if( bIsCTL )
        {
            pTextNode->SetAttr(
                SvxULSpaceItem( rULSpace.GetUpper(), HTML_CTL_PARSPACE, RES_UL_SPACE )  );
        }
        else if( bIsCJK )
        {
            pTextNode->SetAttr(
                SvxULSpaceItem( rULSpace.GetUpper(), HTML_CJK_PARSPACE, RES_UL_SPACE )  );
        } else {
            pTextNode->SetAttr(
                SvxULSpaceItem( rULSpace.GetUpper(), HTML_PARSPACE, RES_UL_SPACE )  );
        }
    }
}
 
void SwHTMLParser::Show()
{
    // Here
    // - a EndAction is called, so the document is formatted
    // - a Reschedule is called,
    // - the own View-Shell is set again
    // - and a StartAction is called
 
    OSL_ENSURE( SvParserState::Working==eState, "Show not in working state - That can go wrong" );
    SwViewShell *pOldVSh = CallEndAction();
 
    Application::Reschedule();
 
    SwDocShell* pShell = m_xDoc->GetDocShell();
    if( ( pShell && pShell->IsAbortingImport() )
        || 1 == m_xDoc->getReferenceCount() )
    {
        // was the import aborted by SFX?
        eState = SvParserState::Error;
    }
 
    // Fetch the SwViewShell again, as it could be destroyed in Reschedule.
    SwViewShell *pVSh = CallStartAction( pOldVSh );
 
    // is the current node not visible anymore, then we use a bigger increment
    if( pVSh )
    {
        m_nParaCnt = (m_pPam->GetPoint()->GetNode().IsInVisibleArea(pVSh))
            ? 5 : 50;
    }
}
 
void SwHTMLParser::ShowStatline()
{
    // Here
    // - a Reschedule is called, so it can be scrolled
    // - the own View-Shell is set again
    // - a StartAction/EndAction is called, when there was scrolling.
 
    OSL_ENSURE( SvParserState::Working==eState, "ShowStatLine not in working state - That can go wrong" );
 
    // scroll bar
    if (m_xProgress)
    {
        m_xProgress->Update(rInput.Tell());
        CheckActionViewShell();
    }
    else
    {
        Application::Reschedule();
 
        SwDocShell* pShell = m_xDoc->GetDocShell();
        if( ( pShell && pShell->IsAbortingImport() )
            || 1 == m_xDoc->getReferenceCount() )
            // was the import aborted by SFX?
            eState = SvParserState::Error;
 
        SwViewShell *pVSh = CheckActionViewShell();
        if( pVSh && pVSh->HasInvalidRect() )
        {
            CallEndAction( false, false );
            CallStartAction( pVSh, false );
        }
    }
}
 
SwViewShell *SwHTMLParser::CallStartAction( SwViewShell *pVSh, bool bChkPtr )
{
    OSL_ENSURE( !m_pActionViewShell, "CallStartAction: SwViewShell already set" );
 
    if( !pVSh || bChkPtr )
    {
#if OSL_DEBUG_LEVEL > 0
        SwViewShell *pOldVSh = pVSh;
#endif
        pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
#if OSL_DEBUG_LEVEL > 0
        OSL_ENSURE( !pVSh || !pOldVSh || pOldVSh == pVSh, "CallStartAction: Who swapped the SwViewShell?" );
        if( pOldVSh && !pVSh )
            pVSh = nullptr;
#endif
    }
    m_pActionViewShell = pVSh;
 
    if( m_pActionViewShell )
    {
        if( auto pEditShell = dynamic_cast< SwEditShell *>( m_pActionViewShell ) )
            pEditShell->StartAction();
        else
            m_pActionViewShell->StartAction();
    }
 
    return m_pActionViewShell;
}
 
SwViewShell *SwHTMLParser::CallEndAction( bool bChkAction, bool bChkPtr )
{
    if( bChkPtr )
    {
        SwViewShell *pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
        OSL_ENSURE( !pVSh || m_pActionViewShell == pVSh,
                "CallEndAction: Who swapped the SwViewShell?" );
#if OSL_DEBUG_LEVEL > 0
        if( m_pActionViewShell && !pVSh )
            pVSh = nullptr;
#endif
        if( pVSh != m_pActionViewShell )
            m_pActionViewShell = nullptr;
    }
 
    if( !m_pActionViewShell || (bChkAction && !m_pActionViewShell->ActionPend()) )
        return m_pActionViewShell;
 
    if (SwEditShell* pEditShell = dynamic_cast<SwEditShell*>(m_pActionViewShell))
    {
        // Already scrolled?, then make sure that the view doesn't move!
        const bool bOldLock = m_pActionViewShell->IsViewLocked();
        m_pActionViewShell->LockView( true );
        pEditShell->EndAction();
        m_pActionViewShell->LockView( bOldLock );
 
        // bChkJumpMark is only set when the object was also found
        if( m_bChkJumpMark )
        {
            const Point aVisSttPos( DOCUMENTBORDER, DOCUMENTBORDER );
            if( GetMedium() && aVisSttPos == m_pActionViewShell->VisArea().Pos() )
                ::JumpToSwMark( m_pActionViewShell,
                                GetMedium()->GetURLObject().GetMark() );
            m_bChkJumpMark = false;
        }
    }
    else
        m_pActionViewShell->EndAction();
 
    // if the parser holds the last reference to the document, then we can
    // abort here and set an error.
    if( 1 == m_xDoc->getReferenceCount() )
    {
        eState = SvParserState::Error;
    }
 
    SwViewShell *pVSh = m_pActionViewShell;
    m_pActionViewShell = nullptr;
 
    return pVSh;
}
 
SwViewShell *SwHTMLParser::CheckActionViewShell()
{
    SwViewShell *pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell();
    OSL_ENSURE( !pVSh || m_pActionViewShell == pVSh,
            "CheckActionViewShell: Who has swapped SwViewShell?" );
#if OSL_DEBUG_LEVEL > 0
    if( m_pActionViewShell && !pVSh )
        pVSh = nullptr;
#endif
    if( pVSh != m_pActionViewShell )
        m_pActionViewShell = nullptr;
 
    return m_pActionViewShell;
}
 
SwHTMLFrameFormatListener::SwHTMLFrameFormatListener(SwFrameFormat* pFrameFormat)
    : m_pFrameFormat(pFrameFormat)
{
    StartListening(m_pFrameFormat->GetNotifier());
}
 
void SwHTMLFrameFormatListener::Notify(const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::Dying)
        m_pFrameFormat = nullptr;
}
 
void SwHTMLParser::SetAttr_( bool bChkEnd, bool bBeforeTable,
                             std::deque<std::unique_ptr<HTMLAttr>> *pPostIts )
{
    SwPaM aAttrPam( *m_pPam->GetPoint() );
    const SwPosition& rEndPos = *m_pPam->GetPoint();
    const sal_Int32 nEndCnt = m_pPam->GetPoint()->GetContentIndex();
    HTMLAttr* pAttr;
    SwContentNode* pCNd;
 
    std::vector<std::unique_ptr<HTMLAttr>> aFields;
 
    for( auto n = m_aSetAttrTab.size(); n; )
    {
        pAttr = m_aSetAttrTab[ --n ];
        sal_uInt16 nWhich = pAttr->m_pItem->Which();
 
        SwNodeOffset nEndParaIdx = pAttr->GetEndParagraphIdx();
        bool bSetAttr;
        if( bChkEnd )
        {
            // Set character attribute with end early on, so set them still in
            // the current paragraph (because of JavaScript and various "chats"(?)).
            // This shouldn't be done for attributes which are used for
            // the whole paragraph, because they could be from a paragraph style
            // which can't be set. Because the attributes are inserted with
            // SETATTR_DONTREPLACE, they should be able to be set later.
            bSetAttr = ( nEndParaIdx < rEndPos.GetNodeIndex() &&
                         ((RES_MARGIN_FIRSTLINE != nWhich && RES_MARGIN_TEXTLEFT != nWhich) || !GetNumInfo().GetNumRule()) ) ||
                       ( !pAttr->IsLikePara() &&
                         nEndParaIdx == rEndPos.GetNodeIndex() &&
                         pAttr->GetEndContent() < nEndCnt &&
                         (isCHRATR(nWhich) || isTXTATR_WITHEND(nWhich)) ) ||
                       ( bBeforeTable &&
                         nEndParaIdx == rEndPos.GetNodeIndex() &&
                         !pAttr->GetEndContent() );
        }
        else
        {
            // Attributes in body nodes array section shouldn't be set if we are in a
            // special nodes array section, but vice versa it's possible.
            SwNodeOffset nEndOfIcons = m_xDoc->GetNodes().GetEndOfExtras().GetIndex();
            bSetAttr = nEndParaIdx < rEndPos.GetNodeIndex() ||
                       rEndPos.GetNodeIndex() > nEndOfIcons ||
                       nEndParaIdx <= nEndOfIcons;
        }
 
        if( bSetAttr )
        {
            // The attribute shouldn't be in the list of temporary paragraph
            // attributes, because then it would be deleted.
            while( !m_aParaAttrs.empty() )
            {
                OSL_ENSURE( pAttr != m_aParaAttrs.back(),
                        "SetAttr: Attribute must not yet be set" );
                m_aParaAttrs.pop_back();
            }
 
            // then set it
            m_aSetAttrTab.erase( m_aSetAttrTab.begin() + n );
 
            while( pAttr )
            {
                HTMLAttr *pPrev = pAttr->GetPrev();
                if( !pAttr->m_bValid )
                {
                    // invalid attributes can be deleted
                    delete pAttr;
                    pAttr = pPrev;
                    continue;
                }
 
                pCNd = pAttr->m_nStartPara.GetNode().GetContentNode();
                if( !pCNd )
                {
                    // because of the awful deleting of nodes an index can also
                    // point to an end node :-(
                    if ( (pAttr->GetStartParagraph() == pAttr->GetEndParagraph()) &&
                         !isTXTATR_NOEND(nWhich) )
                    {
                        // when the end index also points to the node, we don't
                        // need to set attributes anymore, except if it's a text attribute.
                        delete pAttr;
                        pAttr = pPrev;
                        continue;
                    }
                    pCNd = SwNodes::GoNext(&(pAttr->m_nStartPara));
                    if( pCNd )
                        pAttr->m_nStartContent = 0;
                    else
                    {
                        OSL_ENSURE( false, "SetAttr: GoNext() failed!" );
                        delete pAttr;
                        pAttr = pPrev;
                        continue;
                    }
                }
 
                // because of the deleting of BRs the start index can also
                // point behind the end the text
                if( pAttr->m_nStartContent > pCNd->Len() )
                    pAttr->m_nStartContent = pCNd->Len();
                aAttrPam.GetPoint()->Assign( *pCNd, pAttr->m_nStartContent );
 
                aAttrPam.SetMark();
                if ( (pAttr->GetStartParagraph() != pAttr->GetEndParagraph()) &&
                         !isTXTATR_NOEND(nWhich) )
                {
                    pCNd = pAttr->m_nEndPara.GetNode().GetContentNode();
                    if( !pCNd )
                    {
                        pCNd = SwNodes::GoPrevious( &(pAttr->m_nEndPara) );
                        if( pCNd )
                            pAttr->m_nEndContent = pCNd->Len();
                        else
                        {
                            OSL_ENSURE( false, "SetAttr: GoPrevious() failed!" );
                            aAttrPam.DeleteMark();
                            delete pAttr;
                            pAttr = pPrev;
                            continue;
                        }
                    }
                }
                else if( pAttr->IsLikePara() )
                {
                    pAttr->m_nEndContent = pCNd->Len();
                }
 
                // because of the deleting of BRs the start index can also
                // point behind the end the text
                if( pAttr->m_nEndContent > pCNd->Len() )
                    pAttr->m_nEndContent = pCNd->Len();
 
                aAttrPam.GetPoint()->Assign( *pCNd, pAttr->m_nEndContent );
                if( bBeforeTable &&
                    aAttrPam.GetPoint()->GetNodeIndex() ==
                        rEndPos.GetNodeIndex() )
                {
                    // If we're before inserting a table and the attribute ends
                    // in the current node, then we must end it in the previous
                    // node or discard it, if it starts in that node.
                    if( nWhich != RES_BREAK && nWhich != RES_PAGEDESC &&
                         !isTXTATR_NOEND(nWhich) )
                    {
                        if( aAttrPam.GetMark()->GetNodeIndex() !=
                            rEndPos.GetNodeIndex() )
                        {
                            OSL_ENSURE( !aAttrPam.GetPoint()->GetContentIndex(),
                                    "Content-Position before table not 0???" );
                            aAttrPam.Move( fnMoveBackward );
                        }
                        else
                        {
                            aAttrPam.DeleteMark();
                            delete pAttr;
                            pAttr = pPrev;
                            continue;
                        }
                    }
                }
 
                switch( nWhich )
                {
                case RES_FLTR_BOOKMARK: // insert bookmark
                    {
                        const OUString sName( static_cast<SfxStringItem*>(pAttr->m_pItem.get())->GetValue() );
                        IDocumentMarkAccess* const pMarkAccess = m_xDoc->getIDocumentMarkAccess();
                        auto ppBkmk = pMarkAccess->findMark( sName );
                        if( ppBkmk != pMarkAccess->getAllMarksEnd() &&
                            (*ppBkmk)->GetMarkStart() == *aAttrPam.GetPoint() )
                            break; // do not generate duplicates on this position
                        aAttrPam.DeleteMark();
                        const ::sw::mark::MarkBase* const pNewMark = pMarkAccess->makeMark(
                            aAttrPam,
                            sName,
                            IDocumentMarkAccess::MarkType::BOOKMARK,
                            ::sw::mark::InsertMode::New);
 
                        // jump to bookmark
                        if( JumpToMarks::Mark == m_eJumpTo && pNewMark->GetName() == m_sJmpMark )
                        {
                            m_bChkJumpMark = true;
                            m_eJumpTo = JumpToMarks::NONE;
                        }
                    }
                    break;
                case RES_TXTATR_FIELD:
                case RES_TXTATR_ANNOTATION:
                case RES_TXTATR_INPUTFIELD:
                    {
                        SwFieldIds nFieldWhich =
                            pPostIts
                            ? static_cast<const SwFormatField *>(pAttr->m_pItem.get())->GetField()->GetTyp()->Which()
                            : SwFieldIds::Database;
                        if( pPostIts && (SwFieldIds::Postit == nFieldWhich ||
                                         SwFieldIds::Script == nFieldWhich) )
                        {
                            pPostIts->emplace_front( pAttr );
                        }
                        else
                        {
                            aFields.emplace_back( pAttr);
                        }
                    }
                    aAttrPam.DeleteMark();
                    pAttr = pPrev;
                    continue;
 
                // tdf#94088 expand RES_BACKGROUND to the new fill attribute
                // definitions in the range [XATTR_FILL_FIRST .. XATTR_FILL_LAST].
                // This is the right place in the future if the adapted fill attributes
                // may be handled more directly in HTML import to handle them.
                case RES_BACKGROUND:
                {
                    const SvxBrushItem& rBrush = static_cast< SvxBrushItem& >(*pAttr->m_pItem);
                    SfxItemSetFixed<XATTR_FILL_FIRST, XATTR_FILL_LAST> aNewSet(m_xDoc->GetAttrPool());
 
                    setSvxBrushItemAsFillAttributesToTargetSet(rBrush, aNewSet);
                    m_xDoc->getIDocumentContentOperations().InsertItemSet(aAttrPam, aNewSet, SetAttrMode::DONTREPLACE);
                    break;
                }
 
                case RES_LR_SPACE:
                    assert(false);
                    break;
 
                case RES_MARGIN_FIRSTLINE:
                case RES_MARGIN_TEXTLEFT:
                case RES_MARGIN_RIGHT:
                    if( aAttrPam.GetPoint()->GetNodeIndex() ==
                        aAttrPam.GetMark()->GetNodeIndex())
                    {
                        // because of numbering set this attribute directly at node
                        pCNd->SetAttr( *pAttr->m_pItem );
                        break;
                    }
                    OSL_ENSURE( false,
                            "LRSpace set over multiple paragraphs!" );
                    [[fallthrough]]; // (shouldn't reach this point anyway)
                default:
 
                    // maybe jump to a bookmark
                    if( RES_TXTATR_INETFMT == nWhich &&
                        JumpToMarks::Mark == m_eJumpTo &&
                        m_sJmpMark == static_cast<SwFormatINetFormat*>(pAttr->m_pItem.get())->GetName() )
                    {
                        m_bChkJumpMark = true;
                        m_eJumpTo = JumpToMarks::NONE;
                    }
 
                    m_xDoc->getIDocumentContentOperations().InsertPoolItem( aAttrPam, *pAttr->m_pItem, SetAttrMode::DONTREPLACE );
                }
                aAttrPam.DeleteMark();
 
                delete pAttr;
                pAttr = pPrev;
            }
        }
    }
 
    for( auto n = m_aMoveFlyFrames.size(); n; )
    {
        SwFrameFormat *pFrameFormat = m_aMoveFlyFrames[--n]->GetFrameFormat();
        if (!pFrameFormat)
        {
            SAL_WARN("sw.html", "SwFrameFormat deleted during import");
            m_aMoveFlyFrames.erase( m_aMoveFlyFrames.begin() + n );
            m_aMoveFlyCnts.erase( m_aMoveFlyCnts.begin() + n );
            continue;
        }
 
        const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
        OSL_ENSURE( RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId(),
                "Only At-Para flys need special handling" );
        SwNodeOffset nFlyParaIdx = rAnchor.GetAnchorNode()->GetIndex();
        bool bMoveFly;
        if( bChkEnd )
        {
            bMoveFly = nFlyParaIdx < rEndPos.GetNodeIndex() ||
                       ( nFlyParaIdx == rEndPos.GetNodeIndex() &&
                         m_aMoveFlyCnts[n] < nEndCnt );
        }
        else
        {
            SwNodeOffset nEndOfIcons = m_xDoc->GetNodes().GetEndOfExtras().GetIndex();
            bMoveFly = nFlyParaIdx < rEndPos.GetNodeIndex() ||
                       rEndPos.GetNodeIndex() > nEndOfIcons ||
                       nFlyParaIdx <= nEndOfIcons;
        }
        if( bMoveFly )
        {
            pFrameFormat->DelFrames();
            *aAttrPam.GetPoint() = *rAnchor.GetContentAnchor();
            aAttrPam.GetPoint()->SetContent( m_aMoveFlyCnts[n] );
            SwFormatAnchor aAnchor( rAnchor );
            aAnchor.SetType( RndStdIds::FLY_AT_CHAR );
            aAnchor.SetAnchor( aAttrPam.GetPoint() );
            pFrameFormat->SetFormatAttr( aAnchor );
 
            const SwFormatHoriOrient& rHoriOri = pFrameFormat->GetHoriOrient();
            if( text::HoriOrientation::LEFT == rHoriOri.GetHoriOrient() )
            {
                SwFormatHoriOrient aHoriOri( rHoriOri );
                aHoriOri.SetRelationOrient( text::RelOrientation::CHAR );
                pFrameFormat->SetFormatAttr( aHoriOri );
            }
            const SwFormatVertOrient& rVertOri = pFrameFormat->GetVertOrient();
            if( text::VertOrientation::TOP == rVertOri.GetVertOrient() )
            {
                SwFormatVertOrient aVertOri( rVertOri );
                aVertOri.SetRelationOrient( text::RelOrientation::CHAR );
                pFrameFormat->SetFormatAttr( aVertOri );
            }
 
            pFrameFormat->MakeFrames();
            m_aMoveFlyFrames.erase( m_aMoveFlyFrames.begin() + n );
            m_aMoveFlyCnts.erase( m_aMoveFlyCnts.begin() + n );
        }
    }
    for (auto & field : aFields)
    {
        pCNd = field->m_nStartPara.GetNode().GetContentNode();
        aAttrPam.GetPoint()->Assign( *pCNd, field->m_nStartContent );
 
        if( bBeforeTable &&
            aAttrPam.GetPoint()->GetNodeIndex() == rEndPos.GetNodeIndex() )
        {
            OSL_ENSURE( !bBeforeTable, "Aha, the case does occur" );
            OSL_ENSURE( !aAttrPam.GetPoint()->GetContentIndex(),
                    "Content-Position before table not 0???" );
            // !!!
            aAttrPam.Move( fnMoveBackward );
        }
 
        m_xDoc->getIDocumentContentOperations().InsertPoolItem( aAttrPam, *field->m_pItem );
 
        field.reset();
    }
    aFields.clear();
}
 
void SwHTMLParser::NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTable, HTMLAttr **ppAttr, const SfxPoolItem& rItem )
{
    // Font height and font colour as well as escape attributes may not be
    // combined. Therefore they're saved in a list and in it the last opened
    // attribute is at the beginning and count is always one. For all other
    // attributes count is just incremented.
    if( *ppAttr )
    {
        HTMLAttr *pAttr = new HTMLAttr(*m_pPam->GetPoint(), rItem, ppAttr, rAttrTable);
        pAttr->InsertNext( *ppAttr );
        (*ppAttr) = pAttr;
    }
    else
        (*ppAttr) = new HTMLAttr(*m_pPam->GetPoint(), rItem, ppAttr, rAttrTable);
}
 
bool SwHTMLParser::EndAttr( HTMLAttr* pAttr, bool bChkEmpty )
{
    bool bRet = true;
 
    // The list header is saved in the attribute.
    HTMLAttr **ppHead = pAttr->m_ppHead;
 
    OSL_ENSURE( ppHead, "No list header attribute found!" );
 
    // save the current position as end position
    const SwPosition* pEndPos = m_pPam->GetPoint();
    sal_Int32 nEndCnt = m_pPam->GetPoint()->GetContentIndex();
 
    // Is the last started or an earlier started attribute being ended?
    HTMLAttr *pLast = nullptr;
    if( ppHead && pAttr != *ppHead )
    {
        // The last started attribute isn't being ended
 
        // Then we look for attribute which was started immediately afterwards,
        // which has also not yet been ended (otherwise it would no longer be
        // in the list).
        pLast = *ppHead;
        while( pLast && pLast->GetNext() != pAttr )
            pLast = pLast->GetNext();
 
        OSL_ENSURE( pLast, "Attribute not found in own list!" );
    }
 
    bool bMoveBack = false;
    sal_uInt16 nWhich = pAttr->m_pItem->Which();
    if( !nEndCnt && RES_PARATR_BEGIN <= nWhich &&
        pEndPos->GetNodeIndex() != pAttr->GetStartParagraph().GetIndex() )
    {
        // Then move back one position in the content!
        bMoveBack = m_pPam->Move( fnMoveBackward );
        nEndCnt = m_pPam->GetPoint()->GetContentIndex();
    }
 
    // now end the attribute
    HTMLAttr *pNext = pAttr->GetNext();
 
    bool bInsert;
    sal_uInt16 nScriptItem = 0;
    bool bScript = false;
    // does it have a non-empty range?
    if( !bChkEmpty || (RES_PARATR_BEGIN <= nWhich && bMoveBack) ||
        RES_PAGEDESC == nWhich || RES_BREAK == nWhich ||
        pEndPos->GetNodeIndex() != pAttr->GetStartParagraph().GetIndex() ||
        nEndCnt != pAttr->GetStartContent() )
    {
        bInsert = true;
        // We do some optimization for script dependent attributes here.
        if( pEndPos->GetNodeIndex() == pAttr->GetStartParagraph().GetIndex() )
        {
            lcl_swhtml_getItemInfo( *pAttr, bScript, nScriptItem );
        }
    }
    else
    {
        bInsert = false;
    }
 
    const SwTextNode *pTextNd = (bInsert && bScript) ?
        pAttr->GetStartParagraph().GetNode().GetTextNode() :
        nullptr;
 
    if (pTextNd)
    {
        const OUString& rText = pTextNd->GetText();
        sal_uInt16 nScriptText = g_pBreakIt->GetBreakIter()->getScriptType(
                        rText, pAttr->GetStartContent() );
        sal_Int32 nScriptEnd = g_pBreakIt->GetBreakIter()
                    ->endOfScript( rText, pAttr->GetStartContent(), nScriptText );
        while (nScriptEnd < nEndCnt && nScriptEnd != -1)
        {
            if( nScriptItem == nScriptText )
            {
                HTMLAttr *pSetAttr = pAttr->Clone( pEndPos->GetNode(), nScriptEnd );
                pSetAttr->ClearPrev();
                if( pNext )
                    pNext->InsertPrev( pSetAttr );
                else
                {
                    if (pSetAttr->m_bInsAtStart)
                        m_aSetAttrTab.push_front( pSetAttr );
                    else
                        m_aSetAttrTab.push_back( pSetAttr );
                }
            }
            pAttr->m_nStartContent = nScriptEnd;
            nScriptText = g_pBreakIt->GetBreakIter()->getScriptType(
                            rText, nScriptEnd );
            nScriptEnd = g_pBreakIt->GetBreakIter()
                    ->endOfScript( rText, nScriptEnd, nScriptText );
        }
        bInsert = nScriptItem == nScriptText;
    }
    if( bInsert )
    {
        pAttr->m_nEndPara = pEndPos->GetNode();
        pAttr->m_nEndContent = nEndCnt;
        pAttr->m_bInsAtStart = RES_TXTATR_INETFMT != nWhich &&
                             RES_TXTATR_CHARFMT != nWhich;
 
        if( !pNext )
        {
            // No open attributes of that type exists any longer, so all
            // can be set. Except they depend on another attribute, then
            // they're appended there.
            if (pAttr->m_bInsAtStart)
                m_aSetAttrTab.push_front( pAttr );
            else
                m_aSetAttrTab.push_back( pAttr );
        }
        else
        {
            // There are other open attributes of that type,
            // therefore the setting must be postponed.
            // Hence the current attribute is added at the end
            // of the Prev-List of the successor.
            pNext->InsertPrev( pAttr );
        }
    }
    else
    {
        // Then don't insert, but delete. Because of the "faking" of styles
        // by hard attributing there can be also other empty attributes in the
        // Prev-List, which must be set anyway.
        HTMLAttr *pPrev = pAttr->GetPrev();
        bRet = false;
        delete pAttr;
 
        if( pPrev )
        {
            // The previous attributes must be set anyway.
            if( pNext )
                pNext->InsertPrev( pPrev );
            else
            {
                if (pPrev->m_bInsAtStart)
                    m_aSetAttrTab.push_front( pPrev );
                else
                    m_aSetAttrTab.push_back( pPrev );
            }
        }
 
    }
 
    // If the first attribute of the list was set, then the list header
    // must be corrected as well.
    if( pLast )
        pLast->m_pNext = pNext;
    else if( ppHead )
        *ppHead = pNext;
 
    if( bMoveBack )
        m_pPam->Move( fnMoveForward );
 
    return bRet;
}
 
void SwHTMLParser::DeleteAttr( HTMLAttr* pAttr )
{
    // preliminary paragraph attributes are not allowed here, they could
    // be set here and then the pointers become invalid!
    OSL_ENSURE(m_aParaAttrs.empty(),
        "Danger: there are non-final paragraph attributes");
    m_aParaAttrs.clear();
 
    // The list header is saved in the attribute
    HTMLAttr **ppHead = pAttr->m_ppHead;
 
    OSL_ENSURE( ppHead, "no list header attribute found!" );
 
    // Is the last started or an earlier started attribute being removed?
    HTMLAttr *pLast = nullptr;
    if( ppHead && pAttr != *ppHead )
    {
        // The last started attribute isn't being ended
 
        // Then we look for attribute which was started immediately afterwards,
        // which has also not yet been ended (otherwise it would no longer be
        // in the list).
        pLast = *ppHead;
        while( pLast && pLast->GetNext() != pAttr )
            pLast = pLast->GetNext();
 
        OSL_ENSURE( pLast, "Attribute not found in own list!" );
    }
 
    // now delete the attribute
    HTMLAttr *pNext = pAttr->GetNext();
    HTMLAttr *pPrev = pAttr->GetPrev();
    //hold ref to xAttrTab until end of scope to ensure *ppHead validity
    std::shared_ptr<HTMLAttrTable> xKeepAlive(pAttr->m_xAttrTab);
    delete pAttr;
 
    if( pPrev )
    {
        // The previous attributes must be set anyway.
        if( pNext )
            pNext->InsertPrev( pPrev );
        else
        {
            if (pPrev->m_bInsAtStart)
                m_aSetAttrTab.push_front( pPrev );
            else
                m_aSetAttrTab.push_back( pPrev );
        }
    }
 
    // If the first attribute of the list was deleted, then the list header
    // must be corrected as well.
    if( pLast )
        pLast->m_pNext = pNext;
    else if( ppHead )
        *ppHead = pNext;
}
 
void SwHTMLParser::SaveAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab)
{
    // preliminary paragraph attributes are not allowed here, they could
    // be set here and then the pointers become invalid!
    OSL_ENSURE(m_aParaAttrs.empty(),
            "Danger: there are non-final paragraph attributes");
    m_aParaAttrs.clear();
 
    HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get());
    HTMLAttr** pSaveAttributes = reinterpret_cast<HTMLAttr**>(rNewAttrTab.get());
 
    for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes, ++pSaveAttributes)
    {
        *pSaveAttributes = *pHTMLAttributes;
 
        HTMLAttr *pAttr = *pSaveAttributes;
        while (pAttr)
        {
            pAttr->SetHead(pSaveAttributes, rNewAttrTab);
            pAttr = pAttr->GetNext();
        }
 
        *pHTMLAttributes = nullptr;
    }
}
 
void SwHTMLParser::SplitAttrTab( std::shared_ptr<HTMLAttrTable> const & rNewAttrTab,
                                 bool bMoveEndBack )
{
    // preliminary paragraph attributes are not allowed here, they could
    // be set here and then the pointers become invalid!
    OSL_ENSURE(m_aParaAttrs.empty(),
            "Danger: there are non-final paragraph attributes");
    m_aParaAttrs.clear();
 
    SwNodeIndex nEndIdx( m_pPam->GetPoint()->GetNode() );
 
    // close all still open attributes and re-open them after the table
    HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get());
    HTMLAttr** pSaveAttributes = reinterpret_cast<HTMLAttr**>(rNewAttrTab.get());
    bool bSetAttr = true;
    const sal_Int32 nSttCnt = m_pPam->GetPoint()->GetContentIndex();
    sal_Int32 nEndCnt = nSttCnt;
 
    if( bMoveEndBack )
    {
        SwNodeOffset nOldEnd = nEndIdx.GetIndex();
        SwNodeOffset nTmpIdx;
        if( ( nTmpIdx = m_xDoc->GetNodes().GetEndOfExtras().GetIndex()) >= nOldEnd ||
            ( nTmpIdx = m_xDoc->GetNodes().GetEndOfAutotext().GetIndex()) >= nOldEnd )
        {
            nTmpIdx = m_xDoc->GetNodes().GetEndOfInserts().GetIndex();
        }
        SwContentNode* pCNd = SwNodes::GoPrevious(&nEndIdx);
 
        // Don't set attributes, when the PaM was moved outside of the content area.
        bSetAttr = pCNd && nTmpIdx < nEndIdx.GetIndex();
 
        nEndCnt = (bSetAttr ? pCNd->Len() : 0);
    }
    for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; (++pHTMLAttributes, ++pSaveAttributes))
    {
        HTMLAttr *pAttr = *pHTMLAttributes;
        *pSaveAttributes = nullptr;
        while( pAttr )
        {
            HTMLAttr *pNext = pAttr->GetNext();
            HTMLAttr *pPrev = pAttr->GetPrev();
 
            if( bSetAttr &&
                ( pAttr->GetStartParagraphIdx() < nEndIdx.GetIndex() ||
                  (pAttr->GetStartParagraph() == nEndIdx &&
                   pAttr->GetStartContent() != nEndCnt) ) )
            {
                // The attribute must be set before the list. We need the
                // original and therefore we clone it, because pointer to the
                // attribute exist in the other contexts. The Next-List is lost
                // in doing so, but the Previous-List is preserved.
                HTMLAttr *pSetAttr = pAttr->Clone( nEndIdx.GetNode(), nEndCnt );
 
                if( pNext )
                    pNext->InsertPrev( pSetAttr );
                else
                {
                    if (pSetAttr->m_bInsAtStart)
                        m_aSetAttrTab.push_front( pSetAttr );
                    else
                        m_aSetAttrTab.push_back( pSetAttr );
                }
            }
            else if( pPrev )
            {
                // If the attribute doesn't need to be set before the table, then
                // the previous attributes must still be set.
                if( pNext )
                    pNext->InsertPrev( pPrev );
                else
                {
                    if (pPrev->m_bInsAtStart)
                        m_aSetAttrTab.push_front( pPrev );
                    else
                        m_aSetAttrTab.push_back( pPrev );
                }
            }
 
            // set the start of the attribute anew and break link
            pAttr->Reset(m_pPam->GetPoint()->GetNode(), nSttCnt, pSaveAttributes, rNewAttrTab);
 
            if (*pSaveAttributes)
            {
                HTMLAttr *pSAttr = *pSaveAttributes;
                while( pSAttr->GetNext() )
                    pSAttr = pSAttr->GetNext();
                pSAttr->InsertNext( pAttr );
            }
            else
                *pSaveAttributes = pAttr;
 
            pAttr = pNext;
        }
 
        *pHTMLAttributes = nullptr;
    }
}
 
void SwHTMLParser::RestoreAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab)
{
    // preliminary paragraph attributes are not allowed here, they could
    // be set here and then the pointers become invalid!
    OSL_ENSURE(m_aParaAttrs.empty(),
            "Danger: there are non-final paragraph attributes");
    m_aParaAttrs.clear();
 
    HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get());
    HTMLAttr** pSaveAttributes = reinterpret_cast<HTMLAttr**>(rNewAttrTab.get());
 
    for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes, ++pSaveAttributes)
    {
        OSL_ENSURE(!*pHTMLAttributes, "The attribute table is not empty!");
 
        *pHTMLAttributes = *pSaveAttributes;
 
        HTMLAttr *pAttr = *pHTMLAttributes;
        while (pAttr)
        {
            OSL_ENSURE( !pAttr->GetPrev() || !pAttr->GetPrev()->m_ppHead,
                    "Previous attribute has still a header" );
            pAttr->SetHead(pHTMLAttributes, m_xAttrTab);
            pAttr = pAttr->GetNext();
        }
 
        *pSaveAttributes = nullptr;
    }
}
 
void SwHTMLParser::InsertAttr( const SfxPoolItem& rItem, bool bInsAtStart )
{
    HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), rItem, nullptr, std::shared_ptr<HTMLAttrTable>());
    if (bInsAtStart)
        m_aSetAttrTab.push_front( pTmp );
    else
        m_aSetAttrTab.push_back( pTmp );
}
 
void SwHTMLParser::InsertAttrs( std::deque<std::unique_ptr<HTMLAttr>> rAttrs )
{
    while( !rAttrs.empty() )
    {
        std::unique_ptr<HTMLAttr> pAttr = std::move(rAttrs.front());
        InsertAttr( pAttr->GetItem(), false );
        rAttrs.pop_front();
    }
}
 
void SwHTMLParser::NewStdAttr( HtmlTokenId nToken )
{
    OUString aId, aStyle, aLang, aDir;
    OUString aClass;
 
    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::LANG:
            aLang = rOption.GetString();
            break;
        case HtmlOptionId::DIR:
            aDir = rOption.GetString();
            break;
        default: break;
        }
    }
 
    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
 
    // parse styles
    if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
        {
            if( HtmlTokenId::SPAN_ON != nToken || aClass.isEmpty() ||
                !CreateContainer( aClass, aItemSet, aPropInfo, xCntxt.get() ) )
                DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
            InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true );
        }
    }
 
    // save the context
    PushContext(xCntxt);
}
 
void SwHTMLParser::NewStdAttr( HtmlTokenId nToken,
                               HTMLAttr **ppAttr, const SfxPoolItem & rItem,
                               HTMLAttr **ppAttr2, const SfxPoolItem *pItem2,
                               HTMLAttr **ppAttr3, const SfxPoolItem *pItem3 )
{
    OUString aId, aStyle, aClass, aLang, aDir;
 
    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::LANG:
            aLang = rOption.GetString();
            break;
        case HtmlOptionId::DIR:
            aDir = rOption.GetString();
            break;
        default: break;
        }
    }
 
    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
 
    // parse styles
    if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        aItemSet.Put( rItem );
        if( pItem2 )
            aItemSet.Put( *pItem2 );
        if( pItem3 )
            aItemSet.Put( *pItem3 );
 
        if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
 
        InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true );
    }
    else
    {
        InsertAttr( ppAttr ,rItem, xCntxt.get() );
        if( pItem2 )
        {
            OSL_ENSURE( ppAttr2, "missing table entry for item2" );
            InsertAttr( ppAttr2, *pItem2, xCntxt.get() );
        }
        if( pItem3 )
        {
            OSL_ENSURE( ppAttr3, "missing table entry for item3" );
            InsertAttr( ppAttr3, *pItem3, xCntxt.get() );
        }
    }
 
    // save the context
    PushContext(xCntxt);
}
 
void SwHTMLParser::EndTag( HtmlTokenId nToken )
{
    // fetch context
    std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(getOnToken(nToken)));
    if (xCntxt)
    {
        // and maybe end the attributes
        EndContext(xCntxt.get());
    }
}
 
void SwHTMLParser::NewBasefontAttr()
{
    OUString aId, aStyle, aClass, aLang, aDir;
    sal_uInt16 nSize = 3;
 
    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::SIZE:
            nSize = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::LANG:
            aLang = rOption.GetString();
            break;
        case HtmlOptionId::DIR:
            aDir = rOption.GetString();
            break;
        default: break;
        }
    }
 
    if( nSize < 1 )
        nSize = 1;
 
    if( nSize > 7 )
        nSize = 7;
 
    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::BASEFONT_ON));
 
    // parse styles
    if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        //CJK has different defaults
        SvxFontHeightItem aFontHeight( m_aFontHeights[nSize-1], 100, RES_CHRATR_FONTSIZE );
        aItemSet.Put( aFontHeight );
        SvxFontHeightItem aFontHeightCJK( m_aFontHeights[nSize-1], 100, RES_CHRATR_CJK_FONTSIZE );
        aItemSet.Put( aFontHeightCJK );
        //Complex type can contain so many types of letters,
        //that it's not really worthy to bother, IMO.
        //Still, I have set a default.
        SvxFontHeightItem aFontHeightCTL( m_aFontHeights[nSize-1], 100, RES_CHRATR_CTL_FONTSIZE );
        aItemSet.Put( aFontHeightCTL );
 
        if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
 
        InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true );
    }
    else
    {
        SvxFontHeightItem aFontHeight( m_aFontHeights[nSize-1], 100, RES_CHRATR_FONTSIZE );
        InsertAttr( &m_xAttrTab->pFontHeight, aFontHeight, xCntxt.get() );
        SvxFontHeightItem aFontHeightCJK( m_aFontHeights[nSize-1], 100, RES_CHRATR_CJK_FONTSIZE );
        InsertAttr( &m_xAttrTab->pFontHeightCJK, aFontHeightCJK, xCntxt.get() );
        SvxFontHeightItem aFontHeightCTL( m_aFontHeights[nSize-1], 100, RES_CHRATR_CTL_FONTSIZE );
        InsertAttr( &m_xAttrTab->pFontHeightCTL, aFontHeightCTL, xCntxt.get() );
    }
 
    // save the context
    PushContext(xCntxt);
 
    // save the font size
    m_aBaseFontStack.push_back( nSize );
}
 
void SwHTMLParser::EndBasefontAttr()
{
    EndTag( HtmlTokenId::BASEFONT_ON );
 
    // avoid stack underflow in tables
    if( m_aBaseFontStack.size() > m_nBaseFontStMin )
        m_aBaseFontStack.erase( m_aBaseFontStack.begin() + m_aBaseFontStack.size() - 1 );
}
 
void SwHTMLParser::NewFontAttr( HtmlTokenId nToken )
{
    sal_uInt16 nBaseSize =
        ( m_aBaseFontStack.size() > m_nBaseFontStMin
            ? (m_aBaseFontStack[m_aBaseFontStack.size()-1] & FONTSIZE_MASK)
            : 3 );
    sal_uInt16 nFontSize =
        ( m_aFontStack.size() > m_nFontStMin
            ? (m_aFontStack[m_aFontStack.size()-1] & FONTSIZE_MASK)
            : nBaseSize );
 
    OUString aFace, aId, aStyle, aClass, aLang, aDir;
    Color aColor;
    sal_uLong nFontHeight = 0;  // actual font height to set
    sal_uInt32 nSize = 0;       // font height in Netscape notation (1-7)
    bool bColor = false;
 
    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::SIZE:
            if( HtmlTokenId::FONT_ON==nToken && !rOption.GetString().isEmpty() )
            {
                sal_Int32 nSSize;
                if( '+' == rOption.GetString()[0] ||
                    '-' == rOption.GetString()[0] )
                    nSSize = o3tl::saturating_add<sal_Int32>(nBaseSize, rOption.GetSNumber());
                else
                    nSSize = static_cast<sal_Int32>(rOption.GetNumber());
 
                if( nSSize < 1 )
                    nSSize = 1;
                else if( nSSize > 7 )
                    nSSize = 7;
 
                nSize = nSSize;
                nFontHeight = m_aFontHeights[nSize-1];
            }
            break;
        case HtmlOptionId::COLOR:
            if( HtmlTokenId::FONT_ON==nToken )
            {
                rOption.GetColor( aColor );
                bColor = true;
            }
            break;
        case HtmlOptionId::FACE:
            if( HtmlTokenId::FONT_ON==nToken )
                aFace = rOption.GetString();
            break;
        case HtmlOptionId::ID:
            aId = rOption.GetString();
            break;
        case HtmlOptionId::STYLE:
            aStyle = rOption.GetString();
            break;
        case HtmlOptionId::CLASS:
            aClass = rOption.GetString();
            break;
        case HtmlOptionId::LANG:
            aLang = rOption.GetString();
            break;
        case HtmlOptionId::DIR:
            aDir = rOption.GetString();
            break;
        default: break;
        }
    }
 
    if( HtmlTokenId::FONT_ON != nToken )
    {
        // HTML_BIGPRINT_ON or HTML_SMALLPRINT_ON
 
        // In headings the current heading sets the font height
        // and not BASEFONT.
        const SwFormatColl *pColl = GetCurrFormatColl();
        sal_uInt16 nPoolId = pColl ? pColl->GetPoolFormatId() : 0;
        if( nPoolId>=RES_POOLCOLL_HEADLINE1 &&
            nPoolId<=RES_POOLCOLL_HEADLINE6 )
        {
            // If the font height in the heading wasn't changed yet,
            // then take the one from the style.
            if( m_nFontStHeadStart==m_aFontStack.size() )
                nFontSize = static_cast< sal_uInt16 >(6 - (nPoolId - RES_POOLCOLL_HEADLINE1));
        }
        else
            nPoolId = 0;
 
        if( HtmlTokenId::BIGPRINT_ON == nToken )
            nSize = ( nFontSize<7 ? nFontSize+1 : 7 );
        else
            nSize = ( nFontSize>1 ? nFontSize-1 : 1 );
 
        // If possible in headlines we fetch the new font height
        // from the style.
        if( nPoolId && nSize>=1 && nSize <=6 )
            nFontHeight =
                m_pCSS1Parser->GetTextCollFromPool(
                    RES_POOLCOLL_HEADLINE1+6-nSize )->GetSize().GetHeight();
        else
            nFontHeight = m_aFontHeights[nSize-1];
    }
 
    OSL_ENSURE( !nSize == !nFontHeight, "HTML-Font-Size != Font-Height" );
 
    OUString aFontName;
    const OUString aStyleName;
    FontFamily eFamily = FAMILY_DONTKNOW;   // family and pitch,
    FontPitch ePitch = PITCH_DONTKNOW;      // if not found
    rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
 
    if( !aFace.isEmpty() && !m_pCSS1Parser->IsIgnoreFontFamily() )
    {
        const FontList *pFList = nullptr;
        SwDocShell *pDocSh = m_xDoc->GetDocShell();
        if( pDocSh )
        {
            const SvxFontListItem *pFListItem =
               static_cast<const SvxFontListItem *>(pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST));
            if( pFListItem )
                pFList = pFListItem->GetFontList();
        }
 
        bool bFound = false;
        sal_Int32 nStrPos = 0;
        while( nStrPos!= -1 )
        {
            OUString aFName = aFace.getToken( 0, ',', nStrPos );
            aFName = comphelper::string::strip(aFName, ' ');
            if( !aFName.isEmpty() )
            {
                if( !bFound && pFList )
                {
                    sal_Handle hFont = pFList->GetFirstFontMetric( aFName );
                    if( nullptr != hFont )
                    {
                        const FontMetric& rFMetric = FontList::GetFontMetric( hFont );
                        if( RTL_TEXTENCODING_DONTKNOW != rFMetric.GetCharSet() )
                        {
                            bFound = true;
                            if( RTL_TEXTENCODING_SYMBOL == rFMetric.GetCharSet() )
                                eEnc = RTL_TEXTENCODING_SYMBOL;
                        }
                    }
                }
                if( !aFontName.isEmpty() )
                    aFontName += ";";
                aFontName += aFName;
            }
        }
    }
 
    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
 
    // parse styles
    if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if( nFontHeight )
        {
            SvxFontHeightItem aFontHeight( nFontHeight, 100, RES_CHRATR_FONTSIZE );
            aItemSet.Put( aFontHeight );
            SvxFontHeightItem aFontHeightCJK( nFontHeight, 100, RES_CHRATR_CJK_FONTSIZE );
            aItemSet.Put( aFontHeightCJK );
            SvxFontHeightItem aFontHeightCTL( nFontHeight, 100, RES_CHRATR_CTL_FONTSIZE );
            aItemSet.Put( aFontHeightCTL );
        }
        if( bColor )
            aItemSet.Put( SvxColorItem(aColor, RES_CHRATR_COLOR) );
        if( !aFontName.isEmpty() )
        {
            SvxFontItem aFont( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_FONT );
            aItemSet.Put( aFont );
            SvxFontItem aFontCJK( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CJK_FONT );
            aItemSet.Put( aFontCJK );
            SvxFontItem aFontCTL( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CTL_FONT );
            aItemSet.Put( aFontCTL );
        }
 
        if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
 
        InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true );
    }
    else
    {
        if( nFontHeight )
        {
            SvxFontHeightItem aFontHeight( nFontHeight, 100, RES_CHRATR_FONTSIZE );
            InsertAttr( &m_xAttrTab->pFontHeight, aFontHeight, xCntxt.get() );
            SvxFontHeightItem aFontHeightCJK( nFontHeight, 100, RES_CHRATR_CJK_FONTSIZE );
            InsertAttr( &m_xAttrTab->pFontHeight, aFontHeightCJK, xCntxt.get() );
            SvxFontHeightItem aFontHeightCTL( nFontHeight, 100, RES_CHRATR_CTL_FONTSIZE );
            InsertAttr( &m_xAttrTab->pFontHeight, aFontHeightCTL, xCntxt.get() );
        }
        if( bColor )
            InsertAttr( &m_xAttrTab->pFontColor, SvxColorItem(aColor, RES_CHRATR_COLOR), xCntxt.get() );
        if( !aFontName.isEmpty() )
        {
            SvxFontItem aFont( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_FONT );
            InsertAttr( &m_xAttrTab->pFont, aFont, xCntxt.get() );
            SvxFontItem aFontCJK( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CJK_FONT );
            InsertAttr( &m_xAttrTab->pFont, aFontCJK, xCntxt.get() );
            SvxFontItem aFontCTL( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CTL_FONT );
            InsertAttr( &m_xAttrTab->pFont, aFontCTL, xCntxt.get() );
        }
    }
 
    // save the context
    PushContext(xCntxt);
 
    m_aFontStack.push_back( nSize );
}
 
void SwHTMLParser::EndFontAttr( HtmlTokenId nToken )
{
    EndTag( nToken );
 
    // avoid stack underflow in tables
    if( m_aFontStack.size() > m_nFontStMin )
        m_aFontStack.erase( m_aFontStack.begin() + m_aFontStack.size() - 1 );
}
 
void SwHTMLParser::NewPara()
{
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( AM_SPACE );
    else
        AddParSpace();
 
    m_eParaAdjust = SvxAdjust::End;
    OUString aId, aStyle, aClass, aLang, aDir;
 
    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::ALIGN:
                m_eParaAdjust = rOption.GetEnum( aHTMLPAlignTable, m_eParaAdjust );
                break;
            case HtmlOptionId::STYLE:
                aStyle = rOption.GetString();
                break;
            case HtmlOptionId::CLASS:
                aClass = rOption.GetString();
                break;
            case HtmlOptionId::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;
            case HtmlOptionId::XML_SPACE:
                if (rOption.GetString() == "preserve")
                    SetPreserveSpaces(true);
                break;
 
            default: break;
        }
    }
 
    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(
        !aClass.isEmpty() ? new HTMLAttrContext( HtmlTokenId::PARABREAK_ON,
                                             RES_POOLCOLL_TEXT, aClass )
                     : new HTMLAttrContext( HtmlTokenId::PARABREAK_ON ));
 
    // parse styles (Don't consider class. This is only possible as long as none of
    // the CSS1 properties of the class must be formatted hard!!!)
    if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir))
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir))
        {
            OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ),
                    "Class is not considered" );
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
            InsertAttrs( aItemSet, aPropInfo, xCntxt.get() );
 
            if (aPropInfo.m_bPreserveSpace)
                SetPreserveSpaces(true);
        }
    }
 
    if( SvxAdjust::End != m_eParaAdjust )
        InsertAttr( &m_xAttrTab->pAdjust, SvxAdjustItem(m_eParaAdjust, RES_PARATR_ADJUST), xCntxt.get() );
 
    // and push on stack
    PushContext( xCntxt );
 
    // set the current style or its attributes
    SetTextCollAttrs( !aClass.isEmpty() ? m_aContexts.back().get() : nullptr );
 
    // progress bar
    ShowStatline();
 
    OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE, "Now an open paragraph element will be lost." );
    m_nOpenParaToken = HtmlTokenId::PARABREAK_ON;
}
 
void SwHTMLParser::EndPara( bool bReal )
{
    if (HtmlTokenId::LI_ON==m_nOpenParaToken && m_xTable)
    {
#if OSL_DEBUG_LEVEL > 0
        const SwNumRule *pNumRule = m_pPam->GetPointNode().GetTextNode()->GetNumRule();
        OSL_ENSURE( pNumRule, "Where is the NumRule" );
#endif
    }
 
    // Netscape skips empty paragraphs, we do the same; unless in XHTML mode, which prefers mapping
    // the source document to the doc model 1:1 if possible.
    if( bReal )
    {
        if (m_pPam->GetPoint()->GetContentIndex() || m_bXHTML)
            AppendTextNode( AM_SPACE );
        else
            AddParSpace();
    }
 
    // If a DD or DT was open, it's an implied definition list,
    // which must be closed now.
    if( (m_nOpenParaToken == HtmlTokenId::DT_ON || m_nOpenParaToken == HtmlTokenId::DD_ON) &&
        m_nDefListDeep)
    {
        m_nDefListDeep--;
    }
 
    // Pop the context of the stack. It can also be from an
    // implied opened definition list.
    std::unique_ptr<HTMLAttrContext> xCntxt(
        PopContext( m_nOpenParaToken != HtmlTokenId::NONE ? getOnToken(m_nOpenParaToken) : HtmlTokenId::PARABREAK_ON ));
 
    // close attribute
    if (xCntxt)
    {
        EndContext(xCntxt.get());
        SetAttr();  // because of JavaScript set paragraph attributes as fast as possible
        xCntxt.reset();
    }
 
    // reset the existing style
    if( bReal )
        SetTextCollAttrs();
 
    m_nOpenParaToken = HtmlTokenId::NONE;
    SetPreserveSpaces(false);
}
 
void SwHTMLParser::NewHeading( HtmlTokenId nToken )
{
    m_eParaAdjust = SvxAdjust::End;
 
    OUString aId, aStyle, aClass, aLang, aDir;
 
    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::ALIGN:
                m_eParaAdjust = rOption.GetEnum( aHTMLPAlignTable, m_eParaAdjust );
                break;
            case HtmlOptionId::STYLE:
                aStyle = rOption.GetString();
                break;
            case HtmlOptionId::CLASS:
                aClass = rOption.GetString();
                break;
            case HtmlOptionId::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;
            default: break;
        }
    }
 
    // open a new paragraph
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( AM_SPACE );
    else
        AddParSpace();
 
    // search for the matching style
    sal_uInt16 nTextColl;
    switch( nToken )
    {
    case HtmlTokenId::HEAD1_ON:         nTextColl = RES_POOLCOLL_HEADLINE1;  break;
    case HtmlTokenId::HEAD2_ON:         nTextColl = RES_POOLCOLL_HEADLINE2;  break;
    case HtmlTokenId::HEAD3_ON:         nTextColl = RES_POOLCOLL_HEADLINE3;  break;
    case HtmlTokenId::HEAD4_ON:         nTextColl = RES_POOLCOLL_HEADLINE4;  break;
    case HtmlTokenId::HEAD5_ON:         nTextColl = RES_POOLCOLL_HEADLINE5;  break;
    case HtmlTokenId::HEAD6_ON:         nTextColl = RES_POOLCOLL_HEADLINE6;  break;
    default:                    nTextColl = RES_POOLCOLL_STANDARD;   break;
    }
 
    // create the context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken, nTextColl, aClass));
 
    // parse styles (regarding class see also NewPara)
    if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir))
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir))
        {
            OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ),
                    "Class is not considered" );
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
            InsertAttrs( aItemSet, aPropInfo, xCntxt.get() );
        }
    }
 
    if( SvxAdjust::End != m_eParaAdjust )
        InsertAttr( &m_xAttrTab->pAdjust, SvxAdjustItem(m_eParaAdjust, RES_PARATR_ADJUST), xCntxt.get() );
 
    // and push on stack
    PushContext(xCntxt);
 
    // set the current style or its attributes
    SetTextCollAttrs(m_aContexts.back().get());
 
    m_nFontStHeadStart = m_aFontStack.size();
 
    // progress bar
    ShowStatline();
}
 
void SwHTMLParser::EndHeading()
{
    // open a new paragraph
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( AM_SPACE );
    else
        AddParSpace();
 
    // search context matching the token and fetch it from stack
    std::unique_ptr<HTMLAttrContext> xCntxt;
    auto nPos = m_aContexts.size();
    while( !xCntxt && nPos>m_nContextStMin )
    {
        switch( m_aContexts[--nPos]->GetToken() )
        {
        case HtmlTokenId::HEAD1_ON:
        case HtmlTokenId::HEAD2_ON:
        case HtmlTokenId::HEAD3_ON:
        case HtmlTokenId::HEAD4_ON:
        case HtmlTokenId::HEAD5_ON:
        case HtmlTokenId::HEAD6_ON:
            xCntxt = std::move(m_aContexts[nPos]);
            m_aContexts.erase( m_aContexts.begin() + nPos );
            break;
        default: break;
        }
    }
 
    // and now end attributes
    if (xCntxt)
    {
        EndContext(xCntxt.get());
        SetAttr();  // because of JavaScript set paragraph attributes as fast as possible
        xCntxt.reset();
    }
 
    // reset existing style
    SetTextCollAttrs();
 
    m_nFontStHeadStart = m_nFontStMin;
}
 
void SwHTMLParser::NewTextFormatColl( HtmlTokenId nToken, sal_uInt16 nColl )
{
    OUString aId, aStyle, aClass, aLang, aDir;
 
    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::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;
            default: break;
        }
    }
 
    // open a new paragraph
    SwHTMLAppendMode eMode = AM_NORMAL;
    switch( nToken )
    {
    case HtmlTokenId::LISTING_ON:
    case HtmlTokenId::XMP_ON:
        // These both tags will be mapped to the PRE style. For the case that a
        // a CLASS exists we will delete it so that we don't get the CLASS of
        // the PRE style.
        aClass.clear();
        [[fallthrough]];
    case HtmlTokenId::BLOCKQUOTE_ON:
    case HtmlTokenId::BLOCKQUOTE30_ON:
    case HtmlTokenId::PREFORMTXT_ON:
        eMode = AM_SPACE;
        break;
    case HtmlTokenId::ADDRESS_ON:
        eMode = AM_NOSPACE; // ADDRESS can follow on a <P> without </P>
        break;
    case HtmlTokenId::DT_ON:
    case HtmlTokenId::DD_ON:
        eMode = AM_SOFTNOSPACE;
        break;
    default:
        OSL_ENSURE( false, "unknown style" );
        break;
    }
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( eMode );
    else if( AM_SPACE==eMode )
        AddParSpace();
 
    // ... and save in a context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken, nColl, aClass));
 
    // parse styles (regarding class see also NewPara)
    if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir))
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir))
        {
            OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ),
                    "Class is not considered" );
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
            InsertAttrs( aItemSet, aPropInfo, xCntxt.get() );
        }
    }
 
    PushContext(xCntxt);
 
    // set the new style
    SetTextCollAttrs(m_aContexts.back().get());
 
    // update progress bar
    ShowStatline();
}
 
void SwHTMLParser::EndTextFormatColl( HtmlTokenId nToken )
{
    SwHTMLAppendMode eMode = AM_NORMAL;
    switch( getOnToken(nToken) )
    {
    case HtmlTokenId::BLOCKQUOTE_ON:
    case HtmlTokenId::BLOCKQUOTE30_ON:
    case HtmlTokenId::PREFORMTXT_ON:
    case HtmlTokenId::LISTING_ON:
    case HtmlTokenId::XMP_ON:
        eMode = AM_SPACE;
        break;
    case HtmlTokenId::ADDRESS_ON:
    case HtmlTokenId::DT_ON:
    case HtmlTokenId::DD_ON:
        eMode = AM_SOFTNOSPACE;
        break;
    default:
        OSL_ENSURE( false, "unknown style" );
        break;
    }
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( eMode );
    else if( AM_SPACE==eMode )
        AddParSpace();
 
    // pop current context of stack
    std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(getOnToken(nToken)));
 
    // and now end attributes
    if (xCntxt)
    {
        EndContext(xCntxt.get());
        SetAttr();  // because of JavaScript set paragraph attributes as fast as possible
        xCntxt.reset();
    }
 
    // reset existing style
    SetTextCollAttrs();
}
 
void SwHTMLParser::NewDefList()
{
    OUString aId, aStyle, aClass, aLang, aDir;
 
    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::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;
            default: break;
        }
    }
 
    // open a new paragraph
    bool bSpace = (GetNumInfo().GetDepth() + m_nDefListDeep) == 0;
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( bSpace ? AM_SPACE : AM_SOFTNOSPACE );
    else if( bSpace )
        AddParSpace();
 
    // one level more
    m_nDefListDeep++;
 
    bool bInDD = false, bNotInDD = false;
    auto nPos = m_aContexts.size();
    while( !bInDD && !bNotInDD && nPos>m_nContextStMin )
    {
        HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken();
        switch( nCntxtToken )
        {
        case HtmlTokenId::DEFLIST_ON:
        case HtmlTokenId::DIRLIST_ON:
        case HtmlTokenId::MENULIST_ON:
        case HtmlTokenId::ORDERLIST_ON:
        case HtmlTokenId::UNORDERLIST_ON:
            bNotInDD = true;
            break;
        case HtmlTokenId::DD_ON:
            bInDD = true;
            break;
        default: break;
        }
    }
 
    // ... and save in a context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::DEFLIST_ON));
 
    // in it save also the margins
    sal_uInt16 nLeft=0, nRight=0;
    short nIndent=0;
    GetMarginsFromContext( nLeft, nRight, nIndent );
 
    // The indentation, which already results from a DL, correlates with a DT
    // on the current level and this correlates to a DD from the previous level.
    // For a level >=2 we must add DD distance.
    if( !bInDD && m_nDefListDeep > 1 )
    {
 
        // and the one of the DT-style of the current level
        SvxTextLeftMarginItem const& rTextLeftMargin =
            m_pCSS1Parser->GetTextFormatColl(RES_POOLCOLL_HTML_DD, OUString())
                       ->GetTextLeftMargin();
        nLeft = nLeft + static_cast<sal_uInt16>(rTextLeftMargin.GetTextLeft());
    }
 
    xCntxt->SetMargins( nLeft, nRight, nIndent );
 
    // parse styles
    if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) )
        {
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
            InsertAttrs( aItemSet, aPropInfo, xCntxt.get() );
        }
    }
 
    PushContext(xCntxt);
 
    // set the attributes of the new style
    if( m_nDefListDeep > 1 )
        SetTextCollAttrs(m_aContexts.back().get());
}
 
void SwHTMLParser::EndDefList()
{
    bool bSpace = (GetNumInfo().GetDepth() + m_nDefListDeep) == 1;
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( bSpace ? AM_SPACE : AM_SOFTNOSPACE );
    else if( bSpace )
        AddParSpace();
 
    // one level less
    if( m_nDefListDeep > 0 )
        m_nDefListDeep--;
 
    // pop current context of stack
    std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(HtmlTokenId::DEFLIST_ON));
 
    // and now end attributes
    if (xCntxt)
    {
        EndContext(xCntxt.get());
        SetAttr();  // because of JavaScript set paragraph attributes as fast as possible
        xCntxt.reset();
    }
 
    // and set style
    SetTextCollAttrs();
}
 
void SwHTMLParser::NewDefListItem( HtmlTokenId nToken )
{
    // determine if the DD/DT exist in a DL
    bool bInDefList = false, bNotInDefList = false;
    auto nPos = m_aContexts.size();
    while( !bInDefList && !bNotInDefList && nPos>m_nContextStMin )
    {
        HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken();
        switch( nCntxtToken )
        {
        case HtmlTokenId::DEFLIST_ON:
            bInDefList = true;
            break;
        case HtmlTokenId::DIRLIST_ON:
        case HtmlTokenId::MENULIST_ON:
        case HtmlTokenId::ORDERLIST_ON:
        case HtmlTokenId::UNORDERLIST_ON:
            bNotInDefList = true;
            break;
        default: break;
        }
    }
 
    // if not, then implicitly open a new DL
    if( !bInDefList )
    {
        m_nDefListDeep++;
        OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE,
                "Now an open paragraph element will be lost." );
        m_nOpenParaToken = nToken;
    }
 
    NewTextFormatColl( nToken, static_cast< sal_uInt16 >(nToken==HtmlTokenId::DD_ON ? RES_POOLCOLL_HTML_DD
                                              : RES_POOLCOLL_HTML_DT) );
}
 
void SwHTMLParser::EndDefListItem( HtmlTokenId nToken )
{
    // open a new paragraph
    if( nToken == HtmlTokenId::NONE && m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( AM_SOFTNOSPACE );
 
    // search context matching the token and fetch it from stack
    nToken = getOnToken(nToken);
    std::unique_ptr<HTMLAttrContext> xCntxt;
    auto nPos = m_aContexts.size();
    while( !xCntxt && nPos>m_nContextStMin )
    {
        HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken();
        switch( nCntxtToken )
        {
        case HtmlTokenId::DD_ON:
        case HtmlTokenId::DT_ON:
            if( nToken == HtmlTokenId::NONE || nToken == nCntxtToken  )
            {
                xCntxt = std::move(m_aContexts[nPos]);
                m_aContexts.erase( m_aContexts.begin() + nPos );
            }
            break;
        case HtmlTokenId::DEFLIST_ON:
            // don't look at DD/DT outside the current DefList
        case HtmlTokenId::DIRLIST_ON:
        case HtmlTokenId::MENULIST_ON:
        case HtmlTokenId::ORDERLIST_ON:
        case HtmlTokenId::UNORDERLIST_ON:
            // and also not outside another list
            nPos = m_nContextStMin;
            break;
        default: break;
        }
    }
 
    // and now end attributes
    if (xCntxt)
    {
        EndContext(xCntxt.get());
        SetAttr();  // because of JavaScript set paragraph attributes as fast as possible
    }
}
 
/**
 *
 * @param bNoSurroundOnly   The paragraph contains at least one frame
 *                          without wrapping.
 * @param bSurroundOnly     The paragraph contains at least one frame
 *                          with wrapping, but none without wrapping.
 *
 *                          Otherwise the paragraph contains any frame.
 */
bool SwHTMLParser::HasCurrentParaFlys( bool bNoSurroundOnly,
                                       bool bSurroundOnly ) const
{
    SwNode& rNode = m_pPam->GetPoint()->GetNode();
 
 
    bool bFound = false;
    for(sw::SpzFrameFormat* pFormat: *m_xDoc->GetSpzFrameFormats())
    {
        SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
        // A frame was found, when
        // - it is paragraph-bound, and
        // - is anchored in current paragraph, and
        //   - every paragraph-bound frame counts, or
        //   - (only frames without wrapping count and) the frame doesn't have
        //     a wrapping
        SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
        if (pAnchorNode &&
            ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
             (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
            *pAnchorNode == rNode )
        {
            if( !(bNoSurroundOnly || bSurroundOnly) )
            {
                bFound = true;
                break;
            }
            else
            {
                // When looking for frames with wrapping, also disregard
                // ones with wrap-through. In this case it's (still) HIDDEN-Controls,
                // and you don't want to evade those when positioning.
                css::text::WrapTextMode eSurround = pFormat->GetSurround().GetSurround();
                if( bNoSurroundOnly )
                {
                    if( css::text::WrapTextMode_NONE==eSurround )
                    {
                        bFound = true;
                        break;
                    }
                }
                if( bSurroundOnly )
                {
                    if( css::text::WrapTextMode_NONE==eSurround )
                    {
                        bFound = false;
                        break;
                    }
                    else if( css::text::WrapTextMode_THROUGH!=eSurround )
                    {
                        bFound = true;
                        // Continue searching: It's possible that some without
                        // wrapping will follow...
                    }
                }
            }
        }
    }
 
    return bFound;
}
 
// the special methods for inserting of objects
 
const SwFormatColl *SwHTMLParser::GetCurrFormatColl() const
{
    const SwContentNode* pCNd = m_pPam->GetPointContentNode();
    return pCNd ? &pCNd->GetAnyFormatColl() : nullptr;
}
 
void SwHTMLParser::SetTextCollAttrs( HTMLAttrContext *pContext )
{
    SwTextFormatColl *pCollToSet = nullptr; // the style to set
    SfxItemSet *pItemSet = nullptr;         // set of hard attributes
    sal_uInt16 nTopColl = pContext ? pContext->GetTextFormatColl() : 0;
    const OUString rTopClass = pContext ? pContext->GetClass() : OUString();
    sal_uInt16 nDfltColl = RES_POOLCOLL_TEXT;
 
    bool bInPRE=false;                          // some context info
 
    sal_uInt16 nLeftMargin = 0, nRightMargin = 0;   // the margins and
    short nFirstLineIndent = 0;                     // indentations
 
    auto nDepth = m_aContexts.size();
    if (bFuzzing && nDepth > 128)
    {
        SAL_WARN("sw.html", "Not applying any more text collection attributes to a deeply nested node for fuzzing performance");
        nDepth = 0;
    }
 
    for (auto i = m_nContextStAttrMin; i < nDepth; ++i)
    {
        const HTMLAttrContext *pCntxt = m_aContexts[i].get();
 
        sal_uInt16 nColl = pCntxt->GetTextFormatColl();
        if( nColl )
        {
            // There is a style to set. Then at first we must decide,
            // if the style can be set.
            bool bSetThis = true;
            switch( nColl )
            {
            case RES_POOLCOLL_HTML_PRE:
                bInPRE = true;
                break;
            case RES_POOLCOLL_TEXT:
                // <TD><P CLASS=xxx> must become TD.xxx
                if( nDfltColl==RES_POOLCOLL_TABLE ||
                    nDfltColl==RES_POOLCOLL_TABLE_HDLN )
                    nColl = nDfltColl;
                break;
            case RES_POOLCOLL_HTML_HR:
                // also <HR> in <PRE> set as style, otherwise it can't
                // be exported anymore
                break;
            default:
                if( bInPRE )
                    bSetThis = false;
                break;
            }
 
            SwTextFormatColl *pNewColl =
                m_pCSS1Parser->GetTextFormatColl( nColl, pCntxt->GetClass() );
 
            if( bSetThis )
            {
                // If now a different style should be set as previously, the
                // previous style must be replaced by hard attribution.
 
                if( pCollToSet )
                {
                    // insert the attributes hard, which previous style sets
                    if( !pItemSet )
                        pItemSet = new SfxItemSet( pCollToSet->GetAttrSet() );
                    else
                    {
                        const SfxItemSet& rCollSet = pCollToSet->GetAttrSet();
                        SfxItemSet aItemSet( *rCollSet.GetPool(),
                                             rCollSet.GetRanges() );
                        aItemSet.Set( rCollSet );
                        pItemSet->Put( aItemSet );
                    }
                    // but remove the attributes, which the current style sets,
                    // because otherwise they will be overwritten later
                    pItemSet->Differentiate( pNewColl->GetAttrSet() );
                }
 
                pCollToSet = pNewColl;
            }
            else
            {
                // hard attribution
                if( !pItemSet )
                    pItemSet = new SfxItemSet( pNewColl->GetAttrSet() );
                else
                {
                    const SfxItemSet& rCollSet = pNewColl->GetAttrSet();
                    SfxItemSet aItemSet( *rCollSet.GetPool(),
                                         rCollSet.GetRanges() );
                    aItemSet.Set( rCollSet );
                    pItemSet->Put( aItemSet );
                }
            }
        }
        else
        {
            // Maybe a default style exists?
            nColl = pCntxt->GetDefaultTextFormatColl();
            if( nColl )
                nDfltColl = nColl;
        }
 
        // if applicable fetch new paragraph indents
        if( pCntxt->IsLRSpaceChanged() )
        {
            sal_uInt16 nLeft=0, nRight=0;
 
            pCntxt->GetMargins( nLeft, nRight, nFirstLineIndent );
            nLeftMargin = nLeft;
            nRightMargin = nRight;
        }
    }
 
    // If in current context a new style should be set,
    // its paragraph margins must be inserted in the context.
    if( pContext && nTopColl )
    {
        // <TD><P CLASS=xxx> must become TD.xxx
        if( nTopColl==RES_POOLCOLL_TEXT &&
            (nDfltColl==RES_POOLCOLL_TABLE ||
             nDfltColl==RES_POOLCOLL_TABLE_HDLN) )
            nTopColl = nDfltColl;
 
        const SwTextFormatColl *pTopColl =
            m_pCSS1Parser->GetTextFormatColl( nTopColl, rTopClass );
        const SfxItemSet& rItemSet = pTopColl->GetAttrSet();
        if (rItemSet.GetItemIfSet(RES_MARGIN_FIRSTLINE)
            || rItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT)
            || rItemSet.GetItemIfSet(RES_MARGIN_RIGHT))
        {
            sal_Int32 nLeft = rItemSet.Get(RES_MARGIN_TEXTLEFT).GetTextLeft();
            sal_Int32 nRight = rItemSet.Get(RES_MARGIN_RIGHT).GetRight();
            nFirstLineIndent = rItemSet.Get(RES_MARGIN_FIRSTLINE).GetTextFirstLineOffset();
 
            // In Definition lists the margins also contain the margins from the previous levels
            if( RES_POOLCOLL_HTML_DD == nTopColl )
            {
                auto const*const pColl(m_pCSS1Parser->GetTextFormatColl(RES_POOLCOLL_HTML_DT, OUString()));
                nLeft -= pColl->GetTextLeftMargin().GetTextLeft();
                nRight -= pColl->GetRightMargin().GetRight();
            }
            else if( RES_POOLCOLL_HTML_DT == nTopColl )
            {
                nLeft = 0;
                nRight = 0;
            }
 
            // the paragraph margins add up
            nLeftMargin = nLeftMargin + static_cast< sal_uInt16 >(nLeft);
            nRightMargin = nRightMargin + static_cast< sal_uInt16 >(nRight);
 
            pContext->SetMargins( nLeftMargin, nRightMargin,
                                  nFirstLineIndent );
        }
        if( const SvxULSpaceItem* pULItem = rItemSet.GetItemIfSet(RES_UL_SPACE) )
        {
            pContext->SetULSpace( pULItem->GetUpper(), pULItem->GetLower() );
        }
    }
 
    // If no style is set in the context use the text body.
    if( !pCollToSet )
    {
        pCollToSet = m_pCSS1Parser->GetTextCollFromPool( nDfltColl );
        if( !nLeftMargin )
        {
            nLeftMargin = static_cast<sal_uInt16>(pCollToSet->GetTextLeftMargin().GetTextLeft());
        }
        if( !nRightMargin )
        {
            nRightMargin = static_cast<sal_uInt16>(pCollToSet->GetRightMargin().GetRight());
        }
        if( !nFirstLineIndent )
        {
            nFirstLineIndent = pCollToSet->GetFirstLineIndent().GetTextFirstLineOffset();
        }
    }
 
    // remove previous hard attribution of paragraph
    for( auto pParaAttr : m_aParaAttrs )
        pParaAttr->Invalidate();
    m_aParaAttrs.clear();
 
    // set the style
    m_xDoc->SetTextFormatColl( *m_pPam, pCollToSet );
 
    if (m_pTargetCharAttrs)
    {
        std::unique_ptr<SfxItemSet> const pCharSet(new SfxItemSet(*m_pTargetCharAttrs));
        pCharSet->Differentiate(pCollToSet->GetAttrSet());
        m_xDoc->getIDocumentContentOperations().InsertItemSet(*m_pPam, *pCharSet);
    }
 
    // if applicable correct the paragraph indent
    const SvxFirstLineIndentItem & rFirstLine = pCollToSet->GetFirstLineIndent();
    const SvxTextLeftMarginItem & rTextLeftMargin = pCollToSet->GetTextLeftMargin();
    const SvxRightMarginItem & rRightMargin = pCollToSet->GetRightMargin();
    bool bSetLRSpace = nLeftMargin != rTextLeftMargin.GetTextLeft() ||
                      nFirstLineIndent != rFirstLine.GetTextFirstLineOffset() ||
                      nRightMargin != rRightMargin.GetRight();
 
    if( bSetLRSpace )
    {
        SvxFirstLineIndentItem firstLine(rFirstLine);
        SvxTextLeftMarginItem leftMargin(rTextLeftMargin);
        SvxRightMarginItem rightMargin(rRightMargin);
        firstLine.SetTextFirstLineOffset(nFirstLineIndent);
        leftMargin.SetTextLeft(nLeftMargin);
        rightMargin.SetRight(nRightMargin);
        if( pItemSet )
        {
            pItemSet->Put(firstLine);
            pItemSet->Put(leftMargin);
            pItemSet->Put(rightMargin);
        }
        else
        {
            NewAttr(m_xAttrTab, &m_xAttrTab->pFirstLineIndent, firstLine);
            m_xAttrTab->pFirstLineIndent->SetLikePara();
            m_aParaAttrs.push_back(m_xAttrTab->pFirstLineIndent);
            EndAttr(m_xAttrTab->pFirstLineIndent, false);
            NewAttr(m_xAttrTab, &m_xAttrTab->pTextLeftMargin, leftMargin);
            m_xAttrTab->pTextLeftMargin->SetLikePara();
            m_aParaAttrs.push_back(m_xAttrTab->pTextLeftMargin);
            EndAttr(m_xAttrTab->pTextLeftMargin, false);
            NewAttr(m_xAttrTab, &m_xAttrTab->pRightMargin, rightMargin);
            m_xAttrTab->pRightMargin->SetLikePara();
            m_aParaAttrs.push_back(m_xAttrTab->pRightMargin);
            EndAttr(m_xAttrTab->pRightMargin, false);
        }
    }
 
    // and now set the attributes
    if( pItemSet )
    {
        InsertParaAttrs( *pItemSet );
        delete pItemSet;
    }
}
 
void SwHTMLParser::NewCharFormat( HtmlTokenId nToken )
{
    OUString aId, aStyle, aLang, aDir;
    OUString aClass;
 
    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::LANG:
            aLang = rOption.GetString();
            break;
        case HtmlOptionId::DIR:
            aDir = rOption.GetString();
            break;
        default: break;
        }
    }
 
    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken));
 
    // set the style and save it in the context
    SwCharFormat* pCFormat = m_pCSS1Parser->GetChrFormat( nToken, aClass );
    OSL_ENSURE( pCFormat, "No character format found for token" );
 
    // parse styles (regarding class see also NewPara)
    if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir))
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir))
        {
            OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ),
                    "Class is not considered" );
            DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
            InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true );
        }
    }
 
    // Character formats are stored in their own stack and can never be inserted
    // by styles. Therefore the attribute doesn't exist in CSS1-Which-Range.
    if( pCFormat )
        InsertAttr( &m_xAttrTab->pCharFormats, SwFormatCharFormat( pCFormat ), xCntxt.get() );
 
    // save the context
    PushContext(xCntxt);
}
 
void SwHTMLParser::InsertSpacer()
{
    // and if applicable change it via the options
    sal_Int16 eVertOri = text::VertOrientation::TOP;
    sal_Int16 eHoriOri = text::HoriOrientation::NONE;
    Size aSize( 0, 0);
    tools::Long nSize = 0;
    bool bPercentWidth = false;
    bool bPercentHeight = false;
    sal_uInt16 nType = HTML_SPTYPE_HORI;
 
    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
        case HtmlOptionId::TYPE:
            rOption.GetEnum( nType, aHTMLSpacerTypeTable );
            break;
        case HtmlOptionId::ALIGN:
            eVertOri =
                rOption.GetEnum( aHTMLImgVAlignTable,
                                  eVertOri );
            eHoriOri =
                rOption.GetEnum( aHTMLImgHAlignTable,
                                  eHoriOri );
            break;
        case HtmlOptionId::WIDTH:
            // First only save as pixel value!
            bPercentWidth = (rOption.GetString().indexOf('%') != -1);
            aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::HEIGHT:
            // First only save as pixel value!
            bPercentHeight = (rOption.GetString().indexOf('%') != -1);
            aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) );
            break;
        case HtmlOptionId::SIZE:
            // First only save as pixel value!
            nSize = rOption.GetNumber();
            break;
        default: break;
        }
    }
 
    switch( nType )
    {
    case HTML_SPTYPE_BLOCK:
        {
            // create an empty text frame
 
            // 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, aFrameSet );
 
            // and the size of the frame
            Size aDfltSz( MINFLY, MINFLY );
            Size aSpace( 0, 0 );
            SfxItemSet aDummyItemSet( m_xDoc->GetAttrPool(),
                                 m_pCSS1Parser->GetWhichMap() );
            SvxCSS1PropertyInfo aDummyPropInfo;
 
            SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight,
                        aDummyPropInfo, aFrameSet );
            SetSpace( aSpace, aDummyItemSet, aDummyPropInfo, aFrameSet );
 
            // protect the content
            SvxProtectItem aProtectItem( RES_PROTECT) ;
            aProtectItem.SetContentProtect( true );
            aFrameSet.Put( aProtectItem );
 
            // create the frame
            RndStdIds eAnchorId =
                aFrameSet.Get(RES_ANCHOR).GetAnchorId();
            SwFrameFormat *pFlyFormat = m_xDoc->MakeFlySection( eAnchorId,
                                            m_pPam->GetPoint(), &aFrameSet );
            // Possibly create frames and register auto-bound frames.
            RegisterFlyFrame( pFlyFormat );
        }
        break;
    case HTML_SPTYPE_VERT:
        if( nSize > 0 )
        {
            nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip);
 
            // set a paragraph margin
            SwTextNode *pTextNode = nullptr;
            if( !m_pPam->GetPoint()->GetContentIndex() )
            {
                // if possible change the bottom paragraph margin
                // of previous node
 
                SetAttr();  // set still open paragraph attributes
 
                pTextNode = m_xDoc->GetNodes()[m_pPam->GetPoint()->GetNodeIndex()-1]
                               ->GetTextNode();
 
                // If the previous paragraph isn't a text node, then now an
                // empty paragraph is created, which already generates a single
                // line of spacing.
                if( !pTextNode )
                    nSize = nSize>HTML_PARSPACE ? nSize-HTML_PARSPACE : 0;
            }
 
            if( pTextNode )
            {
                SvxULSpaceItem aULSpace( pTextNode->SwContentNode::GetAttr( RES_UL_SPACE ) );
                aULSpace.SetLower( aULSpace.GetLower() + o3tl::narrowing<sal_uInt16>(nSize) );
                pTextNode->SetAttr( aULSpace );
            }
            else
            {
                NewAttr(m_xAttrTab, &m_xAttrTab->pULSpace, SvxULSpaceItem(0, o3tl::narrowing<sal_uInt16>(nSize), RES_UL_SPACE));
                EndAttr( m_xAttrTab->pULSpace, false );
 
                AppendTextNode();    // Don't change spacing!
            }
        }
        break;
    case HTML_SPTYPE_HORI:
        if( nSize > 0 )
        {
            // If the paragraph is still empty, set first line
            // indentation, otherwise apply letter spacing over a space.
 
            nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip);
 
            if( !m_pPam->GetPoint()->GetContentIndex() )
            {
                sal_uInt16 nLeft=0, nRight=0;
                short nIndent = 0;
 
                GetMarginsFromContextWithNumberBullet( nLeft, nRight, nIndent );
                nIndent = nIndent + static_cast<short>(nSize);
 
                SvxFirstLineIndentItem const firstLine(nIndent, RES_MARGIN_FIRSTLINE);
                SvxTextLeftMarginItem const leftMargin(nLeft, RES_MARGIN_TEXTLEFT);
                SvxRightMarginItem const rightMargin(nRight, RES_MARGIN_RIGHT);
 
                NewAttr(m_xAttrTab, &m_xAttrTab->pFirstLineIndent, firstLine);
                EndAttr(m_xAttrTab->pFirstLineIndent, false);
                NewAttr(m_xAttrTab, &m_xAttrTab->pTextLeftMargin, leftMargin);
                EndAttr(m_xAttrTab->pTextLeftMargin, false);
                NewAttr(m_xAttrTab, &m_xAttrTab->pRightMargin, rightMargin);
                EndAttr(m_xAttrTab->pRightMargin, false);
            }
            else
            {
                NewAttr(m_xAttrTab, &m_xAttrTab->pKerning, SvxKerningItem( static_cast<short>(nSize), RES_CHRATR_KERNING ));
                m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, u" "_ustr );
                EndAttr( m_xAttrTab->pKerning );
            }
        }
    }
}
 
sal_uInt16 SwHTMLParser::ToTwips( sal_uInt16 nPixel )
{
    return std::min(o3tl::convert(nPixel, o3tl::Length::px, o3tl::Length::twip),
                    sal_Int64(SAL_MAX_UINT16));
}
 
SwTwips SwHTMLParser::GetCurrentBrowseWidth()
{
    const SwTwips nWidth = SwHTMLTableLayout::GetBrowseWidth( *m_xDoc );
    if( nWidth )
        return nWidth;
 
    if( !m_aHTMLPageSize.Width() )
    {
        const SwFrameFormat& rPgFormat = m_pCSS1Parser->GetMasterPageDesc()->GetMaster();
 
        const SwFormatFrameSize& rSz   = rPgFormat.GetFrameSize();
        const SvxLRSpaceItem& rLR = rPgFormat.GetLRSpace();
        const SvxULSpaceItem& rUL = rPgFormat.GetULSpace();
        const SwFormatCol& rCol = rPgFormat.GetCol();
 
        m_aHTMLPageSize.setWidth( rSz.GetWidth() - rLR.GetLeft() - rLR.GetRight() );
        m_aHTMLPageSize.setHeight( rSz.GetHeight() - rUL.GetUpper() - rUL.GetLower() );
 
        if( 1 < rCol.GetNumCols() )
            m_aHTMLPageSize.setWidth( m_aHTMLPageSize.Width() / ( rCol.GetNumCols()) );
    }
 
    return m_aHTMLPageSize.Width();
}
 
void SwHTMLParser::InsertIDOption()
{
    OUString aId;
    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        if( HtmlOptionId::ID==rOption.GetToken() )
        {
            aId = rOption.GetString();
            break;
        }
    }
 
    if( !aId.isEmpty() )
        InsertBookmark( aId );
}
 
void SwHTMLParser::InsertLineBreak()
{
    OUString aId, aStyle, aClass;             // the id of bookmark
    SwLineBreakClear eClear = SwLineBreakClear::NONE;
 
    // then we fetch the options
    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
            case HtmlOptionId::CLEAR:
                {
                    const OUString &rClear = rOption.GetString();
                    if( rClear.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_all ) )
                    {
                        eClear = SwLineBreakClear::ALL;
                    }
                    else if( rClear.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) )
                    {
                        eClear = SwLineBreakClear::LEFT;
                    }
                    else if( rClear.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) )
                    {
                        eClear = SwLineBreakClear::LEFT;
                    }
                }
                break;
            case HtmlOptionId::ID:
                aId = rOption.GetString();
                break;
            case HtmlOptionId::STYLE:
                aStyle = rOption.GetString();
                break;
            case HtmlOptionId::CLASS:
                aClass = rOption.GetString();
                break;
            default: break;
        }
    }
 
    // parse styles
    std::shared_ptr<SvxFormatBreakItem> aBreakItem(std::make_shared<SvxFormatBreakItem>(SvxBreak::NONE, RES_BREAK));
    bool bBreakItem = false;
    if( HasStyleOptions( aStyle, aId, aClass ) )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
 
        if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ) )
        {
            if( m_pCSS1Parser->SetFormatBreak( aItemSet, aPropInfo ) )
            {
                aBreakItem.reset(aItemSet.Get(RES_BREAK).Clone());
                bBreakItem = true;
            }
            if( !aPropInfo.m_aId.isEmpty() )
                InsertBookmark( aPropInfo.m_aId );
        }
    }
 
    if( bBreakItem && SvxBreak::PageAfter == aBreakItem->GetBreak() )
    {
        NewAttr(m_xAttrTab, &m_xAttrTab->pBreak, *aBreakItem);
        EndAttr( m_xAttrTab->pBreak, false );
    }
 
    if (!bBreakItem)
    {
        if (eClear == SwLineBreakClear::NONE)
        {
            // If no CLEAR could or should be executed, a line break will be inserted
            m_xDoc->getIDocumentContentOperations().InsertString(*m_pPam, u"\x0A"_ustr);
        }
        else
        {
            // <BR CLEAR=xxx> is mapped an SwFormatLineBreak.
            SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
            if (pTextNode)
            {
                SwFormatLineBreak aLineBreak(eClear);
                sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex();
                pTextNode->InsertItem(aLineBreak, nPos, nPos);
            }
        }
    }
    else if( m_pPam->GetPoint()->GetContentIndex() )
    {
        // If a CLEAR is executed in a non-empty paragraph, then after it
        // a new paragraph has to be opened.
        // MIB 21.02.97: Here actually we should change the bottom paragraph
        // margin to zero. This will fail for something like this <BR ..><P>
        // (>Netscape). That's why we don't do it.
        AppendTextNode( AM_NOSPACE );
    }
    if( bBreakItem && SvxBreak::PageBefore == aBreakItem->GetBreak() )
    {
        NewAttr(m_xAttrTab, &m_xAttrTab->pBreak, *aBreakItem);
        EndAttr( m_xAttrTab->pBreak, false );
    }
}
 
void SwHTMLParser::InsertHorzRule()
{
    sal_uInt16 nSize = 0;
    sal_uInt16 nWidth = 0;
 
    SvxAdjust eAdjust = SvxAdjust::End;
 
    bool bPercentWidth = false;
    bool bNoShade = false;
    bool bColor = false;
 
    Color aColor;
    OUString aId;
 
    // let's fetch the options
    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::SIZE:
            nSize = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            break;
        case HtmlOptionId::WIDTH:
            bPercentWidth = (rOption.GetString().indexOf('%') != -1);
            nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
            if( bPercentWidth && nWidth>=100 )
            {
                // the default case are 100% lines (no attributes necessary)
                nWidth = 0;
                bPercentWidth = false;
            }
            break;
        case HtmlOptionId::ALIGN:
            eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust );
            break;
        case HtmlOptionId::NOSHADE:
            bNoShade = true;
            break;
        case HtmlOptionId::COLOR:
            rOption.GetColor( aColor );
            bColor = true;
            break;
        default: break;
        }
    }
 
    if( m_pPam->GetPoint()->GetContentIndex() )
        AppendTextNode( AM_NOSPACE );
    if( m_nOpenParaToken != HtmlTokenId::NONE )
        EndPara();
    AppendTextNode();
    m_pPam->Move( fnMoveBackward );
 
    // ...and save in a context
    std::unique_ptr<HTMLAttrContext> xCntxt(
        new HTMLAttrContext(HtmlTokenId::HORZRULE, RES_POOLCOLL_HTML_HR, OUString()));
 
    PushContext(xCntxt);
 
    // set the new style
    SetTextCollAttrs(m_aContexts.back().get());
 
    // the hard attributes of the current paragraph will never become invalid
    m_aParaAttrs.clear();
 
    if( nSize>0 || bColor || bNoShade )
    {
        // set line colour and/or width
        if( !bColor )
            aColor = COL_GRAY;
 
        SvxBorderLine aBorderLine( &aColor );
        if( nSize )
        {
            tools::Long nPWidth = 0;
            tools::Long nPHeight = static_cast<tools::Long>(nSize);
            SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight );
            if ( !bNoShade )
            {
                aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
            }
            aBorderLine.SetWidth( nPHeight );
        }
        else if( bNoShade )
        {
            aBorderLine.SetWidth( SvxBorderLineWidth::Medium );
        }
        else
        {
            aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
            aBorderLine.SetWidth(SvxBorderLineWidth::Hairline);
        }
 
        SvxBoxItem aBoxItem(RES_BOX);
        aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::BOTTOM );
        HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), aBoxItem, nullptr, std::shared_ptr<HTMLAttrTable>());
        m_aSetAttrTab.push_back( pTmp );
    }
    if( nWidth )
    {
        // If we aren't in a table, then the width value will be "faked" with
        // paragraph indents. That makes little sense in a table. In order to
        // avoid that the line is considered during the width calculation, it
        // still gets an appropriate LRSpace-Item.
        if (!m_xTable)
        {
            // fake length and alignment of line above paragraph indents
            tools::Long nBrowseWidth = GetCurrentBrowseWidth();
            nWidth = bPercentWidth ? o3tl::narrowing<sal_uInt16>((nWidth*nBrowseWidth) / 100)
                               : ToTwips( o3tl::narrowing<sal_uInt16>(nBrowseWidth) );
            if( nWidth < MINLAY )
                nWidth = MINLAY;
 
            const SwFormatColl *pColl = (static_cast<tools::Long>(nWidth) < nBrowseWidth) ? GetCurrFormatColl() : nullptr;
            if (pColl)
            {
                tools::Long nDist = nBrowseWidth - nWidth;
                ::std::optional<SvxTextLeftMarginItem> oLeft;
                ::std::optional<SvxRightMarginItem> oRight;
 
                switch( eAdjust )
                {
                case SvxAdjust::Right:
                    oLeft.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_TEXTLEFT);
                    break;
                case SvxAdjust::Left:
                    oRight.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_RIGHT);
                    break;
                case SvxAdjust::Center:
                default:
                    nDist /= 2;
                    oLeft.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_TEXTLEFT);
                    oRight.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_RIGHT);
                    break;
                }
 
                if (oLeft)
                {
                    HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), *oLeft, nullptr, std::shared_ptr<HTMLAttrTable>());
                    m_aSetAttrTab.push_back( pTmp );
                }
                if (oRight)
                {
                    HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), *oRight, nullptr, std::shared_ptr<HTMLAttrTable>());
                    m_aSetAttrTab.push_back( pTmp );
                }
            }
        }
    }
 
    // it's not possible to insert bookmarks in links
    if( !aId.isEmpty() )
        InsertBookmark( aId );
 
    // pop current context of stack
    std::unique_ptr<HTMLAttrContext> xPoppedContext(PopContext(HtmlTokenId::HORZRULE));
    xPoppedContext.reset();
 
    m_pPam->Move( fnMoveForward );
 
    // and set the current style in the next paragraph
    SetTextCollAttrs();
}
 
void SwHTMLParser::ParseMoreMetaOptions()
{
    OUString aName, aContent;
    bool bHTTPEquiv = false;
 
    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();
            bHTTPEquiv = false;
            break;
        case HtmlOptionId::HTTPEQUIV:
            aName = rOption.GetString();
            bHTTPEquiv = true;
            break;
        case HtmlOptionId::CONTENT:
            aContent = rOption.GetString();
            break;
        default: break;
        }
    }
 
    // Here things get a little tricky: We know for sure, that the Doc-Info
    // wasn't changed. Therefore it's enough to query for Generator and Refresh
    // to find a not processed Token. These are the only ones which won't change
    // the Doc-Info.
    if( aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_generator ) ||
        aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_refresh ) ||
        aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_content_type ) ||
        aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_content_script_type ) )
        return;
 
    aContent = aContent.replaceAll("\r", "").replaceAll("\n", "");
 
    if( aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_sdendnote ) )
    {
        FillEndNoteInfo( aContent );
        return;
    }
 
    if( aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_sdfootnote ) )
    {
        FillFootNoteInfo( aContent );
        return;
    }
 
    OUStringBuffer sText(
        "HTML: <"
        OOO_STRING_SVTOOLS_HTML_meta
        " ");
    if( bHTTPEquiv  )
        sText.append(OOO_STRING_SVTOOLS_HTML_O_httpequiv);
    else
        sText.append(OOO_STRING_SVTOOLS_HTML_O_name);
    sText.append(
        "=\"" + aName
        + "\" "
        OOO_STRING_SVTOOLS_HTML_O_content
        "=\""
        + aContent
        + "\">");
 
    SwPostItField aPostItField(
        static_cast<SwPostItFieldType*>(m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Postit )),
        OUString(), sText.makeStringAndClear(), OUString(), OUString(), DateTime(DateTime::SYSTEM));
    SwFormatField aFormatField( aPostItField );
    InsertAttr( aFormatField,  false );
}
 
HTMLAttr::HTMLAttr( const SwPosition& rPos, const SfxPoolItem& rItem,
                      HTMLAttr **ppHd, std::shared_ptr<HTMLAttrTable> xAttrTab ) :
    m_nStartPara( rPos.GetNode() ),
    m_nEndPara( rPos.GetNode() ),
    m_nStartContent( rPos.GetContentIndex() ),
    m_nEndContent(rPos.GetContentIndex() ),
    m_bInsAtStart( true ),
    m_bLikePara( false ),
    m_bValid( true ),
    m_pItem( rItem.Clone() ),
    m_xAttrTab(std::move( xAttrTab )),
    m_pNext( nullptr ),
    m_pPrev( nullptr ),
    m_ppHead( ppHd )
{
}
 
HTMLAttr::HTMLAttr( const HTMLAttr &rAttr, const SwNode &rEndPara,
                      sal_Int32 nEndCnt, HTMLAttr **ppHd, std::shared_ptr<HTMLAttrTable> xAttrTab ) :
    m_nStartPara( rAttr.m_nStartPara ),
    m_nEndPara( rEndPara ),
    m_nStartContent( rAttr.m_nStartContent ),
    m_nEndContent( nEndCnt ),
    m_bInsAtStart( rAttr.m_bInsAtStart ),
    m_bLikePara( rAttr.m_bLikePara ),
    m_bValid( rAttr.m_bValid ),
    m_pItem( rAttr.m_pItem->Clone() ),
    m_xAttrTab(std::move( xAttrTab )),
    m_pNext( nullptr ),
    m_pPrev( nullptr ),
    m_ppHead( ppHd )
{
}
 
HTMLAttr::~HTMLAttr()
{
}
 
HTMLAttr *HTMLAttr::Clone(const SwNode& rEndPara, sal_Int32 nEndCnt) const
{
    // create the attribute anew with old start position
    HTMLAttr *pNew = new HTMLAttr( *this, rEndPara, nEndCnt, m_ppHead, m_xAttrTab );
 
    // The Previous-List must be taken over, the Next-List not!
    pNew->m_pPrev = m_pPrev;
 
    return pNew;
}
 
void HTMLAttr::Reset(const SwNode& rSttPara, sal_Int32 nSttCnt,
                     HTMLAttr **ppHd, const std::shared_ptr<HTMLAttrTable>& rAttrTab)
{
    // reset the start (and the end)
    m_nStartPara = rSttPara;
    m_nStartContent = nSttCnt;
    m_nEndPara = rSttPara;
    m_nEndContent = nSttCnt;
 
    // correct the head and nullify link
    m_pNext = nullptr;
    m_pPrev = nullptr;
    m_ppHead = ppHd;
    m_xAttrTab = rAttrTab;
}
 
void HTMLAttr::InsertPrev( HTMLAttr *pPrv )
{
    OSL_ENSURE( !pPrv->m_pNext || pPrv->m_pNext == this,
            "HTMLAttr::InsertPrev: pNext wrong" );
    pPrv->m_pNext = nullptr;
 
    OSL_ENSURE( nullptr == pPrv->m_ppHead || m_ppHead == pPrv->m_ppHead,
            "HTMLAttr::InsertPrev: ppHead wrong" );
    pPrv->m_ppHead = nullptr;
 
    HTMLAttr *pAttr = this;
    while( pAttr->GetPrev() )
        pAttr = pAttr->GetPrev();
 
    pAttr->m_pPrev = pPrv;
}
 
bool SwHTMLParser::ParseMetaOptions(
        const uno::Reference<document::XDocumentProperties> & i_xDocProps,
        SvKeyValueIterator *i_pHeader )
{
    // always call base ParseMetaOptions, it sets the encoding (#i96700#)
    bool ret( HTMLParser::ParseMetaOptions(i_xDocProps, i_pHeader) );
    if (!ret && IsNewDoc())
    {
        ParseMoreMetaOptions();
    }
    return ret;
}
 
// override so we can parse DOCINFO field subtypes INFO[1-4]
void SwHTMLParser::AddMetaUserDefined( OUString const & i_rMetaName )
{
    // unless we already have 4 names, append the argument to m_InfoNames
    OUString* pName // the first empty string in m_InfoNames
         (m_InfoNames[0].isEmpty() ? &m_InfoNames[0] :
         (m_InfoNames[1].isEmpty() ? &m_InfoNames[1] :
         (m_InfoNames[2].isEmpty() ? &m_InfoNames[2] :
         (m_InfoNames[3].isEmpty() ? &m_InfoNames[3] : nullptr ))));
    if (pName)
    {
        (*pName) = i_rMetaName;
    }
}
 
void HTMLReader::SetupFilterOptions()
{
    // Reset state from previous Read() invocation.
    m_aNamespace.clear();
 
    if (!m_pMedium)
        return;
 
    auto pItem = m_pMedium->GetItemSet().GetItem(SID_FILE_FILTEROPTIONS);
    if (!pItem)
        return;
 
    OUString aFilterOptions = pItem->GetValue();
    static constexpr OUString aXhtmlNsKey(u"xhtmlns="_ustr);
    if (aFilterOptions.startsWith(aXhtmlNsKey))
    {
        OUString aNamespace = aFilterOptions.copy(aXhtmlNsKey.getLength());
        m_aNamespace = aNamespace;
    }
}
 
namespace
{
    class FontCacheGuard
    {
    public:
        ~FontCacheGuard()
        {
            FlushFontCache();
        }
    };
}
 
bool TestImportHTML(SvStream &rStream)
{
    FontCacheGuard aFontCacheGuard;
    HTMLReader aReader;
    aReader.m_pStream = &rStream;
 
    SwGlobals::ensure();
 
    SfxObjectShellLock xDocSh(new SwDocShell(SfxObjectCreateMode::INTERNAL));
    xDocSh->DoInitNew();
    SwDoc *pD =  static_cast<SwDocShell*>((&xDocSh))->GetDoc();
 
    SwPaM aPaM(pD->GetNodes().GetEndOfContent(), SwNodeOffset(-1));
    pD->SetInReading(true);
    bool bRet = false;
    try
    {
        bRet = aReader.Read(*pD, OUString(), aPaM, OUString()) == ERRCODE_NONE;
    }
    catch (const std::runtime_error&)
    {
    }
    catch (const std::out_of_range&)
    {
    }
    pD->SetInReading(false);
 
    return bRet;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

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

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

V547 Expression '!bBeforeTable' is always false.

V560 A part of conditional expression is always true: nSize >= 1.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.

V1037 Two or more case-branches perform the same actions. Check lines: 1939, 2007

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