/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <memory>
#include <hintids.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/string.hxx>
#include <comphelper/documentinfo.hxx>
#include <vcl/svapp.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <svl/stritem.hxx>
#include <svl/urihelper.hxx>
#include <svl/languageoptions.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/langitem.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/event.hxx>
#include <sfx2/lokhelper.hxx>
#include <vcl/imap.hxx>
#include <svtools/htmltokn.h>
#include <svtools/htmlkywd.hxx>
#include <unotools/eventcfg.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <o3tl/string_view.hxx>

#include <fmtornt.hxx>
#include <fmturl.hxx>
#include <fmtsrnd.hxx>
#include <fmtinfmt.hxx>
#include <fmtcntnt.hxx>
#include <fmtanchr.hxx>
#include <fmtfsize.hxx>
#include <charatr.hxx>
#include <frmfmt.hxx>
#include <charfmt.hxx>
#include <docsh.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <ndtxt.hxx>
#include <shellio.hxx>
#include <poolfmt.hxx>
#include <IMark.hxx>
#include <ndgrf.hxx>
#include "htmlnum.hxx"
#include "swcss1.hxx"
#include "swhtml.hxx"
#include <numrule.hxx>
#include <IDocumentMarkAccess.hxx>
#include <frameformats.hxx>

#include <vcl/graphicfilter.hxx>
#include <tools/UnitConversion.hxx>
#include <tools/hostfilter.hxx>
#include <tools/urlobj.hxx>
#include <unotools/securityoptions.hxx>
#include <unotxdoc.hxx>

using namespace ::com::sun::star;

HTMLOptionEnum<sal_Int16> const aHTMLImgHAlignTable[] =
{
    { OOO_STRING_SVTOOLS_HTML_AL_left,    text::HoriOrientation::LEFT       },
    { OOO_STRING_SVTOOLS_HTML_AL_right,   text::HoriOrientation::RIGHT      },
    { nullptr,                            0               }
};

HTMLOptionEnum<sal_Int16> const aHTMLImgVAlignTable[] =
{
    { OOO_STRING_SVTOOLS_HTML_VA_top,         text::VertOrientation::LINE_TOP       },
    { OOO_STRING_SVTOOLS_HTML_VA_texttop,     text::VertOrientation::CHAR_TOP       },
    { OOO_STRING_SVTOOLS_HTML_VA_middle,      text::VertOrientation::CENTER         },
    { OOO_STRING_SVTOOLS_HTML_AL_center,      text::VertOrientation::CENTER         },
    { OOO_STRING_SVTOOLS_HTML_VA_absmiddle,   text::VertOrientation::LINE_CENTER    },
    { OOO_STRING_SVTOOLS_HTML_VA_bottom,      text::VertOrientation::TOP            },
    { OOO_STRING_SVTOOLS_HTML_VA_baseline,    text::VertOrientation::TOP            },
    { OOO_STRING_SVTOOLS_HTML_VA_absbottom,   text::VertOrientation::LINE_BOTTOM    },
    { nullptr,                                0                   }
};

ImageMap *SwHTMLParser::FindImageMap( std::u16string_view rName ) const
{
    OSL_ENSURE( rName[0] != '#', "FindImageMap: name begins with '#'!" );

    if (m_pImageMaps)
    {
        for (const auto &rpIMap : *m_pImageMaps)
        {
            if (o3tl::equalsIgnoreAsciiCase(rName, rpIMap->GetName()))
            {
                return rpIMap.get();
            }
        }
    }
    return nullptr;
}

void SwHTMLParser::ConnectImageMaps()
{
    SwNodes& rNds = m_xDoc->GetNodes();
    // on the first node of section #1
    SwNodeOffset nIdx = rNds.GetEndOfAutotext().StartOfSectionIndex() + 1;
    SwNodeOffset nEndIdx = rNds.GetEndOfAutotext().GetIndex();

    SwGrfNode* pGrfNd;
    while( m_nMissingImgMaps > 0 && nIdx < nEndIdx )
    {
        SwNode *pNd = rNds[nIdx + 1];
        pGrfNd = pNd->GetGrfNode();
        if( nullptr != pGrfNd )
        {
            SwFrameFormat *pFormat = pGrfNd->GetFlyFormat();
            SwFormatURL aURL( pFormat->GetURL() );
            const ImageMap *pIMap = aURL.GetMap();
            if( pIMap && pIMap->GetIMapObjectCount()==0 )
            {
                // The (empty) image map of the node will be either
                // replaced with found image map or deleted.
                ImageMap *pNewIMap =
                    FindImageMap( pIMap->GetName() );
                aURL.SetMap( pNewIMap );
                pFormat->SetFormatAttr( aURL );
                if( !pGrfNd->IsScaleImageMap() )
                {
                    // meanwhile the graphic size is known or the
                    // graphic don't need scaling
                    pGrfNd->ScaleImageMap();
                }
                m_nMissingImgMaps--;  // search a map less
            }
        }
        nIdx = rNds[nIdx]->EndOfSectionIndex() + 1;
    }
}

void SwHTMLParser::SetAnchorAndAdjustment( sal_Int16 eVertOri,
                                           sal_Int16 eHoriOri,
                                           const SvxCSS1PropertyInfo &rCSS1PropInfo,
                                           SfxItemSet& rFrameItemSet )
{
    const SfxItemSet *pCntnrItemSet = nullptr;
    auto i = m_aContexts.size();
    while( !pCntnrItemSet && i > m_nContextStMin )
        pCntnrItemSet = m_aContexts[--i]->GetFrameItemSet();

    if( pCntnrItemSet )
    {
        // If we are in a container then the anchoring of the container is used.
        rFrameItemSet.Put( *pCntnrItemSet );
    }
    else if( SwCSS1Parser::MayBePositioned( rCSS1PropInfo, true ) )
    {
        // If the alignment can be set via CSS1 options we use them.
        SetAnchorAndAdjustment( rCSS1PropInfo, rFrameItemSet );
    }
    else
    {
        // Otherwise the alignment is set correspondingly the normal HTML options.
        SetAnchorAndAdjustment( eVertOri, eHoriOri, rFrameItemSet );
    }
}

void SwHTMLParser::SetAnchorAndAdjustment( sal_Int16 eVertOri,
                                           sal_Int16 eHoriOri,
                                           SfxItemSet& rFrameSet,
                                           bool bDontAppend )
{
    bool bMoveBackward = false;
    SwFormatAnchor aAnchor( RndStdIds::FLY_AS_CHAR );
    sal_Int16 eVertRel = text::RelOrientation::FRAME;

    if( text::HoriOrientation::NONE != eHoriOri )
    {
        // determine paragraph indent
        sal_uInt16 nLeftSpace = 0, nRightSpace = 0;
        short nIndent = 0;
        GetMarginsFromContextWithNumberBullet( nLeftSpace, nRightSpace, nIndent );

        // determine horizontal alignment and wrapping
        sal_Int16 eHoriRel;
        css::text::WrapTextMode eSurround;
        switch( eHoriOri )
        {
        case text::HoriOrientation::LEFT:
            eHoriRel = nLeftSpace ? text::RelOrientation::PRINT_AREA : text::RelOrientation::FRAME;
            eSurround = css::text::WrapTextMode_RIGHT;
            break;
        case text::HoriOrientation::RIGHT:
            eHoriRel = nRightSpace ? text::RelOrientation::PRINT_AREA : text::RelOrientation::FRAME;
            eSurround = css::text::WrapTextMode_LEFT;
            break;
        case text::HoriOrientation::CENTER:   // for tables
            eHoriRel = text::RelOrientation::FRAME;
            eSurround = css::text::WrapTextMode_NONE;
            break;
        default:
            eHoriRel = text::RelOrientation::FRAME;
            eSurround = css::text::WrapTextMode_PARALLEL;
            break;
        }

        // Create a new paragraph, if the current one has frames
        // anchored at paragraph/at char without wrapping.
        if( !bDontAppend && HasCurrentParaFlys( true ) )
        {
            // When the paragraph only contains graphics then there
            // is no need for bottom margin. Since here also with use of
            // styles no margin should be created, set attributes to
            // override!
            sal_uInt16 nUpper=0, nLower=0;
            GetULSpaceFromContext( nUpper, nLower );
            InsertAttr( SvxULSpaceItem( nUpper, 0, RES_UL_SPACE ), true );

            AppendTextNode( AM_NOSPACE );

            if( nUpper )
            {
                NewAttr(m_xAttrTab, &m_xAttrTab->pULSpace, SvxULSpaceItem(0, nLower, RES_UL_SPACE));
                m_aParaAttrs.push_back( m_xAttrTab->pULSpace );
                EndAttr( m_xAttrTab->pULSpace, false );
            }
        }

        // determine vertical alignment and anchoring
        const sal_Int32 nContent = m_pPam->GetPoint()->GetContentIndex();
        if( nContent )
        {
            aAnchor.SetType( RndStdIds::FLY_AT_CHAR );
            bMoveBackward = true;
            eVertOri = text::VertOrientation::CHAR_BOTTOM;
            eVertRel = text::RelOrientation::CHAR;
        }
        else
        {
            aAnchor.SetType( RndStdIds::FLY_AT_PARA );
            eVertOri = text::VertOrientation::TOP;
            eVertRel = text::RelOrientation::PRINT_AREA;
        }

        rFrameSet.Put( SwFormatHoriOrient( 0, eHoriOri, eHoriRel) );

        rFrameSet.Put( SwFormatSurround( eSurround ) );
    }
    rFrameSet.Put( SwFormatVertOrient( 0, eVertOri, eVertRel) );

    if( bMoveBackward )
        m_pPam->Move( fnMoveBackward );

    if (aAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR && !m_pPam->GetPointNode().GetTextNode())
    {
        eState = SvParserState::Error;
        return;
    }

    aAnchor.SetAnchor( m_pPam->GetPoint() );

    if( bMoveBackward )
        m_pPam->Move( fnMoveForward );

    rFrameSet.Put( aAnchor );
}

void SwHTMLParser::RegisterFlyFrame( SwFrameFormat *pFlyFormat )
{
    // automatically anchored frames must be moved forward by one position
    if( RES_DRAWFRMFMT != pFlyFormat->Which() &&
        (RndStdIds::FLY_AT_PARA == pFlyFormat->GetAnchor().GetAnchorId()) &&
        css::text::WrapTextMode_THROUGH == pFlyFormat->GetSurround().GetSurround() )
    {
        m_aMoveFlyFrames.emplace_back(std::make_unique<SwHTMLFrameFormatListener>(pFlyFormat));
        m_aMoveFlyCnts.push_back( m_pPam->GetPoint()->GetContentIndex() );
    }
}

/*  */

void SwHTMLParser::GetDefaultScriptType( ScriptType& rType,
                                         OUString& rTypeStr ) const
{
    SwDocShell *pDocSh = m_xDoc->GetDocShell();
    SvKeyValueIterator* pHeaderAttrs = pDocSh ? pDocSh->GetHeaderAttributes()
                                              : nullptr;
    rType = GetScriptType( pHeaderAttrs );
    rTypeStr = GetScriptTypeString( pHeaderAttrs );
}

namespace
{
    bool allowAccessLink(const SwDoc& rDoc)
    {
        OUString sReferer;
        SfxObjectShell * sh = rDoc.GetPersist();
        if (sh != nullptr && sh->HasName())
        {
            sReferer = sh->GetMedium()->GetName();
        }
        return !SvtSecurityOptions::isUntrustedReferer(sReferer);
    }
}

/*  */

void SwHTMLParser::InsertImage()
{
    // and now analyze
    OUString sAltNm, aId, aClass, aStyle, aMap, sHTMLGrfName;
    OUString sGrfNm;
    OUString aGraphicData;
    sal_Int16 eVertOri = text::VertOrientation::TOP;
    sal_Int16 eHoriOri = text::HoriOrientation::NONE;
    bool bWidthProvided=false, bHeightProvided=false;
    tools::Long nWidth=0, nHeight=0;
    tools::Long nVSpace=0, nHSpace=0;

    sal_uInt16 nBorder = (m_xAttrTab->pINetFormat ? 1 : 0);
    bool bIsMap = false;
    bool bPercentWidth = false;
    bool bPercentHeight = false;
    OUString sWidthAsString, sHeightAsString;
    SvxMacroItem aMacroItem(RES_FRMMACRO);

    ScriptType eDfltScriptType;
    OUString sDfltScriptType;
    GetDefaultScriptType( eDfltScriptType, sDfltScriptType );

    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        SvMacroItemId nEvent = SvMacroItemId::NONE;
        ScriptType eScriptType2 = eDfltScriptType;
        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::SRC:
                sGrfNm = rOption.GetString();
                if( !InternalImgToPrivateURL(sGrfNm) )
                    sGrfNm = INetURLObject::GetAbsURL( m_sBaseURL, sGrfNm );
                break;
            case HtmlOptionId::DATA:
                aGraphicData = rOption.GetString();
                if (!InternalImgToPrivateURL(aGraphicData))
                    aGraphicData = INetURLObject::GetAbsURL(
                        m_sBaseURL, SwHTMLParser::StripQueryFromPath(m_sBaseURL, aGraphicData));
                break;
            case HtmlOptionId::ALIGN:
                eVertOri =
                    rOption.GetEnum( aHTMLImgVAlignTable,
                                                    text::VertOrientation::TOP );
                eHoriOri =
                    rOption.GetEnum( aHTMLImgHAlignTable );
                break;
            case HtmlOptionId::WIDTH:
                // for now only store as pixel value!
                nWidth = rOption.GetNumber();
                sWidthAsString = rOption.GetString();
                bPercentWidth = (sWidthAsString.indexOf('%') != -1);
                if( bPercentWidth && nWidth>100 )
                    nWidth = 100;
                // width|height = "auto" means viewing app decides the size
                // i.e. proceed as if no particular size was provided
                bWidthProvided = (sWidthAsString != "auto");
                break;
            case HtmlOptionId::HEIGHT:
                // for now only store as pixel value!
                nHeight = rOption.GetNumber();
                sHeightAsString = rOption.GetString();
                bPercentHeight = (sHeightAsString.indexOf('%') != -1);
                if( bPercentHeight && nHeight>100 )
                    nHeight = 100;
                // the same as above w/ HtmlOptionId::WIDTH
                bHeightProvided = (sHeightAsString != "auto");
                break;
            case HtmlOptionId::VSPACE:
                nVSpace = rOption.GetNumber();
                break;
            case HtmlOptionId::HSPACE:
                nHSpace = rOption.GetNumber();
                break;
            case HtmlOptionId::ALT:
                sAltNm = rOption.GetString();
                break;
            case HtmlOptionId::BORDER:
                nBorder = o3tl::narrowing<sal_uInt16>(rOption.GetNumber());
                break;
            case HtmlOptionId::ISMAP:
                bIsMap = true;
                break;
            case HtmlOptionId::USEMAP:
                aMap = rOption.GetString();
                break;
            case HtmlOptionId::NAME:
                sHTMLGrfName = rOption.GetString();
                break;

            case HtmlOptionId::SDONLOAD:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONLOAD:
                nEvent = SvMacroItemId::OnImageLoadDone;
                goto IMAGE_SETEVENT;

            case HtmlOptionId::SDONABORT:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONABORT:
                nEvent = SvMacroItemId::OnImageLoadCancel;
                goto IMAGE_SETEVENT;

            case HtmlOptionId::SDONERROR:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONERROR:
                nEvent = SvMacroItemId::OnImageLoadError;
                goto IMAGE_SETEVENT;
IMAGE_SETEVENT:
                {
                    OUString sTmp( rOption.GetString() );
                    if( !sTmp.isEmpty() )
                    {
                        sTmp = convertLineEnd(sTmp, GetSystemLineEnd());
                        OUString sScriptType;
                        if( EXTENDED_STYPE == eScriptType2 )
                            sScriptType = sDfltScriptType;
                        aMacroItem.SetMacro( nEvent,
                            SvxMacro( sTmp, sScriptType, eScriptType2 ));
                    }
                }
                break;
            default: break;
        }
    }

    if (sGrfNm.isEmpty() && !aGraphicData.isEmpty())
        sGrfNm = aGraphicData;

    if( sGrfNm.isEmpty() )
        return;

    // When we are in an ordered list and the paragraph is still empty and not
    // numbered, it may be a graphic for a bullet list.
    if( !m_pPam->GetPoint()->GetContentIndex() &&
        GetNumInfo().GetDepth() > 0 && GetNumInfo().GetDepth() <= MAXLEVEL &&
        !m_aBulletGrfs[GetNumInfo().GetDepth()-1].isEmpty() &&
        m_aBulletGrfs[GetNumInfo().GetDepth()-1]==sGrfNm )
    {
        SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();

        if( pTextNode && ! pTextNode->IsCountedInList())
        {
            OSL_ENSURE( pTextNode->GetActualListLevel() == GetNumInfo().GetLevel(),
                    "Numbering level is wrong" );

            pTextNode->SetCountedInList( true );

            // It's necessary to invalidate the rule, because between the reading
            // of LI and the graphic an EndAction could be called.
            if( GetNumInfo().GetNumRule() )
                GetNumInfo().GetNumRule()->Invalidate();

            // Set the style again, so that indent of the first line is correct.
            SetTextCollAttrs();

            return;
        }
    }

    Graphic aGraphic;
    INetURLObject aGraphicURL( sGrfNm );
    if( aGraphicURL.GetProtocol() == INetProtocol::Data )
    {
        std::unique_ptr<SvMemoryStream> const pStream(aGraphicURL.getData());
        if (pStream)
        {
            GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
            aGraphic = rFilter.ImportUnloadedGraphic(*pStream);
            sGrfNm.clear();

            if (!sGrfNm.isEmpty())
            {
                if (ERRCODE_NONE == rFilter.ImportGraphic(aGraphic, u"", *pStream))
                    sGrfNm.clear();
            }
        }
    }
    else if (m_sBaseURL.isEmpty() || !aGraphicData.isEmpty())
    {
        if (comphelper::LibreOfficeKit::isActive() && HostFilter::isForbidden(aGraphicURL.GetHost()))
            SfxLokHelper::sendNetworkAccessError("paste");

        // sBaseURL is empty if the source is clipboard
        // aGraphicData is non-empty for <object data="..."> -> not a linked graphic.
        if (ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aGraphicURL))
            sGrfNm.clear();
    }

    if (!sGrfNm.isEmpty())
    {
        aGraphic.SetDefaultType();
    }

    if (!nHeight || !nWidth)
    {
        Size aPixelSize = aGraphic.GetSizePixel(Application::GetDefaultDevice());
        if (!bWidthProvided)
            nWidth = aPixelSize.Width();
        if (!bHeightProvided)
            nHeight = aPixelSize.Height();
        // tdf#142781 - calculate the width/height keeping the aspect ratio
        if (bWidthProvided && !bHeightProvided && aPixelSize.Width())
        {
            if (bPercentWidth)
            {
                nHeight = SwFormatFrameSize::SYNCED;
                bPercentHeight = true;
            }
            else
            {
                nHeight = nWidth * aPixelSize.Height() / aPixelSize.Width();
            }
        }
        else if (!bWidthProvided && bHeightProvided && aPixelSize.Height())
        {
            if (bPercentHeight)
            {
                nWidth = SwFormatFrameSize::SYNCED;
                bPercentWidth = true;
            }
            else
            {
                nWidth = nHeight * aPixelSize.Width() / aPixelSize.Height();
            }
        }
    }

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

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

    // set the border
    tools::Long nHBorderWidth = 0, nVBorderWidth = 0;
    if( nBorder )
    {
        nHBorderWidth = static_cast<tools::Long>(nBorder);
        nVBorderWidth = static_cast<tools::Long>(nBorder);
        SvxCSS1Parser::PixelToTwip( nVBorderWidth, nHBorderWidth );

        ::editeng::SvxBorderLine aHBorderLine( nullptr, nHBorderWidth );
        ::editeng::SvxBorderLine aVBorderLine( nullptr, nVBorderWidth );

        if( m_xAttrTab->pINetFormat )
        {
            const OUString& rURL =
                static_cast<const SwFormatINetFormat&>(m_xAttrTab->pINetFormat->GetItem()).GetValue();

            m_pCSS1Parser->SetATagStyles();
            sal_uInt16 nPoolId =  static_cast< sal_uInt16 >(m_xDoc->IsVisitedURL( rURL )
                                    ? RES_POOLCHR_INET_VISIT
                                    : RES_POOLCHR_INET_NORMAL);
            const SwCharFormat *pCharFormat = m_pCSS1Parser->GetCharFormatFromPool( nPoolId );
            aHBorderLine.SetColor( pCharFormat->GetColor().GetValue() );
            aVBorderLine.SetColor( aHBorderLine.GetColor() );
        }
        else
        {
            const SvxColorItem& rColorItem = m_xAttrTab->pFontColor ?
              static_cast<const SvxColorItem &>(m_xAttrTab->pFontColor->GetItem()) :
              m_xDoc->GetDefault(RES_CHRATR_COLOR);
            aHBorderLine.SetColor( rColorItem.GetValue() );
            aVBorderLine.SetColor( aHBorderLine.GetColor() );
        }

        SvxBoxItem aBoxItem( RES_BOX );
        aBoxItem.SetLine( &aHBorderLine, SvxBoxItemLine::TOP );
        aBoxItem.SetLine( &aHBorderLine, SvxBoxItemLine::BOTTOM );
        aBoxItem.SetLine( &aVBorderLine, SvxBoxItemLine::LEFT );
        aBoxItem.SetLine( &aVBorderLine, SvxBoxItemLine::RIGHT );
        aFrameSet.Put( aBoxItem );
    }

    SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, aFrameSet );

    SetSpace( Size( nHSpace, nVSpace), aItemSet, aPropInfo, aFrameSet );

    // set other CSS1 attributes
    SetFrameFormatAttrs( aItemSet, HtmlFrameFormatFlags::Box, aFrameSet );

    Size aTwipSz( bPercentWidth ? 0 : nWidth, bPercentHeight ? 0 : nHeight );
    if( aTwipSz.Width() || aTwipSz.Height() )
    {
        if (bWidthProvided || bHeightProvided || // attributes imply pixel!
            aGraphic.GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel)
        {
            aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip);
        }
        else
        {   // some bitmaps may have a size in metric units (e.g. PNG); use that
            assert(aGraphic.GetPrefMapMode().GetMapUnit() < MapUnit::MapPixel);
            aTwipSz = o3tl::convert(aGraphic.GetPrefSize(),
                                    MapToO3tlLength(aGraphic.GetPrefMapMode().GetMapUnit()),
                                    o3tl::Length::twip);
        }
    }

    // convert CSS1 size to "normal" size
    switch( aPropInfo.m_eWidthType )
    {
        case SVX_CSS1_LTYPE_TWIP:
            aTwipSz.setWidth( aPropInfo.m_nWidth );
            nWidth = 1; // != 0
            bPercentWidth = false;
            break;
        case SVX_CSS1_LTYPE_PERCENTAGE:
            aTwipSz.setWidth( 0 );
            nWidth = aPropInfo.m_nWidth;
            bPercentWidth = true;
            break;
        default:
            ;
    }
    switch( aPropInfo.m_eHeightType )
    {
        case SVX_CSS1_LTYPE_TWIP:
            aTwipSz.setHeight( aPropInfo.m_nHeight );
            nHeight = 1;    // != 0
            bPercentHeight = false;
            break;
        case SVX_CSS1_LTYPE_PERCENTAGE:
            aTwipSz.setHeight( 0 );
            nHeight = aPropInfo.m_nHeight;
            bPercentHeight = true;
            break;
        default:
            ;
    }

    Size aGrfSz( 0, 0 );
    bool bSetTwipSize = true;       // Set Twip-Size on Node?
    bool bChangeFrameSize = false;    // Change frame format later?
    bool bRequestGrfNow = false;
    bool bSetScaleImageMap = false;
    sal_uInt8 nPercentWidth = 0, nPercentHeight = 0;

    // bPercentWidth / bPercentHeight means we have a percent size.  If that's not the case and we have no
    // size from nWidth / nHeight either, then inspect the image header.
    bool bRelWidthScale = bPercentWidth && nWidth == SwFormatFrameSize::SYNCED;
    bool bNeedWidth = (!bPercentWidth && !nWidth) || bRelWidthScale;
    bool bRelHeightScale = bPercentHeight && nHeight == SwFormatFrameSize::SYNCED;
    bool bNeedHeight = (!bPercentHeight && !nHeight) || bRelHeightScale;
    if ((bNeedWidth || bNeedHeight) && !bFuzzing && allowAccessLink(*m_xDoc) &&
        !aGraphicURL.IsExoticProtocol())
    {
        GraphicDescriptor aDescriptor(aGraphicURL);
        if (aDescriptor.Detect(/*bExtendedInfo=*/true))
        {
            // Try to use size info from the image header before defaulting to
            // HTML_DFLT_IMG_WIDTH/HEIGHT.
            aTwipSz
                = o3tl::convert(aDescriptor.GetSizePixel(), o3tl::Length::px, o3tl::Length::twip);
            if (!bPercentWidth && !nWidth)
            {
                nWidth = aTwipSz.getWidth();
            }
            if (!bPercentHeight && !nHeight)
            {
                nHeight = aTwipSz.getHeight();
            }
        }
    }

    if( !(nWidth && !bRelWidthScale) || !(nHeight && !bRelHeightScale) )
    {
        // When the graphic is in a table, it will be requested immediately,
        // so that it is available before the table is layouted.
        if (m_xTable && !nWidth)
        {
            bRequestGrfNow = true;
            IncGrfsThatResizeTable();
        }

        // The frame size is set later
        bChangeFrameSize = true;
        aGrfSz = aTwipSz;
        if( !nWidth && !nHeight )
        {
            aTwipSz.setWidth( HTML_DFLT_IMG_WIDTH );
            aTwipSz.setHeight( HTML_DFLT_IMG_HEIGHT );
        }
        else if( nWidth )
        {
            // a percentage value
            if( bPercentWidth )
            {
                nPercentWidth = static_cast<sal_uInt8>(nWidth);
                nPercentHeight = 255;
            }
            else
            {
                aTwipSz.setHeight( HTML_DFLT_IMG_HEIGHT );
            }
        }
        else if( nHeight )
        {
            if( bPercentHeight )
            {
                nPercentHeight = static_cast<sal_uInt8>(nHeight);
                nPercentWidth = 255;
            }
            else
            {
                aTwipSz.setWidth( HTML_DFLT_IMG_WIDTH );
            }
        }
    }
    else
    {
        // Width and height were given and don't need to be set
        bSetTwipSize = false;

        if( bPercentWidth )
            nPercentWidth = static_cast<sal_uInt8>(nWidth);

        if( bPercentHeight )
            nPercentHeight = static_cast<sal_uInt8>(nHeight);
    }

    // set image map
    aMap = comphelper::string::stripEnd(aMap, ' ');
    if( !aMap.isEmpty() )
    {
        // Since we only know local image maps we just use everything
        // after # as name
        sal_Int32 nPos = aMap.indexOf( '#' );
        OUString aName;
        if ( -1 == nPos )
            aName = aMap ;
        else
            aName = aMap.copy(nPos+1);

        ImageMap *pImgMap = FindImageMap( aName );
        if( pImgMap )
        {
            SwFormatURL aURL; aURL.SetMap( pImgMap );// is copied

            bSetScaleImageMap = !nPercentWidth || !nPercentHeight;
            aFrameSet.Put( aURL );
        }
        else
        {
            ImageMap aEmptyImgMap( aName );
            SwFormatURL aURL; aURL.SetMap( &aEmptyImgMap );// is copied
            aFrameSet.Put( aURL );
            m_nMissingImgMaps++;          // image maps are missing

            // the graphic has to scaled during SetTwipSize, if we didn't
            // set a size on the node or the size doesn't match the graphic size.
            bSetScaleImageMap = true;
        }
    }

    // observe minimum values !!
    bool bRelSizeScale = bRelWidthScale || bRelHeightScale;
    if( nPercentWidth )
    {
        OSL_ENSURE( !aTwipSz.Width() || bRelSizeScale,
                "Why is a width set if we already have percentage value?" );
        aTwipSz.setWidth( aGrfSz.Width() ? aGrfSz.Width()
                                         : HTML_DFLT_IMG_WIDTH );
    }
    else
    {
        aTwipSz.AdjustWidth(2*nVBorderWidth );
        if( aTwipSz.Width() < MINFLY )
            aTwipSz.setWidth( MINFLY );
    }
    if( nPercentHeight )
    {
        OSL_ENSURE( !aTwipSz.Height() || bRelSizeScale,
                "Why is a height set if we already have percentage value?" );
        aTwipSz.setHeight( aGrfSz.Height() ? aGrfSz.Height()
                                           : HTML_DFLT_IMG_HEIGHT );
    }
    else
    {
        aTwipSz.AdjustHeight(2*nHBorderWidth );
        if( aTwipSz.Height() < MINFLY )
            aTwipSz.setHeight( MINFLY );
    }

    SwFormatFrameSize aFrameSize( SwFrameSize::Fixed, aTwipSz.Width(), aTwipSz.Height() );
    aFrameSize.SetWidthPercent( nPercentWidth );
    aFrameSize.SetHeightPercent( nPercentHeight );
    aFrameSet.Put( aFrameSize );

    const SwNodeType eNodeType = m_pPam->GetPointNode().GetNodeType();
    if (eNodeType != SwNodeType::Text && eNodeType != SwNodeType::Table)
        return;

    SanitizeAnchor(aFrameSet);

    // passing empty sGrfNm here, means we don't want the graphic to be linked
    SwFrameFormat *const pFlyFormat =
        m_xDoc->getIDocumentContentOperations().InsertGraphic(
            *m_pPam, sGrfNm, OUString(), &aGraphic,
            &aFrameSet, nullptr, nullptr);
    SwGrfNode *pGrfNd = m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx()
                                  ->GetIndex()+1 ]->GetGrfNode();

    if( !sHTMLGrfName.isEmpty() )
    {
        pFlyFormat->SetFormatName( UIName(sHTMLGrfName) );

        // maybe jump to graphic
        if( JumpToMarks::Graphic == m_eJumpTo && sHTMLGrfName == m_sJmpMark )
        {
            m_bChkJumpMark = true;
            m_eJumpTo = JumpToMarks::NONE;
        }
    }

    if (pGrfNd)
    {
        if( !sAltNm.isEmpty() )
            pGrfNd->SetTitle( sAltNm );

        if( bSetTwipSize )
            pGrfNd->SetTwipSize( aGrfSz );

        pGrfNd->SetChgTwipSize( bChangeFrameSize );

        if( bSetScaleImageMap )
            pGrfNd->SetScaleImageMap( true );
    }

    if( m_xAttrTab->pINetFormat )
    {
        const SwFormatINetFormat &rINetFormat =
            static_cast<const SwFormatINetFormat&>(m_xAttrTab->pINetFormat->GetItem());

        SwFormatURL aURL( pFlyFormat->GetURL() );

        aURL.SetURL( rINetFormat.GetValue(), bIsMap );
        aURL.SetTargetFrameName( rINetFormat.GetTargetFrame() );
        aURL.SetName( rINetFormat.GetName() );
        pFlyFormat->SetFormatAttr( aURL );

        {
            static const SvMacroItemId aEvents[] = {
                SvMacroItemId::OnMouseOver,
                SvMacroItemId::OnClick,
                SvMacroItemId::OnMouseOut };

            for( SvMacroItemId id : aEvents )
            {
                const SvxMacro *pMacro = rINetFormat.GetMacro( id );
                if( nullptr != pMacro )
                    aMacroItem.SetMacro( id, *pMacro );
            }
        }

        if ((RndStdIds::FLY_AS_CHAR == pFlyFormat->GetAnchor().GetAnchorId()) &&
            m_xAttrTab->pINetFormat->GetStartParagraph() ==
                        m_pPam->GetPoint()->GetNode() &&
            m_xAttrTab->pINetFormat->GetStartContent() ==
                        m_pPam->GetPoint()->GetContentIndex() - 1 )
        {
            // the attribute was insert right before as-character anchored
            // graphic, therefore we move it
            m_xAttrTab->pINetFormat->SetStart( *m_pPam->GetPoint() );

            // When the attribute is also an anchor, we'll insert
            // a bookmark before the graphic, because SwFormatURL
            // isn't an anchor.
            if( !rINetFormat.GetName().isEmpty() )
            {
                m_pPam->Move( fnMoveBackward );
                InsertBookmark( rINetFormat.GetName() );
                m_pPam->Move( fnMoveForward );
            }
        }

    }
    else if (!m_aEmbedURL.isEmpty())
    {
        // This is an inner <object> image and the outer <object> has a URL for us. Set that on the
        // image.
        SwFormatURL aURL(pFlyFormat->GetURL());
        aURL.SetURL(m_aEmbedURL, bIsMap);
        m_aEmbedURL.clear();
        pFlyFormat->SetFormatAttr(aURL);
    }

    if( !aMacroItem.GetMacroTable().empty() )
    {
        NotifyMacroEventRead();
        pFlyFormat->SetFormatAttr( aMacroItem );
    }

    // tdf#87083 If the graphic has not been loaded yet, then load it now.
    // Otherwise it may be loaded during the first paint of the object and it
    // will be too late to adapt the size of the graphic at that point.
    if (bRequestGrfNow && pGrfNd)
    {
        Size aUpdatedSize = pGrfNd->GetTwipSize();  //trigger a swap-in
        SAL_WARN_IF(!aUpdatedSize.Width() || !aUpdatedSize.Height(), "sw.html", "html image with no width or height");
    }

    // maybe create frames and register auto bound frames
    RegisterFlyFrame( pFlyFormat );

    if( !aId.isEmpty() )
        InsertBookmark( aId );
}

/*  */

void SwHTMLParser::InsertBodyOptions()
{
    m_xDoc->SetTextFormatColl( *m_pPam,
                         m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_TEXT ) );

    OUString aBackGround, aId, aStyle, aLang, aDir;
    Color aBGColor, aTextColor, aLinkColor, aVLinkColor;
    bool bBGColor=false, bTextColor=false;
    bool bLinkColor=false, bVLinkColor=false;

    ScriptType eDfltScriptType;
    OUString sDfltScriptType;
    GetDefaultScriptType( eDfltScriptType, sDfltScriptType );

    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        const HTMLOption& rOption = rHTMLOptions[--i];
        ScriptType eScriptType2 = eDfltScriptType;
        OUString aEvent;
        bool bSetEvent = false;

        switch( rOption.GetToken() )
        {
            case HtmlOptionId::ID:
                aId = rOption.GetString();
                break;
            case HtmlOptionId::BACKGROUND:
                aBackGround = rOption.GetString();
                break;
            case HtmlOptionId::BGCOLOR:
                rOption.GetColor( aBGColor );
                bBGColor = true;
                break;
            case HtmlOptionId::TEXT:
                rOption.GetColor( aTextColor );
                bTextColor = true;
                break;
            case HtmlOptionId::LINK:
                rOption.GetColor( aLinkColor );
                bLinkColor = true;
                break;
            case HtmlOptionId::VLINK:
                rOption.GetColor( aVLinkColor );
                bVLinkColor = true;
                break;

            case HtmlOptionId::SDONLOAD:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONLOAD:
                aEvent = GlobalEventConfig::GetEventName( GlobalEventId::OPENDOC );
                bSetEvent = true;
                break;

            case HtmlOptionId::SDONUNLOAD:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONUNLOAD:
                aEvent = GlobalEventConfig::GetEventName( GlobalEventId::PREPARECLOSEDOC );
                bSetEvent = true;
                break;

            case HtmlOptionId::SDONFOCUS:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONFOCUS:
                aEvent = GlobalEventConfig::GetEventName( GlobalEventId::ACTIVATEDOC );
                bSetEvent = true;
                break;

            case HtmlOptionId::SDONBLUR:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONBLUR:
                aEvent = GlobalEventConfig::GetEventName( GlobalEventId::DEACTIVATEDOC );
                bSetEvent = true;
                break;

            case HtmlOptionId::ONERROR:
                break;

            case HtmlOptionId::STYLE:
                aStyle = rOption.GetString();
                bTextColor = true;
                break;
            case HtmlOptionId::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;
            default: break;
        }

        if( bSetEvent )
        {
            const OUString& rEvent = rOption.GetString();
            if( !rEvent.isEmpty() )
                InsertBasicDocEvent( aEvent, rEvent, eScriptType2,
                                     sDfltScriptType );
        }
    }

    if( bTextColor && !m_pCSS1Parser->IsBodyTextSet() )
    {
        // The font colour is set in the default style
        m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD )
            ->SetFormatAttr( SvxColorItem(aTextColor, RES_CHRATR_COLOR) );
        m_pCSS1Parser->SetBodyTextSet();
    }

    // Prepare the items for the page style (background, frame)
    // If BrushItem already set values must remain!
    std::unique_ptr<SvxBrushItem> aBrushItem( m_pCSS1Parser->makePageDescBackground() );
    bool bSetBrush = false;

    if( bBGColor && !m_pCSS1Parser->IsBodyBGColorSet() )
    {
        // background colour from "BGCOLOR"
        OUString aLink;
        if( !aBrushItem->GetGraphicLink().isEmpty() )
            aLink = aBrushItem->GetGraphicLink();
        SvxGraphicPosition ePos = aBrushItem->GetGraphicPos();

        aBrushItem->SetColor( aBGColor );

        if( !aLink.isEmpty() )
        {
            aBrushItem->SetGraphicLink( aLink );
            aBrushItem->SetGraphicPos( ePos );
        }
        bSetBrush = true;
        m_pCSS1Parser->SetBodyBGColorSet();
    }

    if( !aBackGround.isEmpty() && !m_pCSS1Parser->IsBodyBackgroundSet() )
    {
        // background graphic from "BACKGROUND"
        aBrushItem->SetGraphicLink( INetURLObject::GetAbsURL( m_sBaseURL, aBackGround ) );
        aBrushItem->SetGraphicPos( GPOS_TILED );
        bSetBrush = true;
        m_pCSS1Parser->SetBodyBackgroundSet();
    }

    if( !aStyle.isEmpty() || !aDir.isEmpty() )
    {
        SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() );
        SvxCSS1PropertyInfo aPropInfo;
        OUString aDummy;
        (void)ParseStyleOptions( aStyle, aDummy, aDummy, aItemSet, aPropInfo, nullptr, &aDir );

        // Some attributes have to set on the page style, in fact the ones
        // which aren't inherited
        m_pCSS1Parser->SetPageDescAttrs( bSetBrush ? aBrushItem.get() : nullptr,
                                       &aItemSet );

        static const TypedWhichId<SvxFontHeightItem> aWhichIds[3] = { RES_CHRATR_FONTSIZE,
                                       RES_CHRATR_CJK_FONTSIZE,
                                       RES_CHRATR_CTL_FONTSIZE };
        for(auto const & i : aWhichIds)
        {
            const SvxFontHeightItem *pItem = aItemSet.GetItemIfSet( i, false );
            if( pItem && pItem->GetProp() != 100)
            {
                sal_uInt32 nHeight =
                    ( m_aFontHeights[2] * pItem->GetProp() ) / 100;
                SvxFontHeightItem aNewItem( nHeight, 100, i );
                aItemSet.Put( aNewItem );
            }
        }

        // all remaining options can be set on the default style
        m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD )
            ->SetFormatAttr( aItemSet );
    }
    else if( bSetBrush )
    {
        m_pCSS1Parser->SetPageDescAttrs( aBrushItem.get() );
    }

    if( bLinkColor && !m_pCSS1Parser->IsBodyLinkSet() )
    {
        SwCharFormat *pCharFormat =
            m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_INET_NORMAL);
        pCharFormat->SetFormatAttr( SvxColorItem(aLinkColor, RES_CHRATR_COLOR) );
        m_pCSS1Parser->SetBodyLinkSet();
    }
    if( bVLinkColor && !m_pCSS1Parser->IsBodyVLinkSet() )
    {
        SwCharFormat *pCharFormat =
            m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_INET_VISIT);
        pCharFormat->SetFormatAttr( SvxColorItem(aVLinkColor, RES_CHRATR_COLOR) );
        m_pCSS1Parser->SetBodyVLinkSet();
    }
    if( !aLang.isEmpty() )
    {
        LanguageType eLang = LanguageTag::convertToLanguageTypeWithFallback( aLang );
        if( LANGUAGE_DONTKNOW != eLang )
        {
            TypedWhichId<SvxLanguageItem> nWhich(0);
            switch( SvtLanguageOptions::GetScriptTypeOfLanguage( eLang ) )
            {
            case SvtScriptType::LATIN:
                nWhich = RES_CHRATR_LANGUAGE;
                break;
            case SvtScriptType::ASIAN:
                nWhich = RES_CHRATR_CJK_LANGUAGE;
                break;
            case SvtScriptType::COMPLEX:
                nWhich = RES_CHRATR_CTL_LANGUAGE;
                break;
            default: break;
            }
            if( nWhich )
            {
                SvxLanguageItem aLanguage( eLang, nWhich );
                aLanguage.SetWhich( nWhich );
                m_xDoc->SetDefault( aLanguage );
            }
        }
    }

    if( !aId.isEmpty() )
        InsertBookmark( aId );
}

/*  */

void SwHTMLParser::NewAnchor()
{
    // end previous link if there was one
    std::unique_ptr<HTMLAttrContext> xOldCntxt(PopContext(HtmlTokenId::ANCHOR_ON));
    if (xOldCntxt)
    {
        // and maybe end attributes
        EndContext(xOldCntxt.get());
    }

    SvxMacroTableDtor aMacroTable;
    OUString sHRef, aName, sTarget;
    OUString aId, aStyle, aClass, aLang, aDir;
    bool bHasHRef = false, bFixed = false;

    ScriptType eDfltScriptType;
    OUString sDfltScriptType;
    GetDefaultScriptType( eDfltScriptType, sDfltScriptType );

    const HTMLOptions& rHTMLOptions = GetOptions();
    for (size_t i = rHTMLOptions.size(); i; )
    {
        SvMacroItemId nEvent = SvMacroItemId::NONE;
        ScriptType eScriptType2 = eDfltScriptType;
        const HTMLOption& rOption = rHTMLOptions[--i];
        switch( rOption.GetToken() )
        {
            case HtmlOptionId::NAME:
                aName = rOption.GetString();
                break;

            case HtmlOptionId::HREF:
                sHRef = rOption.GetString();
                bHasHRef = true;
                break;
            case HtmlOptionId::TARGET:
                sTarget = rOption.GetString();
                break;

            case HtmlOptionId::STYLE:
                aStyle = rOption.GetString();
                break;
            case HtmlOptionId::ID:
                aId = rOption.GetString();
                break;
            case HtmlOptionId::CLASS:
                aClass = rOption.GetString();
                break;
            case HtmlOptionId::SDFIXED:
                bFixed = true;
                break;
            case HtmlOptionId::LANG:
                aLang = rOption.GetString();
                break;
            case HtmlOptionId::DIR:
                aDir = rOption.GetString();
                break;

            case HtmlOptionId::SDONCLICK:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONCLICK:
                nEvent = SvMacroItemId::OnClick;
                goto ANCHOR_SETEVENT;

            case HtmlOptionId::SDONMOUSEOVER:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONMOUSEOVER:
                nEvent = SvMacroItemId::OnMouseOver;
                goto ANCHOR_SETEVENT;

            case HtmlOptionId::SDONMOUSEOUT:
                eScriptType2 = STARBASIC;
                [[fallthrough]];
            case HtmlOptionId::ONMOUSEOUT:
                nEvent = SvMacroItemId::OnMouseOut;
                goto ANCHOR_SETEVENT;
ANCHOR_SETEVENT:
                {
                    OUString sTmp( rOption.GetString() );
                    if( !sTmp.isEmpty() )
                    {
                        sTmp = convertLineEnd(sTmp, GetSystemLineEnd());
                        OUString sScriptType;
                        if( EXTENDED_STYPE == eScriptType2 )
                            sScriptType = sDfltScriptType;
                        aMacroTable.Insert( nEvent, SvxMacro( sTmp, sScriptType, eScriptType2 ));
                    }
                }
                break;
            default: break;
        }
    }

    // Jump targets, which match our implicit targets,
    // here we throw out rigorously.
    if( !aName.isEmpty() )
    {
        OUString sDecoded( INetURLObject::decode( aName,
                                           INetURLObject::DecodeMechanism::Unambiguous ));
        sal_Int32 nPos = sDecoded.lastIndexOf( cMarkSeparator );
        if( nPos != -1 )
        {
            OUString sCmp= sDecoded.copy(nPos+1).replaceAll(" ","");
            if( !sCmp.isEmpty() )
            {
                sCmp = sCmp.toAsciiLowerCase();
                if( sCmp == "region" ||
                    sCmp == "frame" ||
                    sCmp == "graphic" ||
                    sCmp == "ole" ||
                    sCmp == "table" ||
                    sCmp == "outline" ||
                    sCmp == "text" )
                {
                    aName.clear();
                }
            }
        }
    }

    // create a new context
    std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::ANCHOR_ON));

    bool bEnAnchor = false, bFootnoteAnchor = false, bFootnoteEnSymbol = false;
    OUString aFootnoteName;
    OUString aStrippedClass( aClass );
    SwCSS1Parser::GetScriptFromClass( aStrippedClass, false );
    if( aStrippedClass.getLength() >=9  && bHasHRef && sHRef.getLength() > 1 &&
        ('s' == aStrippedClass[0] || 'S' == aStrippedClass[0]) &&
        ('d' == aStrippedClass[1] || 'D' == aStrippedClass[1]) )
    {
        if( aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdendnote_anc ) )
            bEnAnchor = true;
        else if( aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdfootnote_anc ) )
            bFootnoteAnchor = true;
        else if( aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdendnote_sym ) ||
                 aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdfootnote_sym ) )
            bFootnoteEnSymbol = true;
        if( bEnAnchor || bFootnoteAnchor || bFootnoteEnSymbol )
        {
            aFootnoteName = sHRef.copy( 1 );
            aClass.clear();
            aStrippedClass.clear();
            aName.clear();
            bHasHRef = false;
        }
    }

    // Styles parsen
    if( HasStyleOptions( aStyle, aId, aStrippedClass, &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(), true);
        }
    }

    if( bHasHRef )
    {
        if( !sHRef.isEmpty() )
        {
            sHRef = URIHelper::SmartRel2Abs( INetURLObject(m_sBaseURL), sHRef, Link<OUString *, bool>(), false );
        }
        else
        {
            // use directory if empty URL
            INetURLObject aURLObj( m_aPathToFile );
            sHRef = aURLObj.GetPartBeforeLastName();
        }

        m_pCSS1Parser->SetATagStyles();
        SwFormatINetFormat aINetFormat( sHRef, sTarget );
        aINetFormat.SetName( aName );

        if( !aMacroTable.empty() )
        {
            NotifyMacroEventRead();
            aINetFormat.SetMacroTable( &aMacroTable );
        }

        // set the default attribute
        InsertAttr(&m_xAttrTab->pINetFormat, aINetFormat, xCntxt.get());
    }
    else if( !aName.isEmpty() )
    {
        InsertBookmark( aName );
    }

    if( bEnAnchor || bFootnoteAnchor )
    {
        InsertFootEndNote( aFootnoteName, bEnAnchor, bFixed );
        m_bInFootEndNoteAnchor = m_bCallNextToken = true;
    }
    else if( bFootnoteEnSymbol )
    {
        m_bInFootEndNoteSymbol = m_bCallNextToken = true;
    }

    // save context
    PushContext(xCntxt);
}

void SwHTMLParser::EndAnchor()
{
    if( m_bInFootEndNoteAnchor )
    {
        FinishFootEndNote();
        m_bInFootEndNoteAnchor = false;
    }
    else if( m_bInFootEndNoteSymbol )
    {
        m_bInFootEndNoteSymbol = false;
    }

    EndTag( HtmlTokenId::ANCHOR_OFF );
}

/*  */

void SwHTMLParser::InsertBookmark( const OUString& rName )
{
    HTMLAttr* pTmp = new HTMLAttr( *m_pPam->GetPoint(),
            SfxStringItem(RES_FLTR_BOOKMARK, rName), nullptr, std::shared_ptr<HTMLAttrTable>());
    m_aSetAttrTab.push_back( pTmp );
}

bool SwHTMLParser::HasCurrentParaBookmarks( bool bIgnoreStack ) const
{
    bool bHasMarks = false;
    SwNodeOffset nNodeIdx = m_pPam->GetPoint()->GetNodeIndex();

    // first step: are there still bookmark in the attribute-stack?
    // bookmarks are added to the end of the stack - thus we only have
    // to check the last bookmark
    if( !bIgnoreStack )
    {
        for( auto i = m_aSetAttrTab.size(); i; )
        {
            HTMLAttr* pAttr = m_aSetAttrTab[ --i ];
            if( RES_FLTR_BOOKMARK == pAttr->m_pItem->Which() )
            {
                if( pAttr->GetStartParagraphIdx() == nNodeIdx )
                    bHasMarks = true;
                break;
            }
        }
    }

    if( !bHasMarks )
    {
        // second step: when we didn't find a bookmark, check if there is one set already
        IDocumentMarkAccess* const pMarkAccess = m_xDoc->getIDocumentMarkAccess();
        for(auto ppMark = pMarkAccess->getAllMarksBegin();
            ppMark != pMarkAccess->getAllMarksEnd();
            ++ppMark)
        {
            const ::sw::mark::MarkBase* pBookmark = *ppMark;

            const SwNodeOffset nBookNdIdx = pBookmark->GetMarkPos().GetNodeIndex();
            if( nBookNdIdx==nNodeIdx )
            {
                bHasMarks = true;
                break;
            }
            else if( nBookNdIdx > nNodeIdx )
                break;
        }
    }

    return bHasMarks;
}

/*  */

void SwHTMLParser::StripTrailingPara()
{
    bool bSetSmallFont = false;

    SwContentNode* pCNd = m_pPam->GetPointContentNode();
    SwNodeOffset nNodeIdx = m_pPam->GetPoint()->GetNodeIndex();
    if( !m_pPam->GetPoint()->GetContentIndex() )
    {
        if( pCNd && pCNd->StartOfSectionIndex() + 2 <
            pCNd->EndOfSectionIndex() && CanRemoveNode(nNodeIdx))
        {

            for(sw::SpzFrameFormat* pSpz: *m_xDoc->GetSpzFrameFormats())
            {
                SwFormatAnchor const*const pAnchor = &pSpz->GetAnchor();
                SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
                if (pAnchorNode &&
                    ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
                     (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
                    pAnchorNode->GetIndex() == nNodeIdx )

                    return;     // we can't delete the node
            }

            SetAttr( false );   // the still open attributes must be
                                // closed before the node is deleted,
                                // otherwise the last index is dangling

            if( pCNd->Len() && pCNd->IsTextNode() )
            {
                // fields were inserted into the node, now they have
                // to be moved
                SwTextNode *pPrvNd = m_xDoc->GetNodes()[nNodeIdx-1]->GetTextNode();
                if( pPrvNd )
                {
                    SwContentIndex aSrc( pCNd, 0 );
                    pCNd->GetTextNode()->CutText( pPrvNd, aSrc, pCNd->Len() );
                }
            }

            // now we have to move maybe existing bookmarks
            IDocumentMarkAccess* const pMarkAccess = m_xDoc->getIDocumentMarkAccess();
            for(auto ppMark = pMarkAccess->getAllMarksBegin();
                ppMark != pMarkAccess->getAllMarksEnd();
                ++ppMark)
            {
                ::sw::mark::MarkBase* pMark = *ppMark;

                SwNodeOffset nBookNdIdx = pMark->GetMarkPos().GetNodeIndex();
                if(nBookNdIdx==nNodeIdx)
                {
                    SwNodeIndex nNewNdIdx(m_pPam->GetPoint()->GetNode());
                    SwContentNode* pNd = SwNodes::GoPrevious(&nNewNdIdx);
                    if(!pNd)
                    {
                        OSL_ENSURE(false, "Oops, where is my predecessor node?");
                        return;
                    }
                    // #i81002# - refactoring
                    // Do not directly manipulate member of <SwBookmark>
                    {
                        const SwPaM aPaM(*pNd, pNd->Len());
                        pMarkAccess->repositionMark(*ppMark, aPaM);
                    }
                }
                else if( nBookNdIdx > nNodeIdx )
                    break;
            }

            SwNode& rDelNode = m_pPam->GetPoint()->GetNode();
            m_pPam->Move( fnMoveBackward, GoInNode );
            m_pPam->SetMark();
            m_pPam->DeleteMark();
            m_xDoc->GetNodes().Delete( rDelNode );
        }
        else if (pCNd && pCNd->IsTextNode() && m_xTable)
        {
            // In empty cells we set a small font, so that the cell doesn't
            // get higher than the graphic resp. as low as possible.
            bSetSmallFont = true;
        }
    }
    else if( pCNd && pCNd->IsTextNode() && m_xTable &&
             pCNd->StartOfSectionIndex()+2 ==
             pCNd->EndOfSectionIndex() )
    {
        // When the cell contains only as-character anchored graphics/frames,
        // then we also set a small font.
        bSetSmallFont = true;
        SwTextNode* pTextNd = pCNd->GetTextNode();

        sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex();
        while( bSetSmallFont && nPos>0 )
        {
            --nPos;
            bSetSmallFont =
                (CH_TXTATR_BREAKWORD == pTextNd->GetText()[nPos]) &&
                (nullptr != pTextNd->GetTextAttrForCharAt( nPos, RES_TXTATR_FLYCNT ));
        }
    }

    if( bSetSmallFont )
    {
        // Added default to CJK and CTL
        SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE );
        pCNd->SetAttr( aFontHeight );
        SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE );
        pCNd->SetAttr( aFontHeightCJK );
        SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE );
        pCNd->SetAttr( aFontHeightCTL );
    }
}

void SwHTMLParser::NotifyMacroEventRead()
{
    if (m_bNotifyMacroEventRead)
        return;
    SwDocShell *pDocSh = m_xDoc->GetDocShell();
    if (!pDocSh)
        return;
    comphelper::DocumentInfo::notifyMacroEventRead(pDocSh->GetModel());
    m_bNotifyMacroEventRead = true;
}

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