/* -*- 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 <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/base64.hxx>
#include <comphelper/configuration.hxx>
#include <comphelper/mediamimetype.hxx>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/text/XTextFrame.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/text/SizeType.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/document/XEventsSupplier.hpp>
#include <com/sun/star/document/XEmbeddedObjectSupplier.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/text/HoriOrientation.hpp>
#include <com/sun/star/text/VertOrientation.hpp>
#include <sax/tools/converter.hxx>
#include <utility>
#include <xmloff/xmlimp.hxx>
#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmluconv.hxx>
#include "XMLAnchorTypePropHdl.hxx"
#include <XMLEmbeddedObjectImportContext.hxx>
#include <xmloff/XMLBase64ImportContext.hxx>
#include <XMLReplacementImageContext.hxx>
#include <xmloff/prstylei.hxx>
#include <xmloff/i18nmap.hxx>
#include <xexptran.hxx>
#include <xmloff/shapeimport.hxx>
#include <xmloff/XMLEventsImportContext.hxx>
#include <XMLImageMapContext.hxx>
#include "XMLTextFrameContext.hxx"
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <map>
#include <string_view>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::xml::sax;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::document;
using namespace ::xmloff::token;
using ::com::sun::star::document::XEventsSupplier;

#define XML_TEXT_FRAME_TEXTBOX 1
#define XML_TEXT_FRAME_GRAPHIC 2
#define XML_TEXT_FRAME_OBJECT 3
#define XML_TEXT_FRAME_OBJECT_OLE 4
#define XML_TEXT_FRAME_APPLET 5
#define XML_TEXT_FRAME_PLUGIN 6
#define XML_TEXT_FRAME_FLOATING_FRAME 7

typedef ::std::map < const OUString, OUString > ParamMap;

class XMLTextFrameContextHyperlink_Impl
{
    OUString sHRef;
    OUString sName;
    OUString sTargetFrameName;
    bool bMap;

public:

    inline XMLTextFrameContextHyperlink_Impl( OUString aHRef,
                       OUString aName,
                       OUString aTargetFrameName,
                       bool bMap );

    const OUString& GetHRef() const { return sHRef; }
    const OUString& GetName() const { return sName; }
    const OUString& GetTargetFrameName() const { return sTargetFrameName; }
    bool GetMap() const { return bMap; }
};

inline XMLTextFrameContextHyperlink_Impl::XMLTextFrameContextHyperlink_Impl(
    OUString aHRef, OUString aName,
    OUString aTargetFrameName, bool bM ) :
    sHRef(std::move( aHRef )),
    sName(std::move( aName )),
    sTargetFrameName(std::move( aTargetFrameName )),
    bMap( bM )
{
}

namespace {

// Implement Title/Description Elements UI (#i73249#)
class XMLTextFrameTitleOrDescContext_Impl : public SvXMLImportContext
{
    OUString&   mrTitleOrDesc;

public:


    XMLTextFrameTitleOrDescContext_Impl( SvXMLImport& rImport,
                                         OUString& rTitleOrDesc );

    virtual void SAL_CALL characters( const OUString& rText ) override;
};

}

XMLTextFrameTitleOrDescContext_Impl::XMLTextFrameTitleOrDescContext_Impl(
        SvXMLImport& rImport,
        OUString& rTitleOrDesc )
    : SvXMLImportContext( rImport )
    , mrTitleOrDesc( rTitleOrDesc )
{
}

void XMLTextFrameTitleOrDescContext_Impl::characters( const OUString& rText )
{
    mrTitleOrDesc += rText;
}

namespace {

class XMLTextFrameParam_Impl : public SvXMLImportContext
{
public:
    XMLTextFrameParam_Impl( SvXMLImport& rImport,
            const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList,
            ParamMap &rParamMap);
};

}

XMLTextFrameParam_Impl::XMLTextFrameParam_Impl(
        SvXMLImport& rImport,
        const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList,
        ParamMap &rParamMap):
    SvXMLImportContext( rImport )
{
    OUString sName, sValue;
    bool bFoundValue = false; // to allow empty values
    for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ))
    {
        switch (aIter.getToken())
        {
            case XML_ELEMENT(DRAW, XML_VALUE):
            {
                sValue = aIter.toString();
                bFoundValue = true;
                break;
            }
            case XML_ELEMENT(DRAW, XML_NAME):
                sName = aIter.toString();
                break;
            default:
                XMLOFF_WARN_UNKNOWN("xmloff", aIter);
        }
    }
    if (!sName.isEmpty() && bFoundValue )
        rParamMap[sName] = sValue;
}

namespace {

class XMLTextFrameContourContext_Impl : public SvXMLImportContext
{
    Reference < XPropertySet > xPropSet;

public:


    XMLTextFrameContourContext_Impl( SvXMLImport& rImport, sal_Int32 nElement,
            const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList,
            const Reference < XPropertySet >& rPropSet,
            bool bPath );
};

}

XMLTextFrameContourContext_Impl::XMLTextFrameContourContext_Impl(
        SvXMLImport& rImport,
        sal_Int32 /*nElement*/,
        const Reference< XFastAttributeList > & xAttrList,
        const Reference < XPropertySet >& rPropSet,
        bool bPath ) :
    SvXMLImportContext( rImport ),
    xPropSet( rPropSet )
{
    OUString sD, sPoints, sViewBox;
    bool bPixelWidth = false, bPixelHeight = false;
    bool bAuto = false;
    sal_Int32 nWidth = 0;
    sal_Int32 nHeight = 0;

    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
    {
        switch( aIter.getToken() )
        {
        case XML_ELEMENT(SVG, XML_VIEWBOX):
        case XML_ELEMENT(SVG_COMPAT, XML_VIEWBOX):
            sViewBox = aIter.toString();
            break;
        case XML_ELEMENT(SVG, XML_D):
        case XML_ELEMENT(SVG_COMPAT, XML_D):
            if( bPath )
                sD = aIter.toString();
            break;
        case XML_ELEMENT(DRAW,XML_POINTS):
            if( !bPath )
                sPoints = aIter.toString();
            break;
        case XML_ELEMENT(SVG, XML_WIDTH):
        case XML_ELEMENT(SVG_COMPAT, XML_WIDTH):
            if (::sax::Converter::convertMeasurePx(nWidth, aIter.toView()))
                bPixelWidth = true;
            else
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        nWidth, aIter.toView());
            break;
        case XML_ELEMENT(SVG, XML_HEIGHT):
        case XML_ELEMENT(SVG_COMPAT, XML_HEIGHT):
            if (::sax::Converter::convertMeasurePx(nHeight, aIter.toView()))
                bPixelHeight = true;
            else
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        nHeight, aIter.toView());
            break;
        case  XML_ELEMENT(DRAW, XML_RECREATE_ON_EDIT):
            bAuto = IsXMLToken(aIter, XML_TRUE);
            break;
        }
    }

    OUString sContourPolyPolygon(u"ContourPolyPolygon"_ustr);
    Reference < XPropertySetInfo > xPropSetInfo = rPropSet->getPropertySetInfo();

    if(!xPropSetInfo->hasPropertyByName(sContourPolyPolygon) ||
        nWidth <= 0 || nHeight <= 0 || bPixelWidth != bPixelHeight ||
        !(bPath ? sD : sPoints).getLength())
        return;

    const SdXMLImExViewBox aViewBox( sViewBox, GetImport().GetMM100UnitConverter());
    basegfx::B2DPolyPolygon aPolyPolygon;

    // Related tdf#161833: ignore saved polygon for "recreate on edit" contours
    // tdf#161833 would cause semi-transparent pixels to be treated as fully
    // transparent pixels when calculating the wrap contour for an image. To
    // force the correct contour when loading a document, force the contour
    // to be recalculated by ignoring the saved polygon if the contour is set
    // to "recreate on edit".
    if( !bAuto )
    {
        if( bPath )
        {
            basegfx::utils::importFromSvgD(aPolyPolygon, sD, GetImport().needFixPositionAfterZ(), nullptr);
        }
        else
        {
            basegfx::B2DPolygon aPolygon;

            if(basegfx::utils::importFromSvgPoints(aPolygon, sPoints))
            {
                aPolyPolygon = basegfx::B2DPolyPolygon(aPolygon);
            }
        }
    }

    if(aPolyPolygon.count())
    {
        const basegfx::B2DRange aSourceRange(
            aViewBox.GetX(), aViewBox.GetY(),
            aViewBox.GetX() + aViewBox.GetWidth(), aViewBox.GetY() + aViewBox.GetHeight());
        const basegfx::B2DRange aTargetRange(
            0.0, 0.0,
            nWidth, nHeight);

        if(!aSourceRange.equal(aTargetRange))
        {
            aPolyPolygon.transform(
                basegfx::utils::createSourceRangeTargetRangeTransform(
                    aSourceRange,
                    aTargetRange));
        }

        css::drawing::PointSequenceSequence aPointSequenceSequence;
        basegfx::utils::B2DPolyPolygonToUnoPointSequenceSequence(aPolyPolygon, aPointSequenceSequence);
        xPropSet->setPropertyValue( sContourPolyPolygon, Any(aPointSequenceSequence) );
    }

    static constexpr OUString sIsPixelContour(u"IsPixelContour"_ustr);

    if( xPropSetInfo->hasPropertyByName( sIsPixelContour ) )
    {
        xPropSet->setPropertyValue( sIsPixelContour, Any(bPixelWidth) );
    }

    static constexpr OUString sIsAutomaticContour(u"IsAutomaticContour"_ustr);

    if( xPropSetInfo->hasPropertyByName( sIsAutomaticContour ) )
    {
        xPropSet->setPropertyValue( sIsAutomaticContour, Any(bAuto) );
    }
}

namespace {

class XMLTextFrameContext_Impl : public SvXMLImportContext
{
    css::uno::Reference < css::text::XTextCursor > xOldTextCursor;
    css::uno::Reference < css::beans::XPropertySet > xPropSet;
    css::uno::Reference < css::io::XOutputStream > xBase64Stream;

    /// old list item and block (#89891#)
    bool mbListContextPushed;

    OUString m_sOrigName;
    OUString sName;
    OUString sStyleName;
    OUString sNextName;
    OUString sHRef;
    OUString sCode;
    OUString sMimeType;
    OUString sFrameName;
    OUString sAppletName;
    OUString sFilterService;
    OUString sBase64CharsLeft;
    OUString sTblName;
    OUStringBuffer maUrlBuffer;

    ParamMap aParamMap;

    sal_Int32   nX;
    sal_Int32   nY;
    sal_Int32   nWidth;
    sal_Int32   nHeight;
    sal_Int32   nZIndex;
    sal_Int16   nPage;
    sal_Int16   nRotation;
    sal_Int16   nRelWidth;
    sal_Int16   nRelHeight;

    sal_uInt16 nType;
    css::text::TextContentAnchorType   eAnchorType;

    bool    bMayScript : 1;
    bool    bMinWidth : 1;
    bool    bMinHeight : 1;
    bool    bSyncWidth : 1;
    bool    bSyncHeight : 1;
    bool    bCreateFailed : 1;
    bool    bOwnBase64Stream : 1;
    bool    mbMultipleContent : 1; // This context is created based on a multiple content (image)
    bool    m_isDecorative = false;
    bool    m_isSplitAllowed = false;

    void Create();

public:


    bool CreateIfNotThere();
    const OUString& GetHRef() const { return sHRef; }

    XMLTextFrameContext_Impl( SvXMLImport& rImport,
            sal_Int32 nElement,
            const css::uno::Reference<css::xml::sax::XFastAttributeList > & rAttrList,
            css::text::TextContentAnchorType eAnchorType,
            sal_uInt16 nType,
            const css::uno::Reference<css::xml::sax::XFastAttributeList > & rFrameAttrList,
            bool bMultipleContent = false );

    virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;

    virtual void SAL_CALL characters( const OUString& rChars ) override;

    virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext(
        sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& AttrList ) override;

    void SetHyperlink( const OUString& rHRef,
                       const OUString& rName,
                       const OUString& rTargetFrameName,
                       bool bMap );

    // Implement Title/Description Elements UI (#i73249#)
    void SetTitle( const OUString& rTitle );

    void SetDesc( const OUString& rDesc );

    void SetName();

    const OUString& GetOrigName() const { return m_sOrigName; }

    css::text::TextContentAnchorType GetAnchorType() const { return eAnchorType; }
    const OUString & GetMimeType() const { return sMimeType; }

    const css::uno::Reference < css::beans::XPropertySet >& GetPropSet() const { return xPropSet; }
};

}

void XMLTextFrameContext_Impl::Create()
{
    rtl::Reference < XMLTextImportHelper > xTextImportHelper =
        GetImport().GetTextImport();

    switch ( nType)
    {
        case XML_TEXT_FRAME_OBJECT:
        case XML_TEXT_FRAME_OBJECT_OLE:
            if( xBase64Stream.is() )
            {
                OUString sURL( GetImport().ResolveEmbeddedObjectURLFromBase64() );
                if( !sURL.isEmpty() )
                    xPropSet = GetImport().GetTextImport()
                            ->createAndInsertOLEObject( GetImport(), sURL,
                                                        sStyleName,
                                                        sTblName,
                                                        nWidth, nHeight );
            }
            else if( !sHRef.isEmpty() )
            {
                OUString sURL( GetImport().ResolveEmbeddedObjectURL( sHRef,
                                                                std::u16string_view() ) );

                if( GetImport().IsPackageURL( sHRef ) )
                {
                    xPropSet = GetImport().GetTextImport()
                            ->createAndInsertOLEObject( GetImport(), sURL,
                                                        sStyleName,
                                                        sTblName,
                                                        nWidth, nHeight );
                }
                else
                {
                    // it should be an own OOo link that has no storage persistence
                    xPropSet = GetImport().GetTextImport()
                            ->createAndInsertOOoLink( GetImport(),
                                                        sURL,
                                                        sStyleName,
                                                        sTblName,
                                                        nWidth, nHeight );
                }
            }
            else
            {
                OUString sURL = "vnd.sun.star.ServiceName:" + sFilterService;
                xPropSet = GetImport().GetTextImport()
                            ->createAndInsertOLEObject( GetImport(), sURL,
                                                        sStyleName,
                                                        sTblName,
                                                        nWidth, nHeight );

            }
            break;
        case XML_TEXT_FRAME_APPLET:
        {
            xPropSet = GetImport().GetTextImport()
                            ->createAndInsertApplet( sAppletName, sCode,
                                                     bMayScript, sHRef,
                                                     nWidth, nHeight);
            break;
        }
        case XML_TEXT_FRAME_PLUGIN:
        {
            if(!sHRef.isEmpty())
                GetImport().GetAbsoluteReference(sHRef);
            xPropSet = GetImport().GetTextImport()
                            ->createAndInsertPlugin( sMimeType, sHRef,
                                                         nWidth, nHeight);

            break;
        }
        case XML_TEXT_FRAME_FLOATING_FRAME:
        {
            xPropSet = GetImport().GetTextImport()
                            ->createAndInsertFloatingFrame( sFrameName, sHRef,
                                                            sStyleName,
                                                            nWidth, nHeight);
            break;
        }
        default:
        {
            Reference<XMultiServiceFactory> xFactory( GetImport().GetModel(),
                                                      UNO_QUERY );
            if( xFactory.is() )
            {
                OUString sServiceName;
                switch( nType )
                {
                    case XML_TEXT_FRAME_TEXTBOX: sServiceName = "com.sun.star.text.TextFrame"; break;
                    case XML_TEXT_FRAME_GRAPHIC: sServiceName = "com.sun.star.text.GraphicObject"; break;
                }
                Reference<XInterface> xIfc = xFactory->createInstance( sServiceName );
                SAL_WARN_IF( !xIfc.is(), "xmloff.text", "couldn't create frame" );
                if( xIfc.is() )
                    xPropSet.set( xIfc, UNO_QUERY );
            }
        }
    }

    if( !xPropSet.is() )
    {
        bCreateFailed = true;
        return;
    }

    Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();

    // Skip duplicated frames
    if(!mbMultipleContent && // It's allowed to have multiple image for the same frame
       !sName.isEmpty() &&
       xTextImportHelper->IsDuplicateFrame(sName, nX, nY, nWidth, nHeight))
    {
        bCreateFailed = true;
        return;
    }

    // set name
    Reference < XNamed > xNamed( xPropSet, UNO_QUERY );
    if( xNamed.is() )
    {
        OUString sOrigName( xNamed->getName() );
        if( sOrigName.isEmpty() ||
            (!sName.isEmpty() && sOrigName != sName) )
        {
            OUString sOldName( sName );

            sal_Int32 i = 0;
            while( xTextImportHelper->HasFrameByName( sName ) )
            {
                sName = sOldName + OUString::number( ++i );
            }
            xNamed->setName( sName );
            if( sName != sOldName )
            {
                xTextImportHelper->GetRenameMap().Add( XML_TEXT_RENAME_TYPE_FRAME,
                                             sOldName, sName );

            }
        }
    }

    // frame style
    XMLPropStyleContext *pStyle = nullptr;
    if( !sStyleName.isEmpty() )
    {
        pStyle = xTextImportHelper->FindAutoFrameStyle( sStyleName );
        if( pStyle )
            sStyleName = pStyle->GetParentName();
    }

    Any aAny;
    if( !sStyleName.isEmpty() )
    {
        OUString sDisplayStyleName( GetImport().GetStyleDisplayName(
                            XmlStyleFamily::SD_GRAPHICS_ID, sStyleName ) );
        const Reference < XNameContainer > & rStyles =
            xTextImportHelper->GetFrameStyles();
        if( rStyles.is() &&
            rStyles->hasByName( sDisplayStyleName ) )
        {
            xPropSet->setPropertyValue( u"FrameStyleName"_ustr, Any(sDisplayStyleName) );
        }
    }

    // anchor type (must be set before any other properties, because
    // otherwise some orientations cannot be set or will be changed
    // afterwards)
    xPropSet->setPropertyValue( u"AnchorType"_ustr, Any(eAnchorType) );

    // hard properties
    if( pStyle )
        pStyle->FillPropertySet( xPropSet );

    // x and y
    sal_Int16 nHoriOrient =  HoriOrientation::NONE;
    aAny = xPropSet->getPropertyValue( u"HoriOrient"_ustr );
    aAny >>= nHoriOrient;
    if( HoriOrientation::NONE == nHoriOrient )
    {
        xPropSet->setPropertyValue( u"HoriOrientPosition"_ustr, Any(nX) );
    }

    sal_Int16 nVertOrient =  VertOrientation::NONE;
    aAny = xPropSet->getPropertyValue( u"VertOrient"_ustr );
    aAny >>= nVertOrient;
    if( VertOrientation::NONE == nVertOrient )
    {
        xPropSet->setPropertyValue( u"VertOrientPosition"_ustr, Any(nY) );
    }

    // width
    if( nWidth > 0 )
    {
        xPropSet->setPropertyValue( u"Width"_ustr, Any(nWidth) );
    }
    if( nRelWidth > 0 || nWidth > 0 )
    {
        xPropSet->setPropertyValue( u"RelativeWidth"_ustr, Any(nRelWidth) );
    }
    if( bSyncWidth || nWidth > 0 )
    {
        xPropSet->setPropertyValue( u"IsSyncWidthToHeight"_ustr, Any(bSyncWidth) );
    }
    if( xPropSetInfo->hasPropertyByName( u"WidthType"_ustr ) &&
        (bMinWidth || nWidth > 0 || nRelWidth > 0 ) )
    {
        sal_Int16 nSizeType =
            (bMinWidth && XML_TEXT_FRAME_TEXTBOX == nType) ? SizeType::MIN
                                                           : SizeType::FIX;
        xPropSet->setPropertyValue( u"WidthType"_ustr, Any(nSizeType) );
    }

    if( nHeight > 0 )
    {
        xPropSet->setPropertyValue( u"Height"_ustr, Any(nHeight) );
    }
    if( nRelHeight > 0 || nHeight > 0 )
    {
        xPropSet->setPropertyValue( u"RelativeHeight"_ustr, Any(nRelHeight) );
    }
    if( bSyncHeight || nHeight > 0 )
    {
        xPropSet->setPropertyValue( u"IsSyncHeightToWidth"_ustr, Any(bSyncHeight) );
    }
    if( xPropSetInfo->hasPropertyByName( u"SizeType"_ustr ) &&
        (bMinHeight || nHeight > 0 || nRelHeight > 0 ) )
    {
        sal_Int16 nSizeType =
            (bMinHeight && XML_TEXT_FRAME_TEXTBOX == nType) ? SizeType::MIN
                                                            : SizeType::FIX;
        xPropSet->setPropertyValue( u"SizeType"_ustr, Any(nSizeType) );
    }

    if( XML_TEXT_FRAME_GRAPHIC == nType )
    {
        // URL
        OSL_ENSURE( !sHRef.isEmpty() || xBase64Stream.is(),
                    "neither URL nor base64 image data given" );
        uno::Reference<graphic::XGraphic> xGraphic;
        if (!sHRef.isEmpty())
        {
            xGraphic = GetImport().loadGraphicByURL(sHRef);
        }
        else if (xBase64Stream.is())
        {
            xGraphic = GetImport().loadGraphicFromBase64(xBase64Stream);
            xBase64Stream = nullptr;
        }

        if (xGraphic.is())
            xPropSet->setPropertyValue(u"Graphic"_ustr, Any(xGraphic));

        // filter name
        xPropSet->setPropertyValue( u"GraphicFilter"_ustr, Any(OUString()) );

        // rotation
        xPropSet->setPropertyValue( u"GraphicRotation"_ustr, Any(nRotation) );
    }

    // page number (must be set after the frame is inserted, because it
    // will be overwritten then inserting the frame.
    if( TextContentAnchorType_AT_PAGE == eAnchorType && nPage > 0 )
    {
        xPropSet->setPropertyValue( u"AnchorPageNo"_ustr, Any(nPage) );
    }

    if (m_isDecorative && xPropSetInfo->hasPropertyByName(u"Decorative"_ustr))
    {
        xPropSet->setPropertyValue(u"Decorative"_ustr, uno::Any(true));
    }

    if (m_isSplitAllowed && xPropSetInfo->hasPropertyByName(u"IsSplitAllowed"_ustr))
    {
        xPropSet->setPropertyValue(u"IsSplitAllowed"_ustr, uno::Any(true));
    }

    if( XML_TEXT_FRAME_OBJECT != nType  &&
        XML_TEXT_FRAME_OBJECT_OLE != nType  &&
        XML_TEXT_FRAME_APPLET != nType &&
        XML_TEXT_FRAME_PLUGIN!= nType &&
        XML_TEXT_FRAME_FLOATING_FRAME != nType)
    {
        Reference < XTextContent > xTxtCntnt( xPropSet, UNO_QUERY );
        try
        {
            xTextImportHelper->InsertTextContent(xTxtCntnt);
        }
        catch (lang::IllegalArgumentException const&)
        {
            TOOLS_WARN_EXCEPTION("xmloff.text", "Cannot import part of the text - probably an image in the text frame?");
            return;
        }
    }

    // Make adding the shape to Z-Ordering dependent from if we are
    // inside an inside_deleted_section (redlining). That is necessary
    // since the shape will be removed again later. It would lead to
    // errors if it would stay inside the Z-Ordering. Thus, the
    // easiest way to solve that conflict is to not add it here.
    if(!GetImport().HasTextImport()
        || !GetImport().GetTextImport()->IsInsideDeleteContext())
    {
        Reference < XShape > xShape( xPropSet, UNO_QUERY );

        GetImport().GetShapeImport()->shapeWithZIndexAdded( xShape, nZIndex );
    }

    if( XML_TEXT_FRAME_TEXTBOX != nType )
        return;

    xTextImportHelper->ConnectFrameChains( sName, sNextName, xPropSet );
    Reference < XTextFrame > xTxtFrame( xPropSet, UNO_QUERY );
    Reference < XText > xTxt = xTxtFrame->getText();
    xOldTextCursor = xTextImportHelper->GetCursor();
    xTextImportHelper->SetCursor( xTxt->createTextCursor() );

    // remember old list item and block (#89892#) and reset them
    // for the text frame
    xTextImportHelper->PushListContext();
    mbListContextPushed = true;
}

void XMLTextFrameContext::removeGraphicFromImportContext(const SvXMLImportContext& rContext)
{
    const XMLTextFrameContext_Impl* pXMLTextFrameContext_Impl = dynamic_cast< const XMLTextFrameContext_Impl* >(&rContext);

    if(!pXMLTextFrameContext_Impl)
        return;

    try
    {
        // just dispose to delete
        uno::Reference< lang::XComponent > xComp(pXMLTextFrameContext_Impl->GetPropSet(), UNO_QUERY);

        // Inform shape importer about the removal so it can adjust
        // z-indexes.
        uno::Reference<drawing::XShape> xShape(xComp, uno::UNO_QUERY);
        GetImport().GetShapeImport()->shapeRemoved(xShape);

        if(xComp.is())
        {
            xComp->dispose();
        }
    }
    catch( uno::Exception& )
    {
        OSL_FAIL( "Error in cleanup of multiple graphic object import (!)" );
    }
}

OUString XMLTextFrameContext::getMimeTypeFromImportContext(const SvXMLImportContext& rContext) const
{
    const XMLTextFrameContext_Impl* pXMLTextFrameContext_Impl = dynamic_cast<const XMLTextFrameContext_Impl*>(&rContext);

    if (pXMLTextFrameContext_Impl)
        return pXMLTextFrameContext_Impl->GetMimeType();

    return OUString();
}

OUString XMLTextFrameContext::getGraphicPackageURLFromImportContext(const SvXMLImportContext& rContext) const
{
    const XMLTextFrameContext_Impl* pXMLTextFrameContext_Impl = dynamic_cast< const XMLTextFrameContext_Impl* >(&rContext);

    if(pXMLTextFrameContext_Impl)
    {
        return "vnd.sun.star.Package:" + pXMLTextFrameContext_Impl->GetHRef();
    }

    return OUString();
}

css::uno::Reference<css::graphic::XGraphic> XMLTextFrameContext::getGraphicFromImportContext(const SvXMLImportContext& rContext) const
{
    uno::Reference<graphic::XGraphic> xGraphic;

    const XMLTextFrameContext_Impl* pXMLTextFrameContext_Impl = dynamic_cast<const XMLTextFrameContext_Impl*>(&rContext);

    if (pXMLTextFrameContext_Impl)
    {
        try
        {
            const uno::Reference<beans::XPropertySet>& xPropertySet = pXMLTextFrameContext_Impl->GetPropSet();

            if (xPropertySet.is())
            {
                xPropertySet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
            }
        }
        catch (uno::Exception&)
        {}
    }
    return xGraphic;
}

bool XMLTextFrameContext_Impl::CreateIfNotThere()
{
    if( !xPropSet.is() &&
        ( XML_TEXT_FRAME_OBJECT_OLE == nType ||
          XML_TEXT_FRAME_GRAPHIC == nType ) &&
        xBase64Stream.is() && !bCreateFailed )
    {
        if( bOwnBase64Stream )
            xBase64Stream->closeOutput();
        Create();
    }

    return xPropSet.is();
}

XMLTextFrameContext_Impl::XMLTextFrameContext_Impl(
        SvXMLImport& rImport,
        sal_Int32 /*nElement*/,
        const Reference< XFastAttributeList > & rAttrList,
        TextContentAnchorType eATyp,
        sal_uInt16 nNewType,
        const Reference< XFastAttributeList > & rFrameAttrList,
        bool bMultipleContent )
:   SvXMLImportContext( rImport )
,   mbListContextPushed( false )
,   nType( nNewType )
,   eAnchorType( eATyp )
{
    nX = 0;
    nY = 0;
    nWidth = 0;
    nHeight = 0;
    nZIndex = -1;
    nPage = 0;
    nRotation = 0;
    nRelWidth = 0;
    nRelHeight = 0;
    bMayScript = false;

    bMinHeight = false;
    bMinWidth = false;
    bSyncWidth = false;
    bSyncHeight = false;
    bCreateFailed = false;
    bOwnBase64Stream = false;
    mbMultipleContent = bMultipleContent;

    auto processAttr = [&](sal_Int32 nElement, const sax_fastparser::FastAttributeList::FastAttributeIter& aIter) -> void
    {
        switch( nElement )
        {
        case XML_ELEMENT(DRAW, XML_STYLE_NAME):
            sStyleName = aIter.toString();
            break;
        case XML_ELEMENT(DRAW, XML_NAME):
            m_sOrigName = aIter.toString();
            sName = m_sOrigName;
            break;
        case XML_ELEMENT(DRAW, XML_FRAME_NAME):
            sFrameName = aIter.toString();
            break;
        case XML_ELEMENT(DRAW, XML_APPLET_NAME):
            sAppletName = aIter.toString();
            break;
        case XML_ELEMENT(TEXT, XML_ANCHOR_TYPE):
            if( TextContentAnchorType_AT_PARAGRAPH == eAnchorType ||
                TextContentAnchorType_AT_CHARACTER == eAnchorType ||
                TextContentAnchorType_AS_CHARACTER == eAnchorType )
            {

                TextContentAnchorType eNew;
                if( XMLAnchorTypePropHdl::convert( aIter.toView(), eNew ) &&
                    ( TextContentAnchorType_AT_PARAGRAPH == eNew ||
                      TextContentAnchorType_AT_CHARACTER == eNew ||
                      TextContentAnchorType_AS_CHARACTER == eNew ||
                      TextContentAnchorType_AT_PAGE == eNew) )
                    eAnchorType = eNew;
            }
            break;
        case XML_ELEMENT(TEXT, XML_ANCHOR_PAGE_NUMBER):
            {
                sal_Int32 nTmp;
                sal_Int32 nMax = !comphelper::IsFuzzing() ? SHRT_MAX : 100;
                if (::sax::Converter::convertNumber(nTmp, aIter.toView(), 1, nMax))
                    nPage = static_cast<sal_Int16>(nTmp);
            }
            break;
        case XML_ELEMENT(SVG, XML_X):
        case XML_ELEMENT(SVG_COMPAT, XML_X):
            GetImport().GetMM100UnitConverter().convertMeasureToCore(
                    nX, aIter.toView());
            break;
        case XML_ELEMENT(SVG, XML_Y):
        case XML_ELEMENT(SVG_COMPAT, XML_Y):
            GetImport().GetMM100UnitConverter().convertMeasureToCore(
                    nY, aIter.toView() );
            break;
        case XML_ELEMENT(SVG, XML_WIDTH):
        case XML_ELEMENT(SVG_COMPAT, XML_WIDTH):
            // relative widths are obsolete since SRC617. Remove them some day!
            if( aIter.toView().find( '%' ) != std::string_view::npos )
            {
                sal_Int32 nTmp;
                if (::sax::Converter::convertPercent(nTmp, aIter.toView()))
                    nRelWidth = static_cast<sal_Int16>(nTmp);
            }
            else
            {
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        nWidth, aIter.toView(), 0 );
            }
            break;
        case XML_ELEMENT(STYLE, XML_REL_WIDTH):
            if( IsXMLToken(aIter, XML_SCALE) )
            {
                bSyncWidth = true;
            }
            else
            {
                sal_Int32 nTmp;
                if (::sax::Converter::convertPercent( nTmp, aIter.toView() ))
                    nRelWidth = static_cast<sal_Int16>(nTmp);
            }
            break;
        case XML_ELEMENT(FO, XML_MIN_WIDTH):
        case XML_ELEMENT(FO_COMPAT, XML_MIN_WIDTH):
            if( aIter.toView().find( '%' ) != std::string_view::npos )
            {
                sal_Int32 nTmp;
                if (::sax::Converter::convertPercent(nTmp, aIter.toView()))
                    nRelWidth = static_cast<sal_Int16>(nTmp);
            }
            else
            {
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        nWidth, aIter.toView(), 0 );
            }
            bMinWidth = true;
            break;
        case XML_ELEMENT(SVG, XML_HEIGHT):
        case XML_ELEMENT(SVG_COMPAT, XML_HEIGHT):
            // relative heights are obsolete since SRC617. Remove them some day!
            if( aIter.toView().find( '%' ) != std::string_view::npos )
            {
                sal_Int32 nTmp;
                if (::sax::Converter::convertPercent(nTmp, aIter.toView()))
                    nRelHeight = static_cast<sal_Int16>(nTmp);
            }
            else
            {
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        nHeight, aIter.toView(), 0 );
            }
            break;
        case XML_ELEMENT(STYLE, XML_REL_HEIGHT):
            if( IsXMLToken( aIter, XML_SCALE ) )
            {
                bSyncHeight = true;
            }
            else if( IsXMLToken( aIter, XML_SCALE_MIN ) )
            {
                bSyncHeight = true;
                bMinHeight = true;
            }
            else
            {
                sal_Int32 nTmp;
                if (::sax::Converter::convertPercent( nTmp, aIter.toView() ))
                    nRelHeight = static_cast<sal_Int16>(nTmp);
            }
            break;
        case XML_ELEMENT(FO, XML_MIN_HEIGHT):
        case XML_ELEMENT(FO_COMPAT, XML_MIN_HEIGHT):
            if( aIter.toView().find( '%' ) != std::string_view::npos )
            {
                sal_Int32 nTmp;
                if (::sax::Converter::convertPercent(nTmp, aIter.toView()))
                    nRelHeight = static_cast<sal_Int16>(nTmp);
            }
            else
            {
                GetImport().GetMM100UnitConverter().convertMeasureToCore(
                        nHeight, aIter.toView(), 0 );
            }
            bMinHeight = true;
            break;
        case XML_ELEMENT(DRAW, XML_ZINDEX):
            ::sax::Converter::convertNumber( nZIndex, aIter.toView(), -1 );
            break;
        case XML_ELEMENT(DRAW, XML_CHAIN_NEXT_NAME):
            sNextName = aIter.toString();
            break;
        case XML_ELEMENT(XLINK, XML_HREF):
            sHRef = aIter.toString();
            break;
        case XML_ELEMENT(DRAW, XML_TRANSFORM):
            {
                // RotateFlyFrameFix: im/export full 'draw:transform' using existing tooling
                // Currently only rotation is used, but combinations with 'draw:transform'
                // may be necessary in the future, so that svg:x/svg:y/svg:width/svg:height
                // may be extended/replaced with 'draw:transform' (see draw objects)
                SdXMLImExTransform2D aSdXMLImExTransform2D;
                basegfx::B2DHomMatrix aFullTransform;

                // Use SdXMLImExTransform2D to convert to transformation
                // Note: using GetTwipUnitConverter instead of GetMM100UnitConverter may be needed,
                // but is not generally available (as it should be, a 'current' UnitConverter should
                // be available at GetExport() - and maybe was once). May have to be addressed as soon
                // as translate transformations are used here.
                aSdXMLImExTransform2D.SetString(aIter.toString(), GetImport().GetMM100UnitConverter());
                aSdXMLImExTransform2D.GetFullTransform(aFullTransform);

                if(!aFullTransform.isIdentity())
                {
                    const basegfx::utils::B2DHomMatrixBufferedDecompose aDecomposedTransform(aFullTransform);

                    // currently we *only* use rotation (and translation indirectly), so warn if *any*
                    // of the other transform parts is used
                    SAL_WARN_IF(!basegfx::fTools::equal(1.0, aDecomposedTransform.getScale().getX()), "xmloff.text", "draw:transform uses scaleX" );
                    SAL_WARN_IF(!basegfx::fTools::equal(1.0, aDecomposedTransform.getScale().getY()), "xmloff.text", "draw:transform uses scaleY" );
                    SAL_WARN_IF(!basegfx::fTools::equalZero(aDecomposedTransform.getShearX()), "xmloff.text", "draw:transform uses shearX" );

                    // Translation comes from the translate to RotCenter, rot and BackTranslate.
                    // This means that it represents the translation between unrotated TopLeft
                    // and rotated TopLeft. This may be checked here now, but currently we only
                    // use rotation around center and assume that this *was* a rotation around
                    // center. The check would compare the object's center with the RotCenter
                    // that can be extracted from the transformation in aFullTransform.
                    // The definition contains implicitly the RotationCenter absolute
                    // to the scaled and translated object, so this may be used if needed (see
                    // _exportTextGraphic how the -trans/rot/trans is composed)

                    if(!basegfx::fTools::equalZero(aDecomposedTransform.getRotate()))
                    {
                        // rotation is used, set it. Convert from deg to 10th degree integer
                        // CAUTION: due to #i78696# (rotation mirrored using API) the rotate
                        // value is already mirrored, so do not do it again here (to be in sync
                        // with XMLTextParagraphExport::_exportTextGraphic normally it would need
                        // to me mirrored using * -1.0, see conversion there)
                        // CAUTION-II: due to tdf#115782 it is better for current ODF to indeed use it
                        // with the wrong orientation as in all other cases - ARGH! We will need to
                        // correct this in future ODF ASAP! For now, mirror the rotation here AGAIN
                        const double fRotate(-basegfx::rad2deg<10>(aDecomposedTransform.getRotate()));
                        nRotation = static_cast< sal_Int16 >(basegfx::fround(fRotate) % 3600);

                        // tdf#115529 may be negative, with the above modulo maximal -3599, so
                        // no loop needed here. nRotation is used in setPropertyValue("GraphicRotation")
                        // and *has* to be in the range [0 .. 3600[
                        if(nRotation < 0)
                        {
                            nRotation += 3600;
                        }
                    }
                }
            }
            break;
        case XML_ELEMENT(DRAW, XML_CODE):
            sCode = aIter.toString();
            break;
        case XML_ELEMENT(DRAW, XML_OBJECT):
            break;
        case XML_ELEMENT(DRAW, XML_ARCHIVE):
            break;
        case XML_ELEMENT(DRAW, XML_MAY_SCRIPT):
            bMayScript = IsXMLToken( aIter, XML_TRUE );
            break;
        case XML_ELEMENT(DRAW, XML_MIME_TYPE):
        case XML_ELEMENT(LO_EXT, XML_MIME_TYPE):
            sMimeType = aIter.toString();
            break;
        case XML_ELEMENT(DRAW, XML_NOTIFY_ON_UPDATE_OF_RANGES):
        case XML_ELEMENT(DRAW, XML_NOTIFY_ON_UPDATE_OF_TABLE):
            sTblName = aIter.toString();
            break;
        case XML_ELEMENT(LO_EXT, XML_DECORATIVE):
        case XML_ELEMENT(DRAW, XML_DECORATIVE):
            ::sax::Converter::convertBool(m_isDecorative, aIter.toString());
            break;
        case XML_ELEMENT(LO_EXT, XML_MAY_BREAK_BETWEEN_PAGES):
        case XML_ELEMENT(DRAW, XML_MAY_BREAK_BETWEEN_PAGES):
            sax::Converter::convertBool(m_isSplitAllowed, aIter.toString());
            break;
        default:
            SAL_INFO("xmloff", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << " value=" << aIter.toString());
        }
    };

    for( auto& aIter : sax_fastparser::castToFastAttributeList(rAttrList) )
            processAttr(aIter.getToken(), aIter);
    for( auto& aIter : sax_fastparser::castToFastAttributeList(rFrameAttrList) )
            processAttr(aIter.getToken(), aIter);

    if( ( (XML_TEXT_FRAME_GRAPHIC == nType ||
           XML_TEXT_FRAME_OBJECT == nType ||
           XML_TEXT_FRAME_OBJECT_OLE == nType) &&
          sHRef.isEmpty() ) ||
        ( XML_TEXT_FRAME_APPLET  == nType && sCode.isEmpty() ) ||
        ( XML_TEXT_FRAME_PLUGIN == nType &&
          sHRef.isEmpty() && sMimeType.isEmpty() ) )
        return; // no URL: no image or OLE object

    Create();
}

void XMLTextFrameContext_Impl::endFastElement(sal_Int32 )
{
    if( ( XML_TEXT_FRAME_OBJECT_OLE == nType ||
          XML_TEXT_FRAME_GRAPHIC == nType) &&
        !xPropSet.is() && !bCreateFailed )
    {
        std::u16string_view sTrimmedChars = o3tl::trim(maUrlBuffer);
        if( !sTrimmedChars.empty() )
        {
            if( !xBase64Stream.is() )
            {
                if( XML_TEXT_FRAME_GRAPHIC == nType )
                {
                    xBase64Stream =
                        GetImport().GetStreamForGraphicObjectURLFromBase64();
                }
                else
                {
                    xBase64Stream =
                        GetImport().GetStreamForEmbeddedObjectURLFromBase64();
                }
                if( xBase64Stream.is() )
                    bOwnBase64Stream = true;
            }
            if( bOwnBase64Stream && xBase64Stream.is() )
            {
                OUString sChars;
                if( !sBase64CharsLeft.isEmpty() )
                {
                    sChars = sBase64CharsLeft + sTrimmedChars;
                    sBase64CharsLeft.clear();
                }
                else
                {
                    sChars = sTrimmedChars;
                }
                Sequence< sal_Int8 > aBuffer( (sChars.getLength() / 4) * 3 );
                sal_Int32 nCharsDecoded =
                    ::comphelper::Base64::decodeSomeChars( aBuffer, sChars );
                xBase64Stream->writeBytes( aBuffer );
                if( nCharsDecoded != sChars.getLength() )
                    sBase64CharsLeft = sChars.copy( nCharsDecoded );
            }
        }
        maUrlBuffer.setLength(0);
    }

    CreateIfNotThere();

    if( xOldTextCursor.is() )
    {
        GetImport().GetTextImport()->DeleteParagraph();
        GetImport().GetTextImport()->SetCursor( xOldTextCursor );
    }

    // reinstall old list item (if necessary) #89892#
    if (mbListContextPushed) {
        GetImport().GetTextImport()->PopListContext();
    }

    if (( nType == XML_TEXT_FRAME_APPLET || nType == XML_TEXT_FRAME_PLUGIN ) && xPropSet.is())
        GetImport().GetTextImport()->endAppletOrPlugin( xPropSet, aParamMap);
}

css::uno::Reference< css::xml::sax::XFastContextHandler > XMLTextFrameContext_Impl::createFastChildContext(
    sal_Int32 nElement,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    if( nElement == XML_ELEMENT(DRAW, XML_PARAM) )
    {
        if ( nType == XML_TEXT_FRAME_APPLET || nType == XML_TEXT_FRAME_PLUGIN )
            return new XMLTextFrameParam_Impl( GetImport(),
                                               xAttrList, aParamMap );
    }
    else if( nElement == XML_ELEMENT(OFFICE, XML_BINARY_DATA) )
    {
        if( !xPropSet.is() && !xBase64Stream.is() && !bCreateFailed )
        {
            switch( nType )
            {
            case XML_TEXT_FRAME_GRAPHIC:
                xBase64Stream =
                    GetImport().GetStreamForGraphicObjectURLFromBase64();
                break;
            case XML_TEXT_FRAME_OBJECT_OLE:
                xBase64Stream =
                    GetImport().GetStreamForEmbeddedObjectURLFromBase64();
                break;
            }
            if( xBase64Stream.is() )
                return new XMLBase64ImportContext( GetImport(), xBase64Stream );
        }
    }
    // Correction of condition which also avoids warnings. (#i100480#)
    if( XML_TEXT_FRAME_OBJECT == nType &&
        ( nElement == XML_ELEMENT(OFFICE, XML_DOCUMENT) ||
          nElement == XML_ELEMENT(MATH, XML_MATH) ) )
    {
        if( !xPropSet.is() && !bCreateFailed )
        {
            XMLEmbeddedObjectImportContext *pEContext =
                new XMLEmbeddedObjectImportContext( GetImport(), nElement, xAttrList );
            sFilterService = pEContext->GetFilterServiceName();
            if( !sFilterService.isEmpty() )
            {
                Create();
                if( xPropSet.is() )
                {
                    Reference < XEmbeddedObjectSupplier > xEOS( xPropSet,
                                                                UNO_QUERY );
                    OSL_ENSURE( xEOS.is(),
                            "no embedded object supplier for own object" );
                    Reference<css::lang::XComponent> aXComponent(xEOS->getEmbeddedObject());
                    pEContext->SetComponent( aXComponent );
                }
            }
            return pEContext;
        }
    }

    if( xOldTextCursor.is() )  // text-box
    {
        auto p = GetImport().GetTextImport()->CreateTextChildContext(
                            GetImport(), nElement, xAttrList,
                            XMLTextType::TextBox );
        if (p)
            return p;
    }

    XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);

    return nullptr;
}

void XMLTextFrameContext_Impl::characters( const OUString& rChars )
{
    maUrlBuffer.append(rChars);
}

void XMLTextFrameContext_Impl::SetHyperlink( const OUString& rHRef,
                       const OUString& rName,
                       const OUString& rTargetFrameName,
                       bool bMap )
{
    static constexpr OUString s_HyperLinkURL = u"HyperLinkURL"_ustr;
    static constexpr OUString s_HyperLinkName = u"HyperLinkName"_ustr;
    static constexpr OUString s_HyperLinkTarget = u"HyperLinkTarget"_ustr;
    static constexpr OUString s_ServerMap = u"ServerMap"_ustr;
    if( !xPropSet.is() )
        return;

    Reference < XPropertySetInfo > xPropSetInfo =
        xPropSet->getPropertySetInfo();
    if( !xPropSetInfo.is() ||
        !xPropSetInfo->hasPropertyByName(s_HyperLinkURL))
        return;

    xPropSet->setPropertyValue( s_HyperLinkURL, Any(rHRef) );

    if (xPropSetInfo->hasPropertyByName(s_HyperLinkName))
    {
        xPropSet->setPropertyValue(s_HyperLinkName, Any(rName));
    }

    if (xPropSetInfo->hasPropertyByName(s_HyperLinkTarget))
    {
        xPropSet->setPropertyValue( s_HyperLinkTarget, Any(rTargetFrameName) );
    }

    if (xPropSetInfo->hasPropertyByName(s_ServerMap))
    {
        xPropSet->setPropertyValue(s_ServerMap, Any(bMap));
    }
}

void XMLTextFrameContext_Impl::SetName()
{
    Reference<XNamed> xNamed(xPropSet, UNO_QUERY);
    if (m_sOrigName.isEmpty() || !xNamed.is())
        return;

    OUString const name(xNamed->getName());
    if (name != m_sOrigName)
    {
        try
        {
            xNamed->setName(m_sOrigName);
        }
        catch (uno::Exception const&)
        {   // fdo#71698 document contains 2 frames with same draw:name
            TOOLS_INFO_EXCEPTION("xmloff.text", "SetName(): exception setting \""
                    << m_sOrigName << "\"");
        }
    }
}

// Implement Title/Description Elements UI (#i73249#)
void XMLTextFrameContext_Impl::SetTitle( const OUString& rTitle )
{
    if ( xPropSet.is() )
    {
        Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
        if( xPropSetInfo->hasPropertyByName( u"Title"_ustr ) )
        {
            xPropSet->setPropertyValue( u"Title"_ustr, Any( rTitle ) );
        }
    }
}

void XMLTextFrameContext_Impl::SetDesc( const OUString& rDesc )
{
    if ( xPropSet.is() )
    {
        Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
        if( xPropSetInfo->hasPropertyByName( u"Description"_ustr ) )
        {
            xPropSet->setPropertyValue( u"Description"_ustr, Any( rDesc ) );
        }
    }
}


bool XMLTextFrameContext::CreateIfNotThere( css::uno::Reference < css::beans::XPropertySet >& rPropSet )
{
    SvXMLImportContext *pContext = m_xImplContext.get();
    XMLTextFrameContext_Impl *pImpl = dynamic_cast< XMLTextFrameContext_Impl*>( pContext );
    if( pImpl && pImpl->CreateIfNotThere() )
        rPropSet = pImpl->GetPropSet();

    return rPropSet.is();
}

XMLTextFrameContext::XMLTextFrameContext(
        SvXMLImport& rImport,
        const Reference< XFastAttributeList > & xAttrList,
        TextContentAnchorType eATyp )
:   SvXMLImportContext( rImport )
,   m_xAttrList( new sax_fastparser::FastAttributeList( xAttrList ) )
    // Implement Title/Description Elements UI (#i73249#)
,   m_eDefaultAnchorType( eATyp )
    // Shapes in Writer cannot be named via context menu (#i51726#)
,   m_HasAutomaticStyleWithoutParentStyle( false )
,   m_bSupportsReplacement( false )
{
    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
    {
        // New distinguish attribute between Writer objects and Draw objects is:
        // Draw objects have an automatic style without a parent style (#i51726#)
        switch (aIter.getToken())
        {
            case XML_ELEMENT(DRAW, XML_STYLE_NAME):
            {
                OUString aStyleName = aIter.toString();
                if( !aStyleName.isEmpty() )
                {
                    rtl::Reference < XMLTextImportHelper > xTxtImport =
                                                        GetImport().GetTextImport();
                    XMLPropStyleContext* pStyle = xTxtImport->FindAutoFrameStyle( aStyleName );
                    if ( pStyle && pStyle->GetParentName().isEmpty() )
                    {
                        m_HasAutomaticStyleWithoutParentStyle = true;
                    }
                }
                break;
            }
            case XML_ELEMENT(TEXT, XML_ANCHOR_TYPE):
            {
                TextContentAnchorType eNew;
                if( XMLAnchorTypePropHdl::convert( aIter.toView(), eNew ) &&
                    ( TextContentAnchorType_AT_PARAGRAPH == eNew ||
                      TextContentAnchorType_AT_CHARACTER == eNew ||
                      TextContentAnchorType_AS_CHARACTER == eNew ||
                      TextContentAnchorType_AT_PAGE == eNew) )
                    m_eDefaultAnchorType = eNew;
                break;
            }
        }
    }
}

void XMLTextFrameContext::endFastElement(sal_Int32 )
{
    /// solve if multiple image child contexts were imported
    SvXMLImportContextRef const pMultiContext(solveMultipleImages());

    SvXMLImportContext const*const pContext =
        (pMultiContext.is()) ? pMultiContext.get() : m_xImplContext.get();
    XMLTextFrameContext_Impl *pImpl = const_cast<XMLTextFrameContext_Impl*>(dynamic_cast< const XMLTextFrameContext_Impl*>( pContext ));
    assert(!pMultiContext.is() || pImpl);

    // When we are dealing with a textbox, pImpl will be null;
    // we need to set the hyperlink to the shape instead
    Reference<XShape> xShape = GetShape();
    if (xShape.is() && m_pHyperlink)
    {
        Reference<XPropertySet> xProps(xShape, UNO_QUERY);
        if (xProps.is())
            xProps->setPropertyValue(u"Hyperlink"_ustr, Any(m_pHyperlink->GetHRef()));
    }

    if( !pImpl )
        return;

    pImpl->CreateIfNotThere();

    // fdo#68839: in case the surviving image was not the first one,
    // it will have a counter added to its name - set the original name
    if (pMultiContext.is()) // do this only when necessary; esp. not for text
    {                  // frames that may have entries in GetRenameMap()!
        pImpl->SetName();
    }

    if( !m_sTitle.isEmpty() )
    {
        pImpl->SetTitle( m_sTitle );
    }
    if( !m_sDesc.isEmpty() )
    {
        pImpl->SetDesc( m_sDesc );
    }

    if( m_pHyperlink )
    {
        pImpl->SetHyperlink( m_pHyperlink->GetHRef(), m_pHyperlink->GetName(),
                      m_pHyperlink->GetTargetFrameName(), m_pHyperlink->GetMap() );
        m_pHyperlink.reset();
    }

    GetImport().GetTextImport()->StoreLastImportedFrameName(pImpl->GetOrigName());
}

css::uno::Reference< css::xml::sax::XFastContextHandler > XMLTextFrameContext::createFastChildContext(
    sal_Int32 nElement,
    const uno::Reference< xml::sax::XFastAttributeList>& xAttrList )
{
    SvXMLImportContextRef xContext;

    if( !m_xImplContext.is() )
    {
        // no child exists
        if( IsTokenInNamespace(nElement, XML_NAMESPACE_DRAW) )
        {
            sal_uInt16 nFrameType = USHRT_MAX;
            switch (nElement & TOKEN_MASK)
            {
                case XML_TEXT_BOX:
                    nFrameType = XML_TEXT_FRAME_TEXTBOX;
                    break;
                case XML_IMAGE:
                    nFrameType = XML_TEXT_FRAME_GRAPHIC;
                    break;
                case XML_OBJECT:
                    nFrameType = XML_TEXT_FRAME_OBJECT;
                    break;
                case XML_OBJECT_OLE:
                    nFrameType = XML_TEXT_FRAME_OBJECT_OLE;
                    break;
                case XML_APPLET:
                    nFrameType = XML_TEXT_FRAME_APPLET;
                    break;
                case XML_PLUGIN:
                    nFrameType = XML_TEXT_FRAME_PLUGIN;
                    break;
                case XML_FLOATING_FRAME:
                    nFrameType = XML_TEXT_FRAME_FLOATING_FRAME;
                    break;
            }

            if( USHRT_MAX != nFrameType )
            {
                // Shapes in Writer cannot be named via context menu (#i51726#)
                if ( ( XML_TEXT_FRAME_TEXTBOX == nFrameType ||
                       XML_TEXT_FRAME_GRAPHIC == nFrameType ) &&
                     m_HasAutomaticStyleWithoutParentStyle )
                {
                    Reference < XShapes > xShapes;
                    xContext = XMLShapeImportHelper::CreateFrameChildContext(
                                    GetImport(), nElement, xAttrList, xShapes, m_xAttrList );
                }
                else if( XML_TEXT_FRAME_PLUGIN == nFrameType )
                {
                    bool bMedia = false;

                    // check, if we have a media object
                    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
                    {
                        if( aIter.getToken() == XML_ELEMENT(DRAW, XML_MIME_TYPE) )
                        {
                            if (::comphelper::IsMediaMimeType(aIter.toView()))
                                bMedia = true;

                            // leave this loop
                            break;
                        }
                    }

                    if( bMedia )
                    {
                        Reference < XShapes > xShapes;
                        xContext = XMLShapeImportHelper::CreateFrameChildContext(
                                        GetImport(), nElement, xAttrList, xShapes, m_xAttrList );
                    }
                }
                else if( XML_TEXT_FRAME_OBJECT == nFrameType ||
                         XML_TEXT_FRAME_OBJECT_OLE == nFrameType )
                {
                    m_bSupportsReplacement = true;
                }
                else if(XML_TEXT_FRAME_GRAPHIC == nFrameType)
                {
                    setSupportsMultipleContents( (nElement & TOKEN_MASK) == XML_IMAGE );
                }

                if (!xContext)
                {
                    xContext = new XMLTextFrameContext_Impl( GetImport(), nElement,
                                                        xAttrList,
                                                        m_eDefaultAnchorType,
                                                        nFrameType,
                                                        m_xAttrList );
                }

                m_xImplContext = xContext;

                if(getSupportsMultipleContents() && XML_TEXT_FRAME_GRAPHIC == nFrameType)
                {
                    addContent(*m_xImplContext);
                }
            }
        }
    }
    else if(getSupportsMultipleContents() && nElement == XML_ELEMENT(DRAW, XML_IMAGE))
    {
        // read another image
        xContext = new XMLTextFrameContext_Impl(
            GetImport(), nElement, xAttrList,
            m_eDefaultAnchorType, XML_TEXT_FRAME_GRAPHIC, m_xAttrList, true);

        m_xImplContext = xContext;
        addContent(*m_xImplContext);
    }
    else if( m_bSupportsReplacement && !m_xReplImplContext.is() &&
             nElement == XML_ELEMENT(DRAW, XML_IMAGE) )
    {
        // read replacement image
        Reference < XPropertySet > xPropSet;
        if( CreateIfNotThere( xPropSet ) )
        {
            xContext = new XMLReplacementImageContext( GetImport(),
                                nElement, xAttrList, xPropSet );
            m_xReplImplContext = xContext;
        }
    }
    else if( nullptr != dynamic_cast< const XMLTextFrameContext_Impl*>( m_xImplContext.get() ))
    {
        // the child is a writer frame
        if( IsTokenInNamespace(nElement, XML_NAMESPACE_SVG) ||
            IsTokenInNamespace(nElement, XML_NAMESPACE_SVG_COMPAT) )
        {
            // Implement Title/Description Elements UI (#i73249#)
            const bool bOld = SvXMLImport::OOo_2x >= GetImport().getGeneratorVersion();
            if ( bOld )
            {
                if ( (nElement & TOKEN_MASK) == XML_DESC )
                {
                    xContext = new XMLTextFrameTitleOrDescContext_Impl( GetImport(),
                                                                        m_sTitle );
                }
            }
            else
            {
                if( (nElement & TOKEN_MASK) == XML_TITLE )
                {
                    if (getSupportsMultipleContents())
                    {   // tdf#103567 ensure props are set on surviving shape
                        m_xImplContext = solveMultipleImages();
                    }
                    xContext = new XMLTextFrameTitleOrDescContext_Impl( GetImport(),
                                                                        m_sTitle );
                }
                else if ( (nElement & TOKEN_MASK) == XML_DESC )
                {
                    if (getSupportsMultipleContents())
                    {   // tdf#103567 ensure props are set on surviving shape
                        m_xImplContext = solveMultipleImages();
                    }
                    xContext = new XMLTextFrameTitleOrDescContext_Impl( GetImport(),
                                                                        m_sDesc );
                }
            }
        }
        else if( IsTokenInNamespace(nElement, XML_NAMESPACE_DRAW) )
        {
            Reference < XPropertySet > xPropSet;
            if( (nElement & TOKEN_MASK) == XML_CONTOUR_POLYGON )
            {
                if (getSupportsMultipleContents())
                {   // tdf#103567 ensure props are set on surviving shape
                    m_xImplContext = solveMultipleImages();
                }
                if( CreateIfNotThere( xPropSet ) )
                    xContext = new XMLTextFrameContourContext_Impl( GetImport(), nElement,
                                                  xAttrList, xPropSet, false );
            }
            else if( (nElement & TOKEN_MASK) == XML_CONTOUR_PATH )
            {
                if (getSupportsMultipleContents())
                {   // tdf#103567 ensure props are set on surviving shape
                    m_xImplContext = solveMultipleImages();
                }
                if( CreateIfNotThere( xPropSet ) )
                    xContext = new XMLTextFrameContourContext_Impl( GetImport(), nElement,
                                                  xAttrList, xPropSet, true );
            }
            else if( (nElement & TOKEN_MASK) == XML_IMAGE_MAP )
            {
                if (getSupportsMultipleContents())
                {   // tdf#103567 ensure props are set on surviving shape
                    m_xImplContext = solveMultipleImages();
                }
                if( CreateIfNotThere( xPropSet ) )
                    xContext = new XMLImageMapContext( GetImport(), xPropSet );
            }
        }
        else if( nElement == XML_ELEMENT(OFFICE, XML_EVENT_LISTENERS) )
        {
            if (getSupportsMultipleContents())
            {   // tdf#103567 ensure props are set on surviving shape
                m_xImplContext = solveMultipleImages();
            }
            // do we still have the frame object?
            Reference < XPropertySet > xPropSet;
            if( CreateIfNotThere( xPropSet ) )
            {
                // is it an event supplier?
                Reference<XEventsSupplier> xEventsSupplier(xPropSet, UNO_QUERY);
                if (xEventsSupplier.is())
                {
                    // OK, we have the events, so create the context
                    xContext = new XMLEventsImportContext(GetImport(), xEventsSupplier);
                }
            }
        }
    }
    // #i68101#
    else if( nElement == XML_ELEMENT(SVG, XML_TITLE) || nElement == XML_ELEMENT(SVG, XML_DESC ) ||
             nElement == XML_ELEMENT(SVG_COMPAT, XML_TITLE) || nElement == XML_ELEMENT(SVG_COMPAT, XML_DESC ) )
    {
        if (getSupportsMultipleContents())
        {   // tdf#103567 ensure props are set on surviving shape
            // note: no more draw:image can be added once we get here
            m_xImplContext = solveMultipleImages();
        }
        xContext = static_cast<SvXMLImportContext*>(m_xImplContext->createFastChildContext( nElement, xAttrList ).get());
    }
    else if (nElement == XML_ELEMENT(LO_EXT, XML_SIGNATURELINE))
    {
        if (getSupportsMultipleContents())
        {   // tdf#103567 ensure props are set on surviving shape
            // note: no more draw:image can be added once we get here
            m_xImplContext = solveMultipleImages();
        }
        xContext = static_cast<SvXMLImportContext*>(m_xImplContext->createFastChildContext(nElement, xAttrList).get());
    }
    else if (nElement == XML_ELEMENT(LO_EXT, XML_QRCODE))
    {
        if (getSupportsMultipleContents())
        {   // tdf#103567 ensure props are set on surviving shape
            // note: no more draw:image can be added once we get here
            m_xImplContext = solveMultipleImages();
        }
        xContext = static_cast<SvXMLImportContext*>(m_xImplContext->createFastChildContext(nElement, xAttrList).get());
    }
    else if (nElement == XML_ELEMENT(DRAW, XML_A))
    {
        xContext = static_cast<SvXMLImportContext*>(m_xImplContext->createFastChildContext(nElement, xAttrList).get());
    }
    else
    {
        // the child is a drawing shape
        return XMLShapeImportHelper::CreateFrameChildContext(
                                    m_xImplContext.get(), nElement, xAttrList );
    }

    return xContext;
}

void XMLTextFrameContext::SetHyperlink( const OUString& rHRef,
                       const OUString& rName,
                       const OUString& rTargetFrameName,
                       bool bMap )
{
    OSL_ENSURE( !m_pHyperlink, "recursive SetHyperlink call" );
    m_pHyperlink = std::make_unique<XMLTextFrameContextHyperlink_Impl>(
                rHRef, rName, rTargetFrameName, bMap );
}

TextContentAnchorType XMLTextFrameContext::GetAnchorType() const
{
    SvXMLImportContext *pContext = m_xImplContext.get();
    XMLTextFrameContext_Impl *pImpl = dynamic_cast< XMLTextFrameContext_Impl*>( pContext );
    if( pImpl )
        return pImpl->GetAnchorType();
    else
        return m_eDefaultAnchorType;
}

Reference < XTextContent > XMLTextFrameContext::GetTextContent() const
{
    Reference < XTextContent > xTxtCntnt;
    SvXMLImportContext *pContext = m_xImplContext.get();
    XMLTextFrameContext_Impl *pImpl = dynamic_cast< XMLTextFrameContext_Impl* >( pContext );
    if( pImpl )
        xTxtCntnt.set( pImpl->GetPropSet(), UNO_QUERY );

    return xTxtCntnt;
}

Reference < XShape > XMLTextFrameContext::GetShape() const
{
    Reference < XShape > xShape;
    SvXMLImportContext* pContext = m_xImplContext.get();
    SvXMLShapeContext* pImpl = dynamic_cast<SvXMLShapeContext*>( pContext  );
    if ( pImpl )
    {
        xShape = pImpl->getShape();
    }

    return xShape;
}

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