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

#include <sal/config.h>

#include <o3tl/any.hxx>
#include <xmloff/unointerfacetouniqueidentifiermapper.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/types.h>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/container/XEnumeration.hpp>
#include <com/sun/star/container/XIndexReplace.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XMultiPropertySet.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>
#include <com/sun/star/beans/UnknownPropertyException.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/text/XTextSectionsSupplier.hpp>
#include <com/sun/star/text/XTextTablesSupplier.hpp>
#include <com/sun/star/text/XNumberingRulesSupplier.hpp>
#include <com/sun/star/text/XChapterNumberingSupplier.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/text/XTextContent.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/text/XTextFrame.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/text/SizeType.hpp>
#include <com/sun/star/text/HoriOrientation.hpp>
#include <com/sun/star/text/VertOrientation.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XTextFramesSupplier.hpp>
#include <com/sun/star/text/XTextGraphicObjectsSupplier.hpp>
#include <com/sun/star/text/XTextEmbeddedObjectsSupplier.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/document/XEventsSupplier.hpp>
#include <com/sun/star/document/XRedlinesSupplier.hpp>
#include <com/sun/star/text/XFormField.hpp>
#include <com/sun/star/text/XTextSection.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/style/XAutoStylesSupplier.hpp>
#include <com/sun/star/style/XAutoStyleFamily.hpp>
#include <com/sun/star/text/XTextFieldsSupplier.hpp>
#include <com/sun/star/drawing/XControlShape.hpp>

#include <sax/tools/converter.hxx>

#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmlaustp.hxx>
#include <xmloff/families.hxx>
#include "txtexppr.hxx"
#include <xmloff/xmluconv.hxx>
#include "XMLAnchorTypePropHdl.hxx"
#include <xexptran.hxx>
#include <xmloff/ProgressBarHelper.hxx>
#include <xmloff/namespacemap.hxx>
#include <xmloff/xmlexp.hxx>
#include <txtflde.hxx>
#include <xmloff/txtprmap.hxx>
#include <XMLImageMapExport.hxx>
#include "XMLTextNumRuleInfo.hxx"
#include <xmloff/XMLTextListAutoStylePool.hxx>
#include <xmloff/txtparae.hxx>
#include "XMLSectionExport.hxx"
#include "XMLIndexMarkExport.hxx"
#include <xmloff/XMLEventExport.hxx>
#include "XMLRedlineExport.hxx"
#include <MultiPropertySetHelper.hxx>
#include <xmloff/formlayerexport.hxx>
#include "XMLTextCharStyleNamesElementExport.hxx"
#include <xmloff/odffields.hxx>
#include <xmloff/maptype.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/embed/XTransactedObject.hpp>
#include <com/sun/star/document/XStorageBasedDocument.hpp>
#include <txtlists.hxx>
#include <com/sun/star/rdf/XMetadatable.hpp>
#include <list>
#include <unordered_map>
#include <memory>
#include <vector>
#include <algorithm>
#include <iterator>
#include <officecfg/Office/Common.hxx>
#include <o3tl/safeint.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/sequenceashashmap.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace cpo::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::style;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::document;
using namespace ::com::sun::star::graphic;
using namespace ::xmloff;
using namespace ::xmloff::token;

// Implement Title/Description Elements UI (#i73249#)
constexpr OUString gsTitle(u"Title"_ustr);
constexpr OUString gsDescription(u"Description"_ustr);
constexpr OUString gsAnchorPageNo(u"AnchorPageNo"_ustr);
constexpr OUString gsAnchorType(u"AnchorType"_ustr);
constexpr OUString gsBookmark(u"Bookmark"_ustr);
constexpr OUString gsChainNextName(u"ChainNextName"_ustr);
constexpr OUString gsContourPolyPolygon(u"ContourPolyPolygon"_ustr);
constexpr OUString gsDocumentIndexMark(u"DocumentIndexMark"_ustr);
constexpr OUString gsFrame(u"Frame"_ustr);
constexpr OUString gsGraphicFilter(u"GraphicFilter"_ustr);
constexpr OUString gsGraphicRotation(u"GraphicRotation"_ustr);
constexpr OUString gsHeight(u"Height"_ustr);
constexpr OUString gsHoriOrient(u"HoriOrient"_ustr);
constexpr OUString gsHoriOrientPosition(u"HoriOrientPosition"_ustr);
constexpr OUString gsHyperLinkName(u"HyperLinkName"_ustr);
constexpr OUString gsHyperLinkTarget(u"HyperLinkTarget"_ustr);
constexpr OUString gsHyperLinkURL(u"HyperLinkURL"_ustr);
constexpr OUString gsIsAutomaticContour(u"IsAutomaticContour"_ustr);
constexpr OUString gsIsCollapsed(u"IsCollapsed"_ustr);
constexpr OUString gsIsPixelContour(u"IsPixelContour"_ustr);
constexpr OUString gsIsStart(u"IsStart"_ustr);
constexpr OUString gsIsSyncHeightToWidth(u"IsSyncHeightToWidth"_ustr);
constexpr OUString gsIsSyncWidthToHeight(u"IsSyncWidthToHeight"_ustr);
constexpr OUString gsNumberingRules(u"NumberingRules"_ustr);
constexpr OUString gsParaConditionalStyleName(u"ParaConditionalStyleName"_ustr);
constexpr OUString gsParagraphService(u"com.sun.star.text.Paragraph"_ustr);
constexpr OUString gsRedline(u"Redline"_ustr);
constexpr OUString gsReferenceMark(u"ReferenceMark"_ustr);
constexpr OUString gsRelativeHeight(u"RelativeHeight"_ustr);
constexpr OUString gsRelativeWidth(u"RelativeWidth"_ustr);
constexpr OUString gsRuby(u"Ruby"_ustr);
constexpr OUString gsRubyCharStyleName(u"RubyCharStyleName"_ustr);
constexpr OUString gsRubyText(u"RubyText"_ustr);
constexpr OUString gsServerMap(u"ServerMap"_ustr);
constexpr OUString gsShapeService(u"com.sun.star.drawing.Shape"_ustr);
constexpr OUString gsSizeType(u"SizeType"_ustr);
constexpr OUString gsSoftPageBreak(u"SoftPageBreak"_ustr);
constexpr OUString gsTableService(u"com.sun.star.text.TextTable"_ustr);
constexpr OUString gsText(u"Text"_ustr);
constexpr OUString gsTextContentService(u"com.sun.star.text.TextContent"_ustr);
constexpr OUString gsTextEmbeddedService(u"com.sun.star.text.TextEmbeddedObject"_ustr);
constexpr OUString gsTextField(u"TextField"_ustr);
constexpr OUString gsTextFieldService(u"com.sun.star.text.TextField"_ustr);
constexpr OUString gsTextFrameService(u"com.sun.star.text.TextFrame"_ustr);
constexpr OUString gsTextGraphicService(u"com.sun.star.text.TextGraphicObject"_ustr);
constexpr OUString gsTextPortionType(u"TextPortionType"_ustr);
constexpr OUString gsUnvisitedCharStyleName(u"UnvisitedCharStyleName"_ustr);
constexpr OUString gsVertOrient(u"VertOrient"_ustr);
constexpr OUString gsVertOrientPosition(u"VertOrientPosition"_ustr);
constexpr OUString gsVisitedCharStyleName(u"VisitedCharStyleName"_ustr);
constexpr OUString gsWidth(u"Width"_ustr);
constexpr OUString gsWidthType( u"WidthType"_ustr  );
constexpr OUString gsTextFieldStart(u"TextFieldStart"_ustr);
constexpr OUString gsTextFieldSep(u"TextFieldSeparator"_ustr);
constexpr OUString gsTextFieldEnd(u"TextFieldEnd"_ustr);
constexpr OUString gsTextFieldStartEnd(u"TextFieldStartEnd"_ustr);
constexpr OUString gsPropertyCharStyleNames(u"CharStyleNames"_ustr);

namespace
{
    class TextContentSet
    {
        public:
            typedef std::list<Reference<XTextContent>> contents_t;
            typedef std::back_insert_iterator<contents_t> inserter_t;
            typedef contents_t::const_iterator const_iterator_t;

            inserter_t getInserter()
                { return std::back_insert_iterator<contents_t>(m_vTextContents); };
            const_iterator_t getBegin() const
                { return m_vTextContents.begin(); };
            const_iterator_t getEnd() const
                { return m_vTextContents.end(); };

        private:
            contents_t m_vTextContents;
    };

    struct FrameRefHash
    {
        size_t operator()(const Reference<XTextFrame>& rFrame) const
            { return sal::static_int_cast<size_t>(reinterpret_cast<sal_uIntPtr>(rFrame.get())); }
    };

    bool lcl_TextContentsUnfiltered(const Reference<XTextContent>&)
        { return true; };

    bool lcl_ShapeFilter(const Reference<XTextContent>& xTxtContent)
    {
        Reference<XShape> xShape(xTxtContent, UNO_QUERY);
        if(!xShape.is())
            return false;
        Reference<XServiceInfo> xServiceInfo(xTxtContent, UNO_QUERY);
        return !xServiceInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr) &&
               !xServiceInfo->supportsService(u"com.sun.star.text.TextGraphicObject"_ustr) &&
               !xServiceInfo->supportsService(u"com.sun.star.text.TextEmbeddedObject"_ustr);
    };

    class BoundFrames
    {
        public:
            typedef bool (*filter_t)(const Reference<XTextContent>&);
            BoundFrames(
                const Reference<XEnumerationAccess>& rEnumAccess,
                const filter_t& rFilter)
                : m_xEnumAccess(rEnumAccess)
            {
                Fill(rFilter);
            };
            BoundFrames()
                {};
            const TextContentSet& GetPageBoundContents() const
                { return m_vPageBounds; };
            const TextContentSet* GetFrameBoundContents(const Reference<XTextFrame>& rParentFrame) const
            {
                framebound_map_t::const_iterator it = m_vFrameBoundsOf.find(rParentFrame);
                if(it == m_vFrameBoundsOf.end())
                    return nullptr;
                return &(it->second);
            };
            Reference<XEnumeration> createEnumeration() const
            {
                if(!m_xEnumAccess.is())
                    return Reference<XEnumeration>();
                return m_xEnumAccess->createEnumeration();
            };

        private:
            typedef std::unordered_map<
                Reference<XTextFrame>,
                TextContentSet,
                FrameRefHash> framebound_map_t;
            TextContentSet m_vPageBounds;
            framebound_map_t m_vFrameBoundsOf;
            const Reference<XEnumerationAccess> m_xEnumAccess;
            void Fill(const filter_t& rFilter);
    };

    class FieldParamExporter
    {
        public:
            FieldParamExporter(SvXMLExport* const pExport, Reference<XNameContainer> const & xFieldParams)
                : m_pExport(pExport)
                , m_xFieldParams(xFieldParams)
                { };
            void Export();

        private:
            SvXMLExport* const m_pExport;
            const Reference<XNameContainer> m_xFieldParams;

            void ExportParameter(const OUString& sKey, const OUString& sValue);
    };

    struct HyperlinkData
    {
        OUString href, name, targetFrame, ustyleName, vstyleName;
        bool serverMap = false;
        css::uno::Reference<css::container::XNameReplace> events;

        HyperlinkData() = default;
        HyperlinkData(const css::uno::Reference<css::beans::XPropertySet>& rPropSet);

        bool operator==(const HyperlinkData&) const;

        bool addHyperlinkAttributes(SvXMLExport& rExport);
        void exportEvents(SvXMLExport& rExport);
    };

    HyperlinkData::HyperlinkData(const css::uno::Reference<css::beans::XPropertySet>& rPropSet)
    {
        const auto xPropSetInfo(rPropSet->getPropertySetInfo());

        if (xPropSetInfo->hasPropertyByName(gsTextPortionType))
        {
            // No hyperlink for Ruby portions; the hyperlink will be added for their inner Text
            if (OUString type;
                (rPropSet->getPropertyValue(gsTextPortionType) >>= type) && type == gsRuby)
                return;
        }

        auto hasDirectProperty
            = [&xPropSetInfo,
               xPropState = rPropSet.query<beans::XPropertyState>()](const OUString& prop)
        {
            return xPropSetInfo->hasPropertyByName(prop)
                   && (!xPropState.is()
                       || xPropState->getPropertyState(prop) == beans::PropertyState_DIRECT_VALUE);
        };

        if (hasDirectProperty(gsHyperLinkURL))
        {
            rPropSet->getPropertyValue(gsHyperLinkURL) >>= href;
        }

        if (href.isEmpty())
            return;

        if (hasDirectProperty(gsHyperLinkName))
        {
            rPropSet->getPropertyValue(gsHyperLinkName) >>= name;
        }

        if (hasDirectProperty(gsHyperLinkTarget))
        {
            rPropSet->getPropertyValue(gsHyperLinkTarget) >>= targetFrame;
        }

        if (hasDirectProperty(gsServerMap))
        {
            serverMap = *o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsServerMap));
        }

        if (hasDirectProperty(gsUnvisitedCharStyleName))
        {
            rPropSet->getPropertyValue(gsUnvisitedCharStyleName) >>= ustyleName;
        }

        if (hasDirectProperty(gsVisitedCharStyleName))
        {
            rPropSet->getPropertyValue(gsVisitedCharStyleName) >>= vstyleName;
        }

        static constexpr OUString sHyperLinkEvents(u"HyperLinkEvents"_ustr);
        if (xPropSetInfo->hasPropertyByName(sHyperLinkEvents))
        {
            events.set(rPropSet->getPropertyValue(sHyperLinkEvents), uno::UNO_QUERY);
        }
    }

    bool HyperlinkData::operator==(const HyperlinkData& rOther) const
    {
        if (href != rOther.href || name != rOther.name || targetFrame != rOther.targetFrame
            || ustyleName != rOther.ustyleName || vstyleName != rOther.vstyleName
            || serverMap != rOther.serverMap)
            return false;

        if (events == rOther.events)
            return true;
        if (!events || !rOther.events)
            return false;

        const cpo::uno::Sequence<OUString> aNames = events->getElementNames();
        if (aNames != rOther.events->getElementNames())
            return false;
        for (const auto& rName : aNames)
        {
            const cpo::uno::Any aAny = events->getByName(rName);
            const cpo::uno::Any aOtherAny = rOther.events->getByName(rName);
            if (aAny != aOtherAny)
                return false;
        }
        return true;
    }

    bool HyperlinkData::addHyperlinkAttributes(SvXMLExport& rExport)
    {
        if (href.isEmpty())
        {
            // End of hyperlink
            return false;
        }

        rExport.AddAttribute(XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE);
        rExport.AddAttribute(XML_NAMESPACE_XLINK, XML_HREF, rExport.GetRelativeReference(href));

        if (!name.isEmpty())
            rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_NAME, name);

        if (!targetFrame.isEmpty())
        {
            rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_TARGET_FRAME_NAME, targetFrame);
            enum XMLTokenEnum eTok = targetFrame == "_blank" ? XML_NEW : XML_REPLACE;
            rExport.AddAttribute(XML_NAMESPACE_XLINK, XML_SHOW, eTok);
        }

        if (serverMap)
            rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_SERVER_MAP, XML_TRUE);

        if (!ustyleName.isEmpty())
            rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                                 rExport.EncodeStyleName(ustyleName));

        if (!vstyleName.isEmpty())
            rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_VISITED_STYLE_NAME,
                                 rExport.EncodeStyleName(vstyleName));

        return true;
    }

    void HyperlinkData::exportEvents(SvXMLExport& rExport)
    {
        // export events (if supported)
        if (events)
            rExport.GetEventExport().Export(events, false);
    }
}

namespace xmloff
{
    class BoundFrameSets
    {
        public:
            explicit BoundFrameSets(const Reference<XInterface>& rModel);
            const BoundFrames* GetTexts() const
                { return m_pTexts.get(); };
            const BoundFrames* GetGraphics() const
                { return m_pGraphics.get(); };
            const BoundFrames* GetEmbeddeds() const
                { return m_pEmbeddeds.get(); };
            const BoundFrames* GetShapes() const
                { return m_pShapes.get(); };
        private:
            std::unique_ptr<BoundFrames> m_pTexts;
            std::unique_ptr<BoundFrames> m_pGraphics;
            std::unique_ptr<BoundFrames> m_pEmbeddeds;
            std::unique_ptr<BoundFrames> m_pShapes;
    };
}

#ifdef DBG_UTIL
static bool txtparae_bContainsIllegalCharacters = false;
#endif

// The following map shows which property values are required:

// property                     auto style pass     export

// ParaStyleName                if style exists     always
// ParaConditionalStyleName     if style exists     always
// NumberingRules               if style exists     always
// TextSection                  always              always
// ParaChapterNumberingLevel    never               always
// NumberingIsNumber            never               always

// The conclusion is that for auto styles the first three properties
// should be queried using a multi property set if, and only if, an
// auto style needs to be exported. TextSection should be queried by
// an individual call to getPropertyvalue, because this seems to be
// less expensive than querying the first three properties if they aren't
// required.

// For the export pass all properties can be queried using a multi property
// set.

constexpr OUString aParagraphPropertyNamesAuto[] =
{
    u"NumberingRules"_ustr,
    u"ParaConditionalStyleName"_ustr,
    u"ParaStyleName"_ustr
};

namespace {

enum eParagraphPropertyNamesEnumAuto
{
    NUMBERING_RULES_AUTO = 0,
    PARA_CONDITIONAL_STYLE_NAME_AUTO = 1,
    PARA_STYLE_NAME_AUTO = 2
};

}

constexpr OUString aParagraphPropertyNames[] =
{
    u"NumberingIsNumber"_ustr,
    u"NumberingStyleName"_ustr,
    u"OutlineLevel"_ustr,
    u"ParaConditionalStyleName"_ustr,
    u"ParaStyleName"_ustr,
    u"TextSection"_ustr,
    u"OutlineContentVisible"_ustr
};

namespace {

enum eParagraphPropertyNamesEnum
{
    NUMBERING_IS_NUMBER = 0,
    PARA_NUMBERING_STYLENAME = 1,
    PARA_OUTLINE_LEVEL=2,
    PARA_CONDITIONAL_STYLE_NAME = 3,
    PARA_STYLE_NAME = 4,
    TEXT_SECTION = 5,
    PARA_OUTLINE_CONTENT_VISIBLE = 6
};

}

void BoundFrames::Fill(const filter_t& rFilter)
{
    if(!m_xEnumAccess.is())
        return;
    const Reference< XEnumeration > xEnum = m_xEnumAccess->createEnumeration();
    if(!xEnum.is())
        return;
    static constexpr OUString our_sAnchorType(u"AnchorType"_ustr);
    static constexpr OUString our_sAnchorFrame(u"AnchorFrame"_ustr);
    while(xEnum->hasMoreElements())
    {
        Reference<XPropertySet> xPropSet(xEnum->nextElement(), UNO_QUERY);
        Reference<XTextContent> xTextContent(xPropSet, UNO_QUERY);
        if(!xPropSet.is() || !xTextContent.is())
            continue;
        TextContentAnchorType eAnchor;
        xPropSet->getPropertyValue(our_sAnchorType) >>= eAnchor;
        if(TextContentAnchorType_AT_PAGE != eAnchor && TextContentAnchorType_AT_FRAME != eAnchor)
            continue;
        if(!rFilter(xTextContent))
            continue;

        TextContentSet::inserter_t pInserter = m_vPageBounds.getInserter();
        if(TextContentAnchorType_AT_FRAME == eAnchor)
        {
            Reference<XTextFrame> xAnchorTxtFrame(
                xPropSet->getPropertyValue(our_sAnchorFrame),
                uno::UNO_QUERY);
            pInserter = m_vFrameBoundsOf[xAnchorTxtFrame].getInserter();
        }
        *pInserter++ = xTextContent;
    }
}

BoundFrameSets::BoundFrameSets(const Reference<XInterface>& rModel)
    : m_pTexts(new BoundFrames())
    , m_pGraphics(new BoundFrames())
    , m_pEmbeddeds(new BoundFrames())
    , m_pShapes(new BoundFrames())
{
    const Reference<XTextFramesSupplier> xTFS(rModel, UNO_QUERY);
    const Reference<XTextGraphicObjectsSupplier> xGOS(rModel, UNO_QUERY);
    const Reference<XTextEmbeddedObjectsSupplier> xEOS(rModel, UNO_QUERY);
    const Reference<XDrawPageSupplier> xDPS(rModel, UNO_QUERY);
    if(xTFS.is())
        m_pTexts.reset(new BoundFrames(
            Reference<XEnumerationAccess>(xTFS->getTextFrames(), UNO_QUERY),
            &lcl_TextContentsUnfiltered));
    if(xGOS.is())
        m_pGraphics.reset(new BoundFrames(
            Reference<XEnumerationAccess>(xGOS->getGraphicObjects(), UNO_QUERY),
            &lcl_TextContentsUnfiltered));
    if(xEOS.is())
        m_pEmbeddeds.reset(new BoundFrames(
            Reference<XEnumerationAccess>(xEOS->getEmbeddedObjects(), UNO_QUERY),
            &lcl_TextContentsUnfiltered));
    if(xDPS.is())
        m_pShapes.reset(new BoundFrames(
            Reference<XEnumerationAccess>(xDPS->getDrawPage(), UNO_QUERY),
            &lcl_ShapeFilter));
};

void FieldParamExporter::Export()
{
    const Type aStringType = ::cppu::UnoType<OUString>::get();
    const Type aBoolType = cppu::UnoType<bool>::get();
    const Type aSeqType = cppu::UnoType<Sequence<OUString>>::get();
    const Type aIntType = ::cppu::UnoType<sal_Int32>::get();
    const Sequence<OUString> vParameters(m_xFieldParams->getElementNames());
    for(const auto & rParameter : vParameters)
    {
        const Any aValue = m_xFieldParams->getByName(rParameter);
        const Type& aValueType = aValue.getValueType();
        if(aValueType == aStringType)
        {
            OUString sValue;
            aValue >>= sValue;
            ExportParameter(rParameter,sValue);

            if ( rParameter == ODF_OLE_PARAM )
            {
                // Save the OLE object
                Reference< embed::XStorage > xTargetStg = m_pExport->GetTargetStorage();
                if (xTargetStg.is()) {
                    Reference< embed::XStorage > xDstStg = xTargetStg->openStorageElement(
                        u"OLELinks"_ustr, embed::ElementModes::WRITE );

                    if ( !xDstStg->hasByName( sValue ) ) {
                        Reference< XStorageBasedDocument > xStgDoc (
                            m_pExport->GetModel( ), UNO_QUERY );
                        Reference< embed::XStorage > xDocStg = xStgDoc->getDocumentStorage();
                        Reference< embed::XStorage > xOleStg = xDocStg->openStorageElement(
                            u"OLELinks"_ustr, embed::ElementModes::READ );

                        xOleStg->copyElementTo( sValue, xDstStg, sValue );
                        Reference< embed::XTransactedObject > xTransact( xDstStg, UNO_QUERY );
                        if ( xTransact.is( ) )
                            xTransact->commit( );
                    }
                } else {
                    SAL_WARN("xmloff", "no target storage");
                }
            }
        }
        else if(aValueType == aBoolType)
        {
            bool bValue = false;
            aValue >>= bValue;
            ExportParameter(rParameter, OUString::boolean(bValue) );
        }
        else if(aValueType == aSeqType)
        {
            Sequence<OUString> vValue;
            aValue >>= vValue;
            for (const OUString& i : vValue)
            {
                ExportParameter(rParameter, i);
            }
        }
        else if(aValueType == aIntType)
        {
            sal_Int32 nValue = 0;
            aValue >>= nValue;
            ExportParameter(rParameter, OUString::number(nValue));
        }
    }
}

void FieldParamExporter::ExportParameter(const OUString& sKey, const OUString& sValue)
{
    m_pExport->AddAttribute(XML_NAMESPACE_FIELD, XML_NAME, sKey);
    m_pExport->AddAttribute(XML_NAMESPACE_FIELD, XML_VALUE, sValue);
    m_pExport->StartElement(XML_NAMESPACE_FIELD, XML_PARAM, false);
    m_pExport->EndElement(XML_NAMESPACE_FIELD, XML_PARAM, false);
}

void XMLTextParagraphExport::Add( XmlStyleFamily nFamily,
                                  const Reference < XPropertySet > & rPropSet,
                                  const std::span<const XMLPropertyState> aAddStates,
                                  bool bCheckParent )
{
    rtl::Reference < SvXMLExportPropertyMapper > xPropMapper;
    switch( nFamily )
    {
    case XmlStyleFamily::TEXT_PARAGRAPH:
        xPropMapper = GetParaPropMapper();
        break;
    case XmlStyleFamily::TEXT_TEXT:
        xPropMapper = GetTextPropMapper();
        break;
    case XmlStyleFamily::TEXT_FRAME:
        xPropMapper = GetAutoFramePropMapper();
        break;
    case XmlStyleFamily::TEXT_SECTION:
        xPropMapper = GetSectionPropMapper();
        break;
    case XmlStyleFamily::TEXT_RUBY:
        xPropMapper = GetRubyPropMapper();
        break;
    default: break;
    }
    SAL_WARN_IF( !xPropMapper.is(), "xmloff", "There is the property mapper?" );

    std::vector< XMLPropertyState > aPropStates =
            xPropMapper->Filter(GetExport(), rPropSet);

    aPropStates.insert( aPropStates.end(), aAddStates.begin(), aAddStates.end() );

    if( aPropStates.empty() )
        return;

    Reference< XPropertySetInfo > xPropSetInfo(rPropSet->getPropertySetInfo());
    OUString sParent, sCondParent;
    switch( nFamily )
    {
    case XmlStyleFamily::TEXT_PARAGRAPH:
        if( xPropSetInfo->hasPropertyByName( gsParaStyleName ) )
        {
            rPropSet->getPropertyValue( gsParaStyleName ) >>= sParent;
        }
        if( xPropSetInfo->hasPropertyByName( gsParaConditionalStyleName ) )
        {
            rPropSet->getPropertyValue( gsParaConditionalStyleName ) >>= sCondParent;
        }
        if( xPropSetInfo->hasPropertyByName( gsNumberingRules ) )
        {
            Reference < XIndexReplace > xNumRule(rPropSet->getPropertyValue( gsNumberingRules ), uno::UNO_QUERY);
            if( xNumRule.is() && xNumRule->getCount() )
            {
                Reference < XNamed > xNamed( xNumRule, UNO_QUERY );
                OUString sName;
                if( xNamed.is() )
                    sName = xNamed->getName();
                bool bAdd = sName.isEmpty();
                if( !bAdd )
                {
                    Reference < XPropertySet > xNumPropSet( xNumRule,
                                                            UNO_QUERY );
                    if( xNumPropSet.is() &&
                        xNumPropSet->getPropertySetInfo()
                                   ->hasPropertyByName( u"IsAutomatic"_ustr ) )
                    {
                        bAdd = *o3tl::doAccess<bool>(xNumPropSet->getPropertyValue( u"IsAutomatic"_ustr ));
                        // Check on outline style (#i73361#)
                        if ( bAdd &&
                             xNumPropSet->getPropertySetInfo()
                                       ->hasPropertyByName( u"NumberingIsOutline"_ustr ) )
                        {
                            bAdd = !(*o3tl::doAccess<bool>(xNumPropSet->getPropertyValue( u"NumberingIsOutline"_ustr )));
                        }
                    }
                    else
                    {
                        bAdd = true;
                    }
                }
                if( bAdd )
                    maListAutoPool.Add( xNumRule );
            }
        }
        break;
    case XmlStyleFamily::TEXT_TEXT:
        {
            if (bCheckParent && xPropSetInfo->hasPropertyByName(gsCharStyleName))
            {
                rPropSet->getPropertyValue(gsCharStyleName) >>= sParent;
            }

            // Get parent and remove hyperlinks (they aren't of interest)
            rtl::Reference< XMLPropertySetMapper > xPM(xPropMapper->getPropertySetMapper());
            sal_uInt16 nIgnoreProps = 0;
            for( ::std::vector< XMLPropertyState >::iterator i(aPropStates.begin());
                  nIgnoreProps < 2 && i != aPropStates.end(); )
            {
                if( i->mnIndex == -1 )
                {
                    ++i;
                    continue;
                }

                switch( xPM->GetEntryContextId(i->mnIndex) )
                {
                case CTF_CHAR_STYLE_NAME:
                case CTF_HYPERLINK_URL:
                    i->mnIndex = -1;
                    nIgnoreProps++;
                    i = aPropStates.erase( i );
                    break;
                default:
                    ++i;
                    break;
                }
            }
        }
        break;
    case XmlStyleFamily::TEXT_FRAME:
        if( xPropSetInfo->hasPropertyByName( gsFrameStyleName ) )
        {
            rPropSet->getPropertyValue( gsFrameStyleName ) >>= sParent;
        }
        break;
    case XmlStyleFamily::TEXT_SECTION:
    case XmlStyleFamily::TEXT_RUBY:
        ; // section styles have no parents
        break;
    default: break;
    }
    if (aPropStates.size()) // could change after the previous check
    {
        GetAutoStylePool().Add( nFamily, sParent, std::vector(aPropStates), /*bDontSeek*/false );
        if( !sCondParent.isEmpty() && sParent != sCondParent )
            GetAutoStylePool().Add( nFamily, sCondParent, std::move(aPropStates) );
    }
}

static bool lcl_validPropState( const XMLPropertyState& rState )
{
    return rState.mnIndex != -1;
}

void XMLTextParagraphExport::Add( XmlStyleFamily nFamily,
                                  MultiPropertySetHelper& rPropSetHelper,
                                  const Reference < XPropertySet > & rPropSet)
{
    rtl::Reference < SvXMLExportPropertyMapper > xPropMapper;
    switch( nFamily )
    {
    case XmlStyleFamily::TEXT_PARAGRAPH:
        xPropMapper = GetParaPropMapper();
        break;
    default: break;
    }
    SAL_WARN_IF( !xPropMapper.is(), "xmloff", "There is the property mapper?" );

    std::vector<XMLPropertyState> aPropStates(xPropMapper->Filter(GetExport(), rPropSet));

    if( rPropSetHelper.hasProperty( NUMBERING_RULES_AUTO ) )
    {
        Reference < XIndexReplace > xNumRule(rPropSetHelper.getValue( NUMBERING_RULES_AUTO,
            rPropSet, true ), uno::UNO_QUERY);
        if( xNumRule.is() && xNumRule->getCount() )
        {
            Reference < XNamed > xNamed( xNumRule, UNO_QUERY );
            OUString sName;
            if( xNamed.is() )
                sName = xNamed->getName();
            bool bAdd = sName.isEmpty();
            if( !bAdd )
            {
                Reference < XPropertySet > xNumPropSet( xNumRule,
                                                        UNO_QUERY );
                if( xNumPropSet.is() &&
                    xNumPropSet->getPropertySetInfo()
                               ->hasPropertyByName( u"IsAutomatic"_ustr ) )
                {
                    bAdd = *o3tl::doAccess<bool>(xNumPropSet->getPropertyValue( u"IsAutomatic"_ustr ));
                    // Check on outline style (#i73361#)
                    if ( bAdd &&
                         xNumPropSet->getPropertySetInfo()
                                   ->hasPropertyByName( u"NumberingIsOutline"_ustr ) )
                    {
                        bAdd = !(*o3tl::doAccess<bool>(xNumPropSet->getPropertyValue( u"NumberingIsOutline"_ustr )));
                    }
                }
                else
                {
                    bAdd = true;
                }
            }
            if( bAdd )
                maListAutoPool.Add( xNumRule );
        }
    }

    if( aPropStates.empty() )
        return;

    OUString sParent, sCondParent;
    switch( nFamily )
    {
    case XmlStyleFamily::TEXT_PARAGRAPH:
        if( rPropSetHelper.hasProperty( PARA_STYLE_NAME_AUTO ) )
        {
            rPropSetHelper.getValue( PARA_STYLE_NAME_AUTO, rPropSet,
                                            true ) >>= sParent;
        }
        if( rPropSetHelper.hasProperty( PARA_CONDITIONAL_STYLE_NAME_AUTO ) )
        {
            rPropSetHelper.getValue( PARA_CONDITIONAL_STYLE_NAME_AUTO,
                                             rPropSet, true ) >>= sCondParent;
        }

        break;
    default: break;
    }

    if( std::any_of( aPropStates.begin(), aPropStates.end(), lcl_validPropState ) )
    {
        GetAutoStylePool().Add( nFamily, sParent, std::vector(aPropStates) );
        if( !sCondParent.isEmpty() && sParent != sCondParent )
            GetAutoStylePool().Add( nFamily, sCondParent, std::move(aPropStates) );
    }
}

OUString XMLTextParagraphExport::Find(
        XmlStyleFamily nFamily,
        const Reference < XPropertySet > & rPropSet,
        const OUString& rParent,
        const std::span<const XMLPropertyState> aAddStates) const
{
    OUString sName( rParent );
    rtl::Reference < SvXMLExportPropertyMapper > xPropMapper;
    switch( nFamily )
    {
    case XmlStyleFamily::TEXT_PARAGRAPH:
        xPropMapper = GetParaPropMapper();
        break;
    case XmlStyleFamily::TEXT_FRAME:
        xPropMapper = GetAutoFramePropMapper();
        break;
    case XmlStyleFamily::TEXT_SECTION:
        xPropMapper = GetSectionPropMapper();
        break;
    case XmlStyleFamily::TEXT_RUBY:
        xPropMapper = GetRubyPropMapper();
        break;
    default: break;
    }
    SAL_WARN_IF( !xPropMapper.is(), "xmloff", "There is the property mapper?" );
    if( !xPropMapper.is() )
        return sName;
    std::vector<XMLPropertyState> aPropStates(xPropMapper->Filter(GetExport(), rPropSet));
    aPropStates.insert( aPropStates.end(), aAddStates.begin(), aAddStates.end() );
    if( std::any_of( aPropStates.begin(), aPropStates.end(), lcl_validPropState ) )
        sName = GetAutoStylePool().Find( nFamily, sName, aPropStates );

    return sName;
}

OUString XMLTextParagraphExport::FindTextStyle(
           const Reference < XPropertySet > & rPropSet,
        bool& rbHasCharStyle,
        bool& rbHasAutoStyle,
        const XMLPropertyState** ppAddStates,
        const OUString* pParentName) const
{
    rtl::Reference < SvXMLExportPropertyMapper > xPropMapper(GetTextPropMapper());
    std::vector<XMLPropertyState> aPropStates(xPropMapper->Filter(GetExport(), rPropSet));

    // Get parent and remove hyperlinks (they aren't of interest)
    OUString sName;
    rbHasCharStyle = rbHasAutoStyle = false;
    sal_uInt16 nIgnoreProps = 0;
    rtl::Reference< XMLPropertySetMapper > xPM(xPropMapper->getPropertySetMapper());
    ::std::vector< XMLPropertyState >::iterator aFirstDel = aPropStates.end();
    ::std::vector< XMLPropertyState >::iterator aSecondDel = aPropStates.end();

    for( ::std::vector< XMLPropertyState >::iterator
            i = aPropStates.begin();
         nIgnoreProps < 2 && i != aPropStates.end();
         ++i )
    {
        if( i->mnIndex == -1 )
            continue;

        switch( xPM->GetEntryContextId(i->mnIndex) )
        {
        case CTF_CHAR_STYLE_NAME:
            i->maValue >>= sName;
            i->mnIndex = -1;
            rbHasCharStyle = !sName.isEmpty();
            if( nIgnoreProps )
                aSecondDel = i;
            else
                aFirstDel = i;
            nIgnoreProps++;
            break;
        case CTF_HYPERLINK_URL:
            i->mnIndex = -1;
            if( nIgnoreProps )
                aSecondDel = i;
            else
                aFirstDel = i;
            nIgnoreProps++;
            break;
        }
    }
    if( ppAddStates )
    {
        while( *ppAddStates )
        {
            aPropStates.push_back( **ppAddStates );
            ppAddStates++;
        }
    }
    if (aPropStates.size() - nIgnoreProps)
    {
        // erase the character style, otherwise the autostyle cannot be found!
        // erase the hyperlink, otherwise the autostyle cannot be found!
        if ( nIgnoreProps )
        {
            // If two elements of a vector have to be deleted,
            // we should delete the second one first.
            if( --nIgnoreProps )
                aPropStates.erase( aSecondDel );
            aPropStates.erase( aFirstDel );
        }
        OUString aParentName;
        if (pParentName)
        {
            // Format redlines can have an autostyle with a parent.
            aParentName = *pParentName;
        }
        sName = GetAutoStylePool().Find(
            XmlStyleFamily::TEXT_TEXT,
            aParentName,
            aPropStates );
        rbHasAutoStyle = true;
    }

    return sName;
}

// adjustments to support lists independent from list style
void XMLTextParagraphExport::exportListChange(
        const XMLTextNumRuleInfo& rPrevInfo,
        const XMLTextNumRuleInfo& rNextInfo )
{
    // end a list
    if ( rPrevInfo.GetLevel() > 0 )
    {
        sal_uInt32 nListLevelsToBeClosed = 0; // unsigned larger type to safely multiply and compare
        if ( !rNextInfo.BelongsToSameList( rPrevInfo ) ||
             rNextInfo.GetLevel() <= 0 )
        {
            // close complete previous list
            nListLevelsToBeClosed = rPrevInfo.GetLevel();
        }
        else if ( rPrevInfo.GetLevel() > rNextInfo.GetLevel() )
        {
            // close corresponding sub lists
            nListLevelsToBeClosed = rPrevInfo.GetLevel() - rNextInfo.GetLevel();
        }

        if ( nListLevelsToBeClosed > 0 &&
             maListElements.size() >= 2 * nListLevelsToBeClosed )
        {
            do {
                for(size_t j = 0; j < 2; ++j)
                {
                    OUString aElem(maListElements.back());
                    maListElements.pop_back();
                    GetExport().EndElement(aElem, true);
                }

                // remove closed list from list stack
                mpTextListsHelper->PopListFromStack();

                --nListLevelsToBeClosed;
            } while ( nListLevelsToBeClosed > 0 );
        }
    }

    // start a new list
    if ( rNextInfo.GetLevel() > 0 )
    {
        bool bRootListToBeStarted = false;
        sal_Int16 nListLevelsToBeOpened = 0;
        if ( !rPrevInfo.BelongsToSameList( rNextInfo ) ||
             rPrevInfo.GetLevel() <= 0 )
        {
            // new root list
            bRootListToBeStarted = true;
            nListLevelsToBeOpened = rNextInfo.GetLevel();
        }
        else if ( rNextInfo.GetLevel() > rPrevInfo.GetLevel() )
        {
            // open corresponding sub lists
            nListLevelsToBeOpened = rNextInfo.GetLevel() - rPrevInfo.GetLevel();
        }

        if ( nListLevelsToBeOpened > 0 )
        {
            const OUString& sListStyleName( rNextInfo.GetNumRulesName() );
            // Currently only the text documents support <ListId>.
            // Thus, for other document types <sListId> is empty.
            const OUString& sListId( rNextInfo.GetListId() );
            bool bExportListStyle( true );
            bool bRestartNumberingAtContinuedList( false );
            sal_Int32 nRestartValueForContinuedList( -1 );
            bool bContinueingPreviousSubList = !bRootListToBeStarted &&
                                               rNextInfo.IsContinueingPreviousSubTree();
            do {
                GetExport().CheckAttrList();

                if ( bRootListToBeStarted )
                {
                    if ( !mpTextListsHelper->IsListProcessed( sListId ) )
                    {
                        if ( ExportListId() &&
                             !sListId.isEmpty() && !rNextInfo.IsListIdDefault() )
                        {
                            /* Property text:id at element <text:list> has to be
                               replaced by property xml:id (#i92221#)
                            */
                            GetExport().AddAttribute( XML_NAMESPACE_XML,
                                                      XML_ID,
                                                      sListId );
                        }
                        mpTextListsHelper->KeepListAsProcessed( sListId,
                                                                sListStyleName,
                                                                OUString() );
                    }
                    else
                    {
                        const OUString sNewListId(
                                        mpTextListsHelper->GenerateNewListId() );
                        if ( ExportListId() &&
                             !sListId.isEmpty() && !rNextInfo.IsListIdDefault() )
                        {
                            /* Property text:id at element <text:list> has to be
                               replaced by property xml:id (#i92221#)
                            */
                            GetExport().AddAttribute( XML_NAMESPACE_XML,
                                                      XML_ID,
                                                      sNewListId );
                        }

                        const OUString sContinueListId =
                            mpTextListsHelper->GetLastContinuingListId( sListId );
                        // store that list with list id <sNewListId> is last list,
                        // which has continued list with list id <sListId>
                        mpTextListsHelper->StoreLastContinuingList( sListId,
                                                                    sNewListId );
                        if ( sListStyleName ==
                                mpTextListsHelper->GetListStyleOfLastProcessedList() &&
                             // Inconsistent behavior regarding lists (#i92811#)
                             sContinueListId ==
                                mpTextListsHelper->GetLastProcessedListId() )
                        {
                            GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                                      XML_CONTINUE_NUMBERING,
                                                      XML_TRUE );
                        }
                        else
                        {
                            if ( ExportListId() &&
                                 !sListId.isEmpty() )
                            {
                                GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                                          XML_CONTINUE_LIST,
                                                          sContinueListId );
                            }
                        }

                        if ( rNextInfo.IsRestart() &&
                             ( nListLevelsToBeOpened != 1 ||
                               !rNextInfo.HasStartValue() ) )
                        {
                            bRestartNumberingAtContinuedList = true;
                            nRestartValueForContinuedList =
                                        rNextInfo.GetListLevelStartValue();
                        }

                        mpTextListsHelper->KeepListAsProcessed( sNewListId,
                                                                sListStyleName,
                                                                sContinueListId );
                    }

                    GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                            GetExport().EncodeStyleName( sListStyleName ) );
                    bExportListStyle = false;

                    bRootListToBeStarted = false;
                }
                else if ( bExportListStyle &&
                          !mpTextListsHelper->EqualsToTopListStyleOnStack( sListStyleName ) )
                {
                    GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                            GetExport().EncodeStyleName( sListStyleName ) );
                    bExportListStyle = false;

                }
                else
                {
                    // rhbz#746174: also export list restart for non root list
                    if (rNextInfo.IsRestart() && !rNextInfo.HasStartValue())
                    {
                        bRestartNumberingAtContinuedList = true;
                        nRestartValueForContinuedList =
                                        rNextInfo.GetListLevelStartValue();
                    }
                }

                if ( bContinueingPreviousSubList )
                {
                    GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                              XML_CONTINUE_NUMBERING, XML_TRUE );
                    bContinueingPreviousSubList = false;
                }

                enum XMLTokenEnum eLName = XML_LIST;

                OUString aElem(GetExport().GetNamespaceMap().GetQNameByKey(
                                            XML_NAMESPACE_TEXT,
                                            GetXMLToken(eLName) ) );
                GetExport().IgnorableWhitespace();
                GetExport().StartElement(aElem, false);

                maListElements.push_back(aElem);

                mpTextListsHelper->PushListOnStack( sListId,
                                                    sListStyleName );

                // <text:list-header> or <text:list-item>
                GetExport().CheckAttrList();

                /* Export start value at correct list item (#i97309#) */
                if ( nListLevelsToBeOpened == 1 )
                {
                    if ( rNextInfo.HasStartValue() )
                    {
                        OUString aTmp = OUString::number( static_cast<sal_Int32>(rNextInfo.GetStartValue()) );
                        GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_START_VALUE,
                                      aTmp );
                    }
                    else if (bRestartNumberingAtContinuedList)
                    {
                        GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                                  XML_START_VALUE,
                                                  OUString::number(nRestartValueForContinuedList) );
                        bRestartNumberingAtContinuedList = false;
                    }
                }

                eLName = ( rNextInfo.IsNumbered() || nListLevelsToBeOpened > 1 )
                         ? XML_LIST_ITEM
                         : XML_LIST_HEADER;
                aElem = GetExport().GetNamespaceMap().GetQNameByKey(
                                            XML_NAMESPACE_TEXT,
                                            GetXMLToken(eLName) );
                GetExport().IgnorableWhitespace();
                GetExport().StartElement(aElem, false);
                maListElements.push_back(aElem);

                // export of <text:number> element for last opened <text:list-item>, if requested
                if ( GetExport().exportTextNumberElement() &&
                     eLName == XML_LIST_ITEM && nListLevelsToBeOpened == 1 && // last iteration --> last opened <text:list-item>
                     !rNextInfo.ListLabelString().isEmpty() )
                {
                    const OUString aTextNumberElem =
                            GetExport().GetNamespaceMap().GetQNameByKey(
                                      XML_NAMESPACE_TEXT,
                                      GetXMLToken(XML_NUMBER) );
                    GetExport().IgnorableWhitespace();
                    GetExport().StartElement( aTextNumberElem, false );
                    GetExport().Characters( rNextInfo.ListLabelString() );
                    GetExport().EndElement( aTextNumberElem, true );
                }
                --nListLevelsToBeOpened;
            } while ( nListLevelsToBeOpened > 0 );
        }
    }

    bool bEndElement = false;

    if ( rNextInfo.GetLevel() > 0 &&
         rNextInfo.IsNumbered() &&
         rPrevInfo.BelongsToSameList( rNextInfo ) &&
         rPrevInfo.GetLevel() >= rNextInfo.GetLevel() )
    {
        assert(maListElements.size() >= 2 && "list elements missing");
        bEndElement = maListElements.size() >= 2;
    }

    if (!bEndElement)
        return;

    // close previous list-item
    GetExport().EndElement(maListElements.back(), true );
    maListElements.pop_back();

    // Only for sub lists (#i103745#)
    if ( rNextInfo.IsRestart() && !rNextInfo.HasStartValue() &&
         rNextInfo.GetLevel() != 1 )
    {
        // start new sub list respectively list on same list level
        GetExport().EndElement(maListElements.back(), true );
        GetExport().IgnorableWhitespace();
        GetExport().StartElement(maListElements.back(), false);
    }

    // open new list-item
    GetExport().CheckAttrList();
    if( rNextInfo.HasStartValue() )
    {
        OUString aTmp = OUString::number( static_cast<sal_Int32>(rNextInfo.GetStartValue()) );
        GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_START_VALUE, aTmp );
    }
    // Handle restart without start value on list level 1 (#i103745#)
    else if ( rNextInfo.IsRestart() && /*!rNextInfo.HasStartValue() &&*/
              rNextInfo.GetLevel() == 1 )
    {
        OUString aTmp = OUString::number( static_cast<sal_Int32>(rNextInfo.GetListLevelStartValue()) );
        GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_START_VALUE, aTmp );
    }
    if ( ( GetExport().getExportFlags() & SvXMLExportFlags::OASIS ) &&
        GetExport().getSaneDefaultVersion() >= SvtSaveOptions::ODFSVER_012)
    {
        const OUString& sListStyleName( rNextInfo.GetNumRulesName() );
        if ( !mpTextListsHelper->EqualsToTopListStyleOnStack( sListStyleName ) )
        {
            GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                      XML_STYLE_OVERRIDE,
                                      GetExport().EncodeStyleName( sListStyleName ) );
        }
    }
    OUString aElem( GetExport().GetNamespaceMap().GetQNameByKey(
                            XML_NAMESPACE_TEXT,
                            GetXMLToken(XML_LIST_ITEM) ) );
    GetExport().IgnorableWhitespace();
    GetExport().StartElement(aElem, false );
    maListElements.push_back(aElem);

    // export of <text:number> element for <text:list-item>, if requested
    if ( GetExport().exportTextNumberElement() &&
         !rNextInfo.ListLabelString().isEmpty() )
    {
        const OUString aTextNumberElem =
                GetExport().GetNamespaceMap().GetQNameByKey(
                          XML_NAMESPACE_TEXT,
                          GetXMLToken(XML_NUMBER) );
        GetExport().IgnorableWhitespace();
        GetExport().StartElement( aTextNumberElem, false );
        GetExport().Characters( rNextInfo.ListLabelString() );
        GetExport().EndElement( aTextNumberElem, true );
    }

}

struct XMLTextParagraphExport::Impl
{
    typedef ::std::map<Reference<XFormField>, sal_Int32> FieldMarkMap_t;
    FieldMarkMap_t m_FieldMarkMap;

    explicit Impl() {}
    sal_Int32 AddFieldMarkStart(Reference<XFormField> const& i_xFieldMark)
    {
        assert(m_FieldMarkMap.find(i_xFieldMark) == m_FieldMarkMap.end());
        sal_Int32 const ret(m_FieldMarkMap.size());
        m_FieldMarkMap.insert(::std::make_pair(i_xFieldMark, ret));
        return ret;
    }
    sal_Int32 GetFieldMarkIndex(Reference<XFormField> const& i_xFieldMark)
    {
        FieldMarkMap_t::const_iterator const it(
                m_FieldMarkMap.find(i_xFieldMark));
        // rely on SwXFieldmark::CreateXFieldmark returning the same instance
        // because the Reference in m_FieldMarkMap will keep it alive
        assert(it != m_FieldMarkMap.end());
        return it->second;
    }
};

struct XMLTextParagraphExport::DocumentListNodes
{
    struct NodeData
    {
        std::ptrdiff_t order;
        sal_Int32 index; // see SwNode::GetIndex and SwNodeOffset
        sal_uInt64 style_id; // actually a pointer to NumRule
        OUString list_id;
    };
    std::vector<NodeData> docListNodes;
    DocumentListNodes(const css::uno::Reference<css::frame::XModel>& xModel,
                      const std::vector<sal_Int32>& aDocumentNodeOrder)
    {
        // Sequence of nodes, each of them represented by three-element sequence,
        // corresponding to NodeData members
        cpo::uno::Sequence<cpo::uno::Sequence<cpo::uno::Any>> nodes;
        if (auto xPropSet = xModel.query<css::beans::XPropertySet>())
        {
            try
            {
                // See SwXTextDocument::getPropertyValue
                xPropSet->getPropertyValue(u"ODFExport_ListNodes"_ustr) >>= nodes;
            }
            catch (css::beans::UnknownPropertyException&)
            {
                // That's absolutely fine!
            }
        }

        docListNodes.reserve(nodes.getLength());
        for (const auto& node : nodes)
        {
            assert(node.getLength() == 3);
            sal_Int32 nodeIndex = node[0].get<sal_Int32>();
            auto nodeOrder = std::distance(
                aDocumentNodeOrder.begin(),
                std::find(aDocumentNodeOrder.begin(), aDocumentNodeOrder.end(), nodeIndex));
            docListNodes.push_back({ .order = nodeOrder,
                                     .index = nodeIndex,
                                     .style_id = node[1].get<sal_uInt64>(),
                                     .list_id = node[2].get<OUString>() });
        }

        std::sort(docListNodes.begin(), docListNodes.end(),
                  [](const NodeData& lhs, const NodeData& rhs) { return lhs.order < rhs.order; });
    }
    bool ShouldSkipListId(const Reference<XTextContent>& xTextContent) const
    {
        if (docListNodes.empty())
            return false;

        if (auto xPropSet = xTextContent.query<css::beans::XPropertySet>())
        {
            sal_Int32 index = 0;
            try
            {
                // See SwXParagraph::Impl::GetPropertyValues_Impl
                xPropSet->getPropertyValue(u"ODFExport_NodeIndex"_ustr) >>= index;
            }
            catch (css::beans::UnknownPropertyException&)
            {
                // That's absolutely fine!
                return false;
            }

            auto it = std::find_if(docListNodes.begin(), docListNodes.end(),
                                   [index](const NodeData& el) { return el.index == index; });
            if (it == docListNodes.end())
                return false;

            // We need to write the id, when there will be continuation of the list either with
            // a different list style, or after another list.

            for (auto next = it + 1; next != docListNodes.end(); ++next)
            {
                if (it->list_id != next->list_id)
                {
                    // List changed. We will have to refer to this id, only if there will
                    // appear a continuation of this list
                    return std::find_if(next + 1, docListNodes.end(),
                                        [list_id = it->list_id](const NodeData& data)
                                        { return data.list_id == list_id; })
                           == docListNodes.end();
                }

                if (it->style_id != next->style_id)
                {
                    // Same list, new style -> this "next" will refer to the id, no skipping
                    return false;
                }
                if (it->index + 1 != next->index)
                {
                    // we have a gap before the next node with the same list and style,
                    // with no other lists in between. There will be a continuation with a
                    // simple 'text:continue-numbering="true"'.
                    return true;
                }
                it = next; // walk through adjacent nodes of the same list
            }
            // all nodes were adjacent and of the same list and style -> no continuation, skip id
            return true;
        }

        return false;
    }
};

XMLTextParagraphExport::XMLTextParagraphExport(
        SvXMLExport& rExp,
        SvXMLAutoStylePoolP & rASP
        ) :
    XMLStyleExport( rExp, &rASP ),
    m_xImpl(new Impl),
    m_rAutoStylePool( rASP ),
    m_pBoundFrameSets(new BoundFrameSets(GetExport().GetModel())),
    maListAutoPool( GetExport() ),
    m_bProgress( false ),
    m_bBlock( false ),
    m_bOpenRuby( false ),
    mpTextListsHelper( nullptr ),
    mbCollected(false),
    m_aCharStyleNamesPropInfoCache( gsCharStyleNames )
{
    rtl::Reference < XMLPropertySetMapper > xPropMapper(new XMLTextPropertySetMapper( TextPropMap::PARA, true ));
    m_xParaPropMapper = new XMLTextExportPropertySetMapper( xPropMapper,
                                                             GetExport() );

    OUString sFamily( GetXMLToken(XML_PARAGRAPH) );
    OUString aPrefix(u'P');
    m_rAutoStylePool.AddFamily( XmlStyleFamily::TEXT_PARAGRAPH, sFamily,
                              m_xParaPropMapper, aPrefix );

    xPropMapper = new XMLTextPropertySetMapper( TextPropMap::TEXT, true );
    m_xTextPropMapper = new XMLTextExportPropertySetMapper( xPropMapper,
                                                             GetExport() );
    sFamily = GetXMLToken(XML_TEXT);
    aPrefix = u"T"_ustr;
    m_rAutoStylePool.AddFamily( XmlStyleFamily::TEXT_TEXT, sFamily,
                              m_xTextPropMapper, aPrefix );

    xPropMapper = new XMLTextPropertySetMapper( TextPropMap::AUTO_FRAME, true );
    m_xAutoFramePropMapper = new XMLTextExportPropertySetMapper( xPropMapper,
                                                                  GetExport() );
    sFamily = XML_STYLE_FAMILY_SD_GRAPHICS_NAME;
    aPrefix = u"fr"_ustr;
    m_rAutoStylePool.AddFamily( XmlStyleFamily::TEXT_FRAME, sFamily,
                              m_xAutoFramePropMapper, aPrefix );

    xPropMapper = new XMLTextPropertySetMapper( TextPropMap::SECTION, true );
    m_xSectionPropMapper = new XMLTextExportPropertySetMapper( xPropMapper,
                                                             GetExport() );
    sFamily = GetXMLToken( XML_SECTION );
    aPrefix = u"Sect"_ustr ;
    m_rAutoStylePool.AddFamily( XmlStyleFamily::TEXT_SECTION, sFamily,
                              m_xSectionPropMapper, aPrefix );

    xPropMapper = new XMLTextPropertySetMapper( TextPropMap::RUBY, true );
    m_xRubyPropMapper = new SvXMLExportPropertyMapper( xPropMapper );
    sFamily = GetXMLToken( XML_RUBY );
    aPrefix = u"Ru"_ustr;
    m_rAutoStylePool.AddFamily( XmlStyleFamily::TEXT_RUBY, sFamily,
                              m_xRubyPropMapper, aPrefix );

    xPropMapper = new XMLTextPropertySetMapper( TextPropMap::FRAME, true );
    m_xFramePropMapper = new XMLTextExportPropertySetMapper( xPropMapper,
                                                              GetExport() );

    m_pSectionExport.reset( new XMLSectionExport( rExp, *this ) );
    m_pIndexMarkExport.reset( new XMLIndexMarkExport( rExp ) );

    if( ! IsBlockMode() &&
        Reference<XRedlinesSupplier>( GetExport().GetModel(), UNO_QUERY ).is())
        m_pRedlineExport.reset( new XMLRedlineExport( rExp ) );

    // The text field helper needs a pre-constructed XMLPropertyState
    // to export the combined characters field. We construct that
    // here, because we need the text property mapper to do it.

    // construct Any value, then find index
    sal_Int32 nIndex = m_xTextPropMapper->getPropertySetMapper()->FindEntryIndex(
                                "", XML_NAMESPACE_STYLE,
                                GetXMLToken(XML_TEXT_COMBINE));
    m_pFieldExport.reset( new XMLTextFieldExport( rExp, std::make_unique<XMLPropertyState>( nIndex, cpo::uno::Any(true) ) ) );
    PushNewTextListsHelper();
}

XMLTextParagraphExport::~XMLTextParagraphExport()
{
    m_pRedlineExport.reset();
    m_pIndexMarkExport.reset();
    m_pSectionExport.reset();
    m_pFieldExport.reset();
#ifdef DBG_UTIL
    txtparae_bContainsIllegalCharacters = false;
#endif
    PopTextListsHelper();
    SAL_WARN_IF( !maTextListsHelperStack.empty(), "xmloff",
                "misusage of text lists helper stack - it is not empty. Serious defect" );
}

SvXMLExportPropertyMapper *XMLTextParagraphExport::CreateShapeExtPropMapper(
        SvXMLExport& rExport )
{
    rtl::Reference < XMLPropertySetMapper > xPropMapper =
        new XMLTextPropertySetMapper( TextPropMap::SHAPE, true );
    return new XMLTextExportPropertySetMapper( xPropMapper, rExport );
}

SvXMLExportPropertyMapper *XMLTextParagraphExport::CreateCharExtPropMapper(
        SvXMLExport& rExport)
{
    XMLPropertySetMapper *pPropMapper =
        new XMLTextPropertySetMapper( TextPropMap::TEXT, true );
    return new XMLTextExportPropertySetMapper( pPropMapper, rExport );
}

SvXMLExportPropertyMapper *XMLTextParagraphExport::CreateParaExtPropMapper(
        SvXMLExport& rExport)
{
    XMLPropertySetMapper *pPropMapper =
        new XMLTextPropertySetMapper( TextPropMap::SHAPE_PARA, true );
    return new XMLTextExportPropertySetMapper( pPropMapper, rExport );
}

SvXMLExportPropertyMapper *XMLTextParagraphExport::CreateParaDefaultExtPropMapper(
        SvXMLExport& rExport)
{
    XMLPropertySetMapper *pPropMapper =
        new XMLTextPropertySetMapper( TextPropMap::TEXT_ADDITIONAL_DEFAULTS, true );
    return new XMLTextExportPropertySetMapper( pPropMapper, rExport );
}

void XMLTextParagraphExport::exportPageFrames( bool bIsProgress )
{
    const TextContentSet& rTexts = m_pBoundFrameSets->GetTexts()->GetPageBoundContents();
    const TextContentSet& rGraphics = m_pBoundFrameSets->GetGraphics()->GetPageBoundContents();
    const TextContentSet& rEmbeddeds = m_pBoundFrameSets->GetEmbeddeds()->GetPageBoundContents();
    const TextContentSet& rShapes = m_pBoundFrameSets->GetShapes()->GetPageBoundContents();
    for(TextContentSet::const_iterator_t it = rTexts.getBegin();
        it != rTexts.getEnd();
        ++it)
        exportTextFrame(*it, false/*bAutoStyles*/, bIsProgress, true);
    for(TextContentSet::const_iterator_t it = rGraphics.getBegin();
        it != rGraphics.getEnd();
        ++it)
        exportTextGraphic(*it, false/*bAutoStyles*/);
    for(TextContentSet::const_iterator_t it = rEmbeddeds.getBegin();
        it != rEmbeddeds.getEnd();
        ++it)
        exportTextEmbedded(*it, false/*bAutoStyles*/);
    for(TextContentSet::const_iterator_t it = rShapes.getBegin();
        it != rShapes.getEnd();
        ++it)
        exportShape(*it, false/*bAutoStyles*/);
}

void XMLTextParagraphExport::exportFrameFrames(
        bool bAutoStyles,
        bool bIsProgress,
        const Reference < XTextFrame >& rParentTxtFrame )
{
    const TextContentSet* const pTexts = m_pBoundFrameSets->GetTexts()->GetFrameBoundContents(rParentTxtFrame);
    if(pTexts)
        for(TextContentSet::const_iterator_t it = pTexts->getBegin();
            it != pTexts->getEnd();
            ++it)
            exportTextFrame(*it, bAutoStyles, bIsProgress, true);
    const TextContentSet* const pGraphics = m_pBoundFrameSets->GetGraphics()->GetFrameBoundContents(rParentTxtFrame);
    if(pGraphics)
        for(TextContentSet::const_iterator_t it = pGraphics->getBegin();
            it != pGraphics->getEnd();
            ++it)
            exportTextGraphic(*it, bAutoStyles);
    const TextContentSet* const pEmbeddeds = m_pBoundFrameSets->GetEmbeddeds()->GetFrameBoundContents(rParentTxtFrame);
    if(pEmbeddeds)
        for(TextContentSet::const_iterator_t it = pEmbeddeds->getBegin();
            it != pEmbeddeds->getEnd();
            ++it)
            exportTextEmbedded(*it, bAutoStyles);
    const TextContentSet* const pShapes = m_pBoundFrameSets->GetShapes()->GetFrameBoundContents(rParentTxtFrame);
    if(pShapes)
        for(TextContentSet::const_iterator_t it = pShapes->getBegin();
            it != pShapes->getEnd();
            ++it)
            exportShape(*it, bAutoStyles);
}

// bookmarks, reference marks (and TOC marks) are the same except for the
// element names. We use the same method for export and it an array with
// the proper element names
const enum XMLTokenEnum lcl_XmlReferenceElements[] = {
    XML_REFERENCE_MARK, XML_REFERENCE_MARK_START, XML_REFERENCE_MARK_END };
const enum XMLTokenEnum lcl_XmlBookmarkElements[] = {
    XML_BOOKMARK, XML_BOOKMARK_START, XML_BOOKMARK_END };

void XMLTextParagraphExport::collectTextAutoStylesAndNodeExportOrder(bool bIsProgress)
{
    GetExport().GetShapeExport(); // make sure the graphics styles family is added

    if (mbCollected)
        return;

    const bool bAutoStyles = true;
    const bool bExportContent = true;

    if (auto xTextDocument = GetExport().GetModel().query<XTextDocument>())
    {
        bInDocumentNodeOrderCollection = true;
        collectTextAutoStyles(xTextDocument->getText(), bIsProgress);
        bInDocumentNodeOrderCollection = false;
    }

    // Export text frames:
    Reference<XEnumeration> xTextFramesEnum = m_pBoundFrameSets->GetTexts()->createEnumeration();
    if(xTextFramesEnum.is())
        while(xTextFramesEnum->hasMoreElements())
        {
            Reference<XTextContent> xTxtCntnt(xTextFramesEnum->nextElement(), UNO_QUERY);
            if(xTxtCntnt.is())
                exportTextFrame(xTxtCntnt, bAutoStyles, bIsProgress, bExportContent);
        }

    // Export graphic objects:
    Reference<XEnumeration> xGraphicsEnum = m_pBoundFrameSets->GetGraphics()->createEnumeration();
    if(xGraphicsEnum.is())
        while(xGraphicsEnum->hasMoreElements())
        {
            Reference<XTextContent> xTxtCntnt(xGraphicsEnum->nextElement(), UNO_QUERY);
            if(xTxtCntnt.is())
                exportTextGraphic(xTxtCntnt, true);
        }

    // Export embedded objects:
    Reference<XEnumeration> xEmbeddedsEnum = m_pBoundFrameSets->GetEmbeddeds()->createEnumeration();
    if(xEmbeddedsEnum.is())
        while(xEmbeddedsEnum->hasMoreElements())
        {
            Reference<XTextContent> xTxtCntnt(xEmbeddedsEnum->nextElement(), UNO_QUERY);
            if(xTxtCntnt.is())
                exportTextEmbedded(xTxtCntnt, true);
        }

    // Export shapes:
    Reference<XEnumeration> xShapesEnum = m_pBoundFrameSets->GetShapes()->createEnumeration();
    if(xShapesEnum.is())
        while(xShapesEnum->hasMoreElements())
        {
            Reference<XTextContent> xTxtCntnt(xShapesEnum->nextElement(), UNO_QUERY);
            if(xTxtCntnt.is())
            {
                Reference<XServiceInfo> xServiceInfo(xTxtCntnt, UNO_QUERY);
                if( xServiceInfo->supportsService(gsShapeService))
                    exportShape(xTxtCntnt, true);
            }
        }

    if (GetExport().getExportFlags() & SvXMLExportFlags::CONTENT)
        exportTrackedChanges(true);

    mbCollected = true;
}

void XMLTextParagraphExport::exportText(
        const Reference < XText > & rText,
        bool bAutoStyles,
        bool bIsProgress,
        bool bExportParagraph,
        TextPNS eExtensionNS)
{
    if( bAutoStyles )
        GetExport().GetShapeExport(); // make sure the graphics styles family
                                      // is added
    Reference < XEnumerationAccess > xEA( rText, UNO_QUERY );
    if( ! xEA.is() )
        return;

    Reference < XEnumeration > xParaEnum(xEA->createEnumeration());
    Reference < XPropertySet > xPropertySet( rText, UNO_QUERY );
    Reference < XTextSection > xBaseSection;

    // #97718# footnotes don't supply paragraph enumerations in some cases
    // This is always a bug, but at least we don't want to crash.
    SAL_WARN_IF( !xParaEnum.is(), "xmloff", "We need a paragraph enumeration" );
    if( ! xParaEnum.is() )
        return;

    if (xPropertySet.is())
    {
        Reference < XPropertySetInfo > xInfo ( xPropertySet->getPropertySetInfo() );

        if( xInfo.is() )
        {
            if (xInfo->hasPropertyByName( gsTextSection ))
            {
                xPropertySet->getPropertyValue(gsTextSection) >>= xBaseSection ;
            }
        }
    }

    // #96530# Export redlines at start & end of XText before & after
    // exporting the text content enumeration
    if( !bAutoStyles && (m_pRedlineExport != nullptr) )
        m_pRedlineExport->ExportStartOrEndRedline( xPropertySet, true );
    exportTextContentEnumeration( xParaEnum, bAutoStyles, xBaseSection,
                                  bIsProgress, bExportParagraph, nullptr, eExtensionNS );
    if( !bAutoStyles && (m_pRedlineExport != nullptr) )
        m_pRedlineExport->ExportStartOrEndRedline( xPropertySet, false );
}

void XMLTextParagraphExport::exportText(
        const Reference < XText > & rText,
        const Reference < XTextSection > & rBaseSection,
        bool bAutoStyles,
        bool bIsProgress,
        bool bExportParagraph)
{
    if( bAutoStyles )
        GetExport().GetShapeExport(); // make sure the graphics styles family
                                      // is added
    Reference < XEnumerationAccess > xEA( rText, UNO_QUERY );
    Reference < XEnumeration > xParaEnum(xEA->createEnumeration());

    // #98165# don't continue without a paragraph enumeration
    if( ! xParaEnum.is() )
        return;

    // #96530# Export redlines at start & end of XText before & after
    // exporting the text content enumeration
    Reference<XPropertySet> xPropertySet;
    if( !bAutoStyles && (m_pRedlineExport != nullptr) )
    {
        xPropertySet.set(rText, uno::UNO_QUERY );
        m_pRedlineExport->ExportStartOrEndRedline( xPropertySet, true );
    }
    exportTextContentEnumeration( xParaEnum, bAutoStyles, rBaseSection,
                                  bIsProgress, bExportParagraph );
    if( !bAutoStyles && (m_pRedlineExport != nullptr) )
        m_pRedlineExport->ExportStartOrEndRedline( xPropertySet, false );
}

bool XMLTextParagraphExport::ExportListId() const
{
    return (GetExport().getExportFlags() & SvXMLExportFlags::OASIS)
           && GetExport().getSaneDefaultVersion() >= SvtSaveOptions::ODFSVER_012;
}

#ifndef NDEBUG
static bool isInShapesTextFrame(const css::uno::Reference<css::text::XTextContent>& xTextContent)
{
    auto xTextRange = xTextContent.query<css::text::XTextRange>();
    if (!xTextRange)
        return false;
    auto xParentTextProps = xTextRange->getText().query<css::beans::XPropertySet>();
    if (!xParentTextProps)
        return false;
    try
    {
        // see SwXTextFrame::getEvents
        cpo::uno::Any ret = xParentTextProps->getPropertyValue(u"DbgIsShapesTextFrame"_ustr);
        if (bool result; ret >>= result)
            return result;
        return false;
    }
    catch (css::beans::UnknownPropertyException&)
    {
        return false;
    }
}
#endif

void XMLTextParagraphExport::RecordNodeIndex(const css::uno::Reference<css::text::XTextContent>& xTextContent)
{
    if (!bInDocumentNodeOrderCollection)
        return;
    if (auto xPropSet = xTextContent.query<css::beans::XPropertySet>())
    {
        try
        {
            sal_Int32 index = 0;
            // See SwXParagraph::Impl::GetPropertyValues_Impl
            xPropSet->getPropertyValue(u"ODFExport_NodeIndex"_ustr) >>= index;
            auto it = std::find(maDocumentNodeOrder.begin(), maDocumentNodeOrder.end(), index);
            assert(it == maDocumentNodeOrder.end() || isInShapesTextFrame(xTextContent));
            if (it == maDocumentNodeOrder.end())
                maDocumentNodeOrder.push_back(index);
        }
        catch (css::beans::UnknownPropertyException&)
        {
            // That's absolutely fine!
        }
    }
}

bool XMLTextParagraphExport::ShouldSkipListId(const Reference<XTextContent>& xTextContent)
{
    if (!mpDocumentListNodes)
    {
        if (ExportListId())
            mpDocumentListNodes.reset(new DocumentListNodes(GetExport().GetModel(), maDocumentNodeOrder));
        else
            mpDocumentListNodes.reset(new DocumentListNodes({}, {}));
    }

    return mpDocumentListNodes->ShouldSkipListId(xTextContent);
}

void XMLTextParagraphExport::exportTextContentEnumeration(
        const Reference < XEnumeration > & rContEnum,
        bool bAutoStyles,
        const Reference < XTextSection > & rBaseSection,
        bool bIsProgress,
        bool bExportParagraph,
        const Reference < XPropertySet > *pRangePropSet,
        TextPNS eExtensionNS )
{
    SAL_WARN_IF( !rContEnum.is(), "xmloff", "No enumeration to export!" );
    bool bHasMoreElements = rContEnum->hasMoreElements();
    if( !bHasMoreElements )
        return;

    XMLTextNumRuleInfo aPrevNumInfo;
    XMLTextNumRuleInfo aNextNumInfo;

    bool bHasContent = false;
    Reference<XTextSection> xCurrentTextSection(rBaseSection);

    MultiPropertySetHelper aPropSetHelper(
                               bAutoStyles ? std::span<const OUString>(aParagraphPropertyNamesAuto) :
                                          std::span<const OUString>(aParagraphPropertyNames) );

    bool bHoldElement = false;
    Reference < XTextContent > xTxtCntnt;
    while( bHoldElement || bHasMoreElements )
    {
        if (bHoldElement)
        {
            bHoldElement = false;
        }
        else
        {
            xTxtCntnt.set(rContEnum->nextElement(), uno::UNO_QUERY);

            aPropSetHelper.resetValues();

        }

        Reference<XServiceInfo> xServiceInfo( xTxtCntnt, UNO_QUERY );
        if( xServiceInfo->supportsService( gsParagraphService ) )
        {
            if( bAutoStyles )
            {
                RecordNodeIndex(xTxtCntnt);
                exportListAndSectionChange( xCurrentTextSection, xTxtCntnt,
                                            aPrevNumInfo, aNextNumInfo,
                                            bAutoStyles );
            }
            else
            {
                /* Pass list auto style pool to <XMLTextNumRuleInfo> instance
                   Pass info about request to export <text:number> element
                   to <XMLTextNumRuleInfo> instance (#i69627#)
                */
                aNextNumInfo.Set( xTxtCntnt,
                                  GetExport().writeOutlineStyleAsNormalListStyle(),
                                  GetListAutoStylePool(),
                                  GetExport().exportTextNumberElement(),
                                  ShouldSkipListId(xTxtCntnt) );

                exportListAndSectionChange( xCurrentTextSection, aPropSetHelper,
                                            TEXT_SECTION, xTxtCntnt,
                                            aPrevNumInfo, aNextNumInfo,
                                            bAutoStyles );
            }

            // if we found a mute section: skip all section content
            if (m_pSectionExport->IsMuteSection(xCurrentTextSection))
            {
                // Make sure headings are exported anyway.
                if( !bAutoStyles )
                    m_pSectionExport->ExportMasterDocHeadingDummies();

                while (rContEnum->hasMoreElements() &&
                       XMLSectionExport::IsInSection( xCurrentTextSection,
                                                    xTxtCntnt, true ))
                {
                    xTxtCntnt.set(rContEnum->nextElement(), uno::UNO_QUERY);
                    aPropSetHelper.resetValues();
                    aNextNumInfo.Reset();
                }
                // the first non-mute element still needs to be processed
                bHoldElement =
                    ! XMLSectionExport::IsInSection( xCurrentTextSection,
                                                   xTxtCntnt, false );
            }
            else
                exportParagraph( xTxtCntnt, bAutoStyles, bIsProgress,
                                 bExportParagraph, aPropSetHelper, eExtensionNS );
            bHasContent = true;
        }
        else if( xServiceInfo->supportsService( gsTableService ) )
        {
            if( !bAutoStyles )
            {
                aNextNumInfo.Reset();
            }

            exportListAndSectionChange( xCurrentTextSection, xTxtCntnt,
                                        aPrevNumInfo, aNextNumInfo,
                                        bAutoStyles );

            if (! m_pSectionExport->IsMuteSection(xCurrentTextSection))
            {
                // export start + end redlines (for wholly redlined tables)
                if ((! bAutoStyles) && (nullptr != m_pRedlineExport))
                    m_pRedlineExport->ExportStartOrEndRedline(xTxtCntnt, true);

                exportTable( xTxtCntnt, bAutoStyles, bIsProgress  );

                if ((! bAutoStyles) && (nullptr != m_pRedlineExport))
                    m_pRedlineExport->ExportStartOrEndRedline(xTxtCntnt, false);
            }
            else if( !bAutoStyles )
            {
                // Make sure headings are exported anyway.
                m_pSectionExport->ExportMasterDocHeadingDummies();
            }

            bHasContent = true;
        }
        else if( xServiceInfo->supportsService( gsTextFrameService ) )
        {
            exportTextFrame( xTxtCntnt, bAutoStyles, bIsProgress, true, pRangePropSet );
        }
        else if( xServiceInfo->supportsService( gsTextGraphicService ) )
        {
            exportTextGraphic( xTxtCntnt, bAutoStyles, pRangePropSet );
        }
        else if( xServiceInfo->supportsService( gsTextEmbeddedService ) )
        {
            exportTextEmbedded( xTxtCntnt, bAutoStyles, pRangePropSet );
        }
        else if( xServiceInfo->supportsService( gsShapeService ) )
        {
            exportShape( xTxtCntnt, bAutoStyles, pRangePropSet );
        }
        else
        {
            SAL_WARN_IF( xTxtCntnt.is(), "xmloff", "unknown text content" );
        }

        if( !bAutoStyles )
        {
            aPrevNumInfo = aNextNumInfo;
        }

        bHasMoreElements = rContEnum->hasMoreElements();
    }

    if( bHasContent && !bAutoStyles )
    {
        aNextNumInfo.Reset();

        // close open lists and sections; no new styles
        exportListAndSectionChange( xCurrentTextSection, rBaseSection,
                                    aPrevNumInfo, aNextNumInfo,
                                    bAutoStyles );
    }
}

void XMLTextParagraphExport::exportParagraph(
        const Reference < XTextContent > & rTextContent,
        bool bAutoStyles, bool bIsProgress, bool bExportParagraph,
        MultiPropertySetHelper& rPropSetHelper, TextPNS eExtensionNS)
{
    sal_Int16 nOutlineLevel = -1;

    if( bIsProgress )
    {
        ProgressBarHelper *pProgress = GetExport().GetProgressBarHelper();
        pProgress->SetValue( pProgress->GetValue()+1 );
    }

    // get property set or multi property set and initialize helper
    Reference<XMultiPropertySet> xMultiPropSet( rTextContent, UNO_QUERY );
    Reference<XPropertySet> xPropSet( rTextContent, UNO_QUERY );

    // check for supported properties
    if( !rPropSetHelper.checkedProperties() )
        rPropSetHelper.hasProperties( xPropSet->getPropertySetInfo() );

//  if( xMultiPropSet.is() )
//      rPropSetHelper.getValues( xMultiPropSet );
//  else
//      rPropSetHelper.getValues( xPropSet );

    if( bExportParagraph )
    {
        if( bAutoStyles )
        {
            Add( XmlStyleFamily::TEXT_PARAGRAPH, rPropSetHelper, xPropSet );
        }
        else
        {
            // xml:id for RDF metadata
            GetExport().AddAttributeXmlId(rTextContent);
            GetExport().AddAttributesRDFa(rTextContent);

            OUString sStyle;
            if( rPropSetHelper.hasProperty( PARA_STYLE_NAME ) )
            {
                if( xMultiPropSet.is() )
                    rPropSetHelper.getValue( PARA_STYLE_NAME,
                                                    xMultiPropSet ) >>= sStyle;
                else
                    rPropSetHelper.getValue( PARA_STYLE_NAME,
                                                    xPropSet ) >>= sStyle;
            }

            if( rTextContent.is() )
            {
                const OUString& rIdentifier = GetExport().getInterfaceToIdentifierMapper().getIdentifier( rTextContent );
                if( !rIdentifier.isEmpty() )
                {
                    // FIXME: this is just temporary until EditEngine
                    // paragraphs implement XMetadatable.
                    // then that must be used and not the mapper, because
                    // when both can be used we get two xml:id!
                    uno::Reference<rdf::XMetadatable> const xMeta(rTextContent,
                        uno::UNO_QUERY);
                    OSL_ENSURE(!xMeta.is(), "paragraph that implements "
                        "XMetadatable used in interfaceToIdentifierMapper?");
                    GetExport().AddAttributeIdLegacy(XML_NAMESPACE_TEXT,
                        rIdentifier);
                }
            }

            OUString sAutoStyle = Find( XmlStyleFamily::TEXT_PARAGRAPH, xPropSet, sStyle );
            if ( sAutoStyle.isEmpty() )
                sAutoStyle = sStyle;
            if( !sAutoStyle.isEmpty() )
                GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                              GetExport().EncodeStyleName( sAutoStyle ) );

            if( rPropSetHelper.hasProperty( PARA_CONDITIONAL_STYLE_NAME ) )
            {
                OUString sCondStyle;
                if( xMultiPropSet.is() )
                    rPropSetHelper.getValue( PARA_CONDITIONAL_STYLE_NAME,
                                                     xMultiPropSet ) >>= sCondStyle;
                else
                    rPropSetHelper.getValue( PARA_CONDITIONAL_STYLE_NAME,
                                                     xPropSet ) >>= sCondStyle;
                if( sCondStyle != sStyle )
                {
                    sCondStyle = Find( XmlStyleFamily::TEXT_PARAGRAPH, xPropSet,
                                          sCondStyle );
                    if( !sCondStyle.isEmpty() )
                        GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                                  XML_COND_STYLE_NAME,
                              GetExport().EncodeStyleName( sCondStyle ) );
                }
            }

            if( rPropSetHelper.hasProperty( PARA_OUTLINE_LEVEL ) )
            {
                if( xMultiPropSet.is() )
                    rPropSetHelper.getValue( PARA_OUTLINE_LEVEL,
                                                     xMultiPropSet ) >>= nOutlineLevel;
                else
                    rPropSetHelper.getValue( PARA_OUTLINE_LEVEL,
                                                     xPropSet ) >>= nOutlineLevel;

                if( 0 < nOutlineLevel )
                {
                    GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                              XML_OUTLINE_LEVEL,
                                  OUString::number( sal_Int32( nOutlineLevel) ) );

                    if ( rPropSetHelper.hasProperty( PARA_OUTLINE_CONTENT_VISIBLE ) )
                    {
                        cpo::uno::Sequence<beans::PropertyValue> propList;
                        bool bIsOutlineContentVisible = true;
                        if( xMultiPropSet.is() )
                            rPropSetHelper.getValue(
                                       PARA_OUTLINE_CONTENT_VISIBLE, xMultiPropSet ) >>= propList;
                        else
                            rPropSetHelper.getValue(
                                       PARA_OUTLINE_CONTENT_VISIBLE, xPropSet ) >>= propList;
                        for (const auto& rProp : propList)
                        {
                            OUString propName = rProp.Name;
                            if (propName == "OutlineContentVisibleAttr")
                            {
                                rProp.Value >>= bIsOutlineContentVisible;
                                break;
                            }
                        }
                        if (!bIsOutlineContentVisible)
                        {
                            GetExport().AddAttribute( XML_NAMESPACE_LO_EXT,
                                              XML_OUTLINE_CONTENT_VISIBLE,
                                              XML_FALSE);
                        }
                    }

                    if( rPropSetHelper.hasProperty( NUMBERING_IS_NUMBER ) )
                    {
                        bool bIsNumber = false;
                        if( xMultiPropSet.is() )
                            rPropSetHelper.getValue(
                                       NUMBERING_IS_NUMBER, xMultiPropSet ) >>= bIsNumber;
                        else
                            rPropSetHelper.getValue(
                                       NUMBERING_IS_NUMBER, xPropSet ) >>= bIsNumber;

                        OUString sListStyleName;
                        if( xMultiPropSet.is() )
                            rPropSetHelper.getValue(
                                       PARA_NUMBERING_STYLENAME, xMultiPropSet ) >>= sListStyleName;
                        else
                            rPropSetHelper.getValue(
                                       PARA_NUMBERING_STYLENAME, xPropSet ) >>= sListStyleName;

                        bool bAssignedtoOutlineStyle = false;
                        {
                            Reference< XChapterNumberingSupplier > xCNSupplier( GetExport().GetModel(), UNO_QUERY );

                            if (xCNSupplier.is())
                            {
                                Reference< XIndexReplace > xNumRule ( xCNSupplier->getChapterNumberingRules() );
                                SAL_WARN_IF( !xNumRule.is(), "xmloff", "no chapter numbering rules" );

                                if (xNumRule.is())
                                {
                                    Reference< XPropertySet > xNumRulePropSet( xNumRule, UNO_QUERY );
                                    OUString sOutlineName;
                                    xNumRulePropSet->getPropertyValue(
                                        u"Name"_ustr ) >>= sOutlineName;
                                    bAssignedtoOutlineStyle = ( sListStyleName == sOutlineName );
                                }
                            }
                        }

                        if( ! bIsNumber && bAssignedtoOutlineStyle )
                            GetExport().AddAttribute( XML_NAMESPACE_TEXT,
                                                      XML_IS_LIST_HEADER,
                                                      XML_TRUE );
                    }

                    {
                        bool bIsRestartNumbering = false;

                        Reference< XPropertySetInfo >
                        xPropSetInfo(xMultiPropSet.is() ?
                                     xMultiPropSet->getPropertySetInfo():
                                     xPropSet->getPropertySetInfo());

                        if (xPropSetInfo->
                            hasPropertyByName(u"ParaIsNumberingRestart"_ustr))
                        {
                            xPropSet->getPropertyValue(u"ParaIsNumberingRestart"_ustr)
                                >>= bIsRestartNumbering;
                        }

                        if (bIsRestartNumbering)
                        {
                            GetExport().AddAttribute(XML_NAMESPACE_TEXT,
                                                     XML_RESTART_NUMBERING,
                                                     XML_TRUE);

                            if (xPropSetInfo->
                                hasPropertyByName(u"NumberingStartValue"_ustr))
                            {
                                sal_Int32 nStartValue = 0;

                                xPropSet->getPropertyValue(u"NumberingStartValue"_ustr)
                                    >>= nStartValue;

                                GetExport().
                                    AddAttribute(XML_NAMESPACE_TEXT,
                                                 XML_START_VALUE,
                                                 OUString::number(nStartValue));
                            }
                        }
                    }
                }
            }
        }

        if (GetExport().getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED)
        {
            try
            {
                // ParaMarkerAutoStyleSpan is a hidden property, just to pass the autostyle here
                // See SwXParagraph::Impl::GetPropertyValues_Impl
                cpo::uno::Any aVal = xPropSet->getPropertyValue(u"ParaMarkerAutoStyleSpan"_ustr);
                if (auto xFakeSpan = aVal.query<css::beans::XPropertySet>())
                {
                    if (bAutoStyles)
                    {
                        Add(XmlStyleFamily::TEXT_TEXT, xFakeSpan);
                    }
                    else
                    {
                        bool bIsUICharStyle, bHasAutoStyle;
                        OUString sStyle = FindTextStyle(xFakeSpan, bIsUICharStyle, bHasAutoStyle);
                        if (!sStyle.isEmpty())
                        {
                            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_MARKER_STYLE_NAME,
                                                     sStyle);
                        }
                    }
                }
            }
            catch (const css::beans::UnknownPropertyException&)
            {
                // No problem
            }
        }
    }

    Reference < XEnumerationAccess > xEA( rTextContent, UNO_QUERY );
    Reference < XEnumeration > xTextEnum = xEA->createEnumeration();

    Reference < XEnumeration> xContentEnum;
    Reference < XContentEnumerationAccess > xCEA( rTextContent, UNO_QUERY );
    if( xCEA.is() )
        xContentEnum.set(xCEA->createContentEnumeration( gsTextContentService ));
    const bool bHasContentEnum = xContentEnum.is() &&
                                        xContentEnum->hasMoreElements();

    Reference < XTextSection > xSection;
    if( bHasContentEnum )
    {
        // For the auto styles, the multi property set helper is only used
        // if hard attributes are existing. Therefore, it seems to be a better
        // strategy to have the TextSection property separate, because otherwise
        // we always retrieve the style names even if they are not required.
        if( bAutoStyles )
        {
            if( xPropSet->getPropertySetInfo()->hasPropertyByName( gsTextSection ) )
            {
                xSection.set(xPropSet->getPropertyValue( gsTextSection ), uno::UNO_QUERY);
            }
        }
        else
        {
            if( rPropSetHelper.hasProperty( TEXT_SECTION ) )
            {
                xSection.set(rPropSetHelper.getValue( TEXT_SECTION ), uno::UNO_QUERY);
            }
        }
    }

    bool bPrevCharIsSpace(true); // true because whitespace at start is ignored

    {
        enum XMLTokenEnum eElem =
            0 < nOutlineLevel ? XML_H : XML_P;
        SvXMLElementExport aElem( GetExport(), !bAutoStyles, eExtensionNS == TextPNS::EXTENSION ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_TEXT, eElem,
                                  true, false );
        if( bHasContentEnum )
        {
            exportTextContentEnumeration(
                                    xContentEnum, bAutoStyles, xSection,
                                    bIsProgress );
        }
        exportTextRangeEnumeration(xTextEnum, bAutoStyles, bIsProgress, bPrevCharIsSpace);
    }
}

void XMLTextParagraphExport::exportTextRangeEnumeration(
        const Reference < XEnumeration > & rTextEnum,
        bool bAutoStyles, bool bIsProgress,
        bool & rPrevCharIsSpace)
{
    static const char sFieldMarkName[] = "__FieldMark_";

    /* This is  used for exporting to strict OpenDocument 1.2, in which case traditional
     * bookmarks are used instead of fieldmarks. */
    FieldmarkType openFieldMark = NONE;

    std::optional<SvXMLElementExport> oTextA;
    HyperlinkData aHyperlinkData;

    while( rTextEnum->hasMoreElements() )
    {
        Reference<XPropertySet> xPropSet(rTextEnum->nextElement(), UNO_QUERY);
        Reference < XTextRange > xTxtRange(xPropSet, uno::UNO_QUERY);
        Reference<XPropertySetInfo> xPropInfo(xPropSet->getPropertySetInfo());

        if (!bAutoStyles)
        {
            if (HyperlinkData aNewHyperlinkData(xPropSet); aNewHyperlinkData != aHyperlinkData)
            {
                aHyperlinkData = std::move(aNewHyperlinkData);
                oTextA.reset();
                if (aHyperlinkData.addHyperlinkAttributes(GetExport()))
                {
                    oTextA.emplace(GetExport(), true, XML_NAMESPACE_TEXT, XML_A, false, false);
                    aHyperlinkData.exportEvents(GetExport());
                }
            }
        }

        if (xPropInfo->hasPropertyByName(gsTextPortionType))
        {
            OUString sType;
            xPropSet->getPropertyValue(gsTextPortionType) >>= sType;

            if( sType == gsText)
            {
                exportTextRange( xTxtRange, bAutoStyles,
                                 rPrevCharIsSpace, openFieldMark);
            }
            else if( sType == gsTextField)
            {
                exportTextField(xTxtRange, bAutoStyles, bIsProgress, &rPrevCharIsSpace);
            }
            else if ( sType == "Annotation" )
            {
                exportTextField(xTxtRange, bAutoStyles, bIsProgress, &rPrevCharIsSpace);
            }
            else if ( sType == "AnnotationEnd" )
            {
                if (!bAutoStyles)
                {
                    Reference<XNamed> xBookmark(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
                    const OUString aName = xBookmark->getName();
                    if (!aName.isEmpty())
                    {
                        GetExport().AddAttribute(XML_NAMESPACE_OFFICE, XML_NAME, aName);
                    }
                    SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_OFFICE, XML_ANNOTATION_END, false, false );
                }
            }
            else if( sType == gsFrame )
            {
                Reference < XEnumeration> xContentEnum;
                Reference < XContentEnumerationAccess > xCEA( xTxtRange,
                                                              UNO_QUERY );
                if( xCEA.is() )
                    xContentEnum.set(xCEA->createContentEnumeration(
                                                    gsTextContentService ));
                // frames are never in sections
                Reference<XTextSection> xSection;
                if( xContentEnum.is() )
                    exportTextContentEnumeration( xContentEnum,
                                                    bAutoStyles,
                                                    xSection, bIsProgress, true,
                                                     &xPropSet  );

            }
            else if (sType == gsFootnote)
            {
                exportTextFootnote(xPropSet,
                                   xTxtRange->getString(),
                                   bAutoStyles, bIsProgress );
            }
            else if (sType == gsBookmark)
            {
                exportTextMark(xPropSet,
                               gsBookmark,
                               lcl_XmlBookmarkElements,
                               bAutoStyles);
            }
            else if (sType == gsReferenceMark)
            {
                exportTextMark(xPropSet,
                               gsReferenceMark,
                               lcl_XmlReferenceElements,
                               bAutoStyles);
            }
            else if (sType == gsDocumentIndexMark)
            {
                m_pIndexMarkExport->ExportIndexMark(xPropSet, bAutoStyles);
            }
            else if (sType == gsRedline)
            {
                if (nullptr != m_pRedlineExport)
                    m_pRedlineExport->ExportChange(xPropSet, bAutoStyles);
            }
            else if (sType == gsRuby)
            {
                exportRuby(xPropSet, bAutoStyles);
            }
            else if (sType == "InContentMetadata")
            {
                exportMeta(xPropSet, bAutoStyles, bIsProgress, rPrevCharIsSpace);
            }
            else if (sType == "ContentControl")
            {
                ExportContentControl(xPropSet, bAutoStyles, bIsProgress, rPrevCharIsSpace);
            }
            else if (sType == gsTextFieldStart)
            {
                Reference< css::text::XFormField > xFormField(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);

                /* As of now, textmarks are a proposed extension to the OpenDocument standard. */
                if (!bAutoStyles)
                {
                    if (GetExport().getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED)
                    {
                        Reference<XNamed> xBookmark(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
                        if (xBookmark.is())
                        {
                            GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_NAME, xBookmark->getName());
                        }

                        if (xFormField.is())
                        {
                            GetExport().AddAttribute(XML_NAMESPACE_FIELD, XML_TYPE, xFormField->getFieldType());
                        }

                        GetExport().StartElement(XML_NAMESPACE_FIELD, XML_FIELDMARK_START, false);
                        if (xFormField.is())
                        {
                            FieldParamExporter(&GetExport(), xFormField->getParameters()).Export();
                        }
                        GetExport().EndElement(XML_NAMESPACE_FIELD, XML_FIELDMARK_START, false);
                    }
                    /* The OpenDocument standard does not include support for TextMarks for now, so use bookmarks instead. */
                    else
                    {
                        if (xFormField.is())
                        {
                            OUString sName;
                            Reference< css::container::XNameAccess > xParameters = xFormField->getParameters();
                            if (xParameters.is() && xParameters->hasByName(u"Name"_ustr))
                            {
                                const Any aValue = xParameters->getByName(u"Name"_ustr);
                                aValue >>= sName;
                            }
                            if (sName.isEmpty())
                            {   // name attribute is mandatory, so have to pull a
                                // rabbit out of the hat here
                                sName = sFieldMarkName + OUString::number(
                                        m_xImpl->AddFieldMarkStart(xFormField));
                            }
                            GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_NAME,
                                    sName);
                            SvXMLElementExport aElem( GetExport(), !bAutoStyles,
                                XML_NAMESPACE_TEXT, XML_BOOKMARK_START,
                                false, false );
                            const OUString sFieldType = xFormField->getFieldType();
                            if (sFieldType ==  ODF_FORMTEXT)
                            {
                                openFieldMark = TEXT;
                            }
                            else if (sFieldType == ODF_FORMCHECKBOX)
                            {
                                openFieldMark = CHECK;
                            }
                            else
                            {
                                openFieldMark = NONE;
                            }
                        }
                    }
                }
            }
            else if (sType == gsTextFieldSep)
            {
                if (!bAutoStyles)
                {
                    if (GetExport().getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED)
                    {
                        SvXMLElementExport aElem( GetExport(), !bAutoStyles,
                            XML_NAMESPACE_FIELD, XML_FIELDMARK_SEPARATOR,
                            false, false );
                    }
                }
            }
            else if (sType == gsTextFieldEnd)
            {
                if (!bAutoStyles)
                {
                    Reference< css::text::XFormField > xFormField(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);

                    if (GetExport().getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED)
                    {
                        SvXMLElementExport aElem( GetExport(), !bAutoStyles,
                            XML_NAMESPACE_FIELD, XML_FIELDMARK_END,
                            false, false );
                    }
                    else
                    {
                        if (xFormField.is())
                        {
                            OUString sName;
                            Reference< css::container::XNameAccess > xParameters = xFormField->getParameters();
                            if (xParameters.is() && xParameters->hasByName(u"Name"_ustr))
                            {
                                const Any aValue = xParameters->getByName(u"Name"_ustr);
                                aValue >>= sName;
                            }
                            if (sName.isEmpty())
                            {   // name attribute is mandatory, so have to pull a
                                // rabbit out of the hat here
                                sName = sFieldMarkName + OUString::number(
                                    m_xImpl->GetFieldMarkIndex(xFormField));
                            }
                            GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_NAME,
                                    sName);
                            SvXMLElementExport aElem( GetExport(), !bAutoStyles,
                                XML_NAMESPACE_TEXT, XML_BOOKMARK_END,
                                false, false );
                        }
                    }
                }
            }
            else if (sType == gsTextFieldStartEnd)
            {
                exportTextFieldStartEnd(xPropSet, bAutoStyles);
            }
            else if (sType == gsSoftPageBreak)
            {
                if (!bAutoStyles)
                    exportSoftPageBreak();
            }
            else if (sType == "LineBreak")
            {
                if (!bAutoStyles)
                    exportTextLineBreak(xPropSet);
            }
            else {
                OSL_FAIL("unknown text portion type");
            }
        }
        else
        {
            Reference<XServiceInfo> xServiceInfo( xTxtRange, UNO_QUERY );
            if( xServiceInfo->supportsService( gsTextFieldService ) )
            {
                exportTextField(xTxtRange, bAutoStyles, bIsProgress, &rPrevCharIsSpace);
            }
            else
            {
                // no TextPortionType property -> non-Writer app -> text
                exportTextRange(xTxtRange, bAutoStyles, rPrevCharIsSpace, openFieldMark);
            }
        }
    }

// now that there are nested enumerations for meta(-field), this may be valid!
//  SAL_WARN_IF( bOpenRuby, "xmloff", "Red Alert: Ruby still open!" );
}

void XMLTextParagraphExport::exportTable(
        const Reference < XTextContent > &,
        bool /*bAutoStyles*/, bool /*bIsProgress*/ )
{
}

void XMLTextParagraphExport::exportTextField(
        const Reference < XTextRange > & rTextRange,
        bool bAutoStyles, bool bIsProgress, bool *const pPrevCharIsSpace)
{
    Reference < XPropertySet > xPropSet( rTextRange, UNO_QUERY );
    // non-Writer apps need not support Property TextField, so test first
    if (!xPropSet->getPropertySetInfo()->hasPropertyByName( gsTextField ))
        return;

    Reference < XTextField > xTxtFld(xPropSet->getPropertyValue( gsTextField ), uno::UNO_QUERY);
    SAL_WARN_IF( !xTxtFld.is(), "xmloff", "text field missing" );
    if( xTxtFld.is() )
    {
        exportTextField(xTxtFld, bAutoStyles, bIsProgress, pPrevCharIsSpace);
    }
    else
    {
        // write only characters
        GetExport().Characters(rTextRange->getString());
    }
}

void XMLTextParagraphExport::exportTextField(
        const Reference < XTextField > & xTextField,
        const bool bAutoStyles, const bool bIsProgress,
        bool *const pPrevCharIsSpace)
{
    if ( bAutoStyles )
    {
        m_pFieldExport->ExportFieldAutoStyle( xTextField, bIsProgress );
    }
    else
    {
        assert(pPrevCharIsSpace);
        m_pFieldExport->ExportField(xTextField, bIsProgress, *pPrevCharIsSpace);
    }
}

void XMLTextParagraphExport::exportTextFieldStartEnd(const Reference < XPropertySet >& xPropSet, const bool bAutoStyles)
{
    if (!bAutoStyles)
    {
        if (GetExport().getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED)
        {
            bool bHasStyle = false;
            Reference<XNamed> xBookmark(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
            if (xBookmark.is())
            {
                Reference<XTextContent> xBookmarkContent(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
                Reference<XPropertySet> xRangePropSet(xBookmarkContent->getAnchor(), UNO_QUERY);
                const XMLPropertyState **pStates = nullptr;
                // find out whether we need to set the style
                bool bIsUICharStyle;
                bool bHasAutoStyle;
                OUString sStyle = GetExport().GetTextParagraphExport()->
                    FindTextStyle( xRangePropSet, bIsUICharStyle, bHasAutoStyle, pStates );
                bHasStyle = !sStyle.isEmpty();
                Reference<XPropertySetInfo> xRangePropSetInfo;
                XMLTextCharStyleNamesElementExport aCharStylesExport(
                    GetExport(), bIsUICharStyle &&
                                    GetExport().GetTextParagraphExport()
                                        ->GetCharStyleNamesPropInfoCache().hasProperty(
                                                xRangePropSet, xRangePropSetInfo ), bHasAutoStyle,
                    xRangePropSet, gsPropertyCharStyleNames );

                if( bHasStyle )
                {
                    // export <text:span> element
                    GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                                    GetExport().EncodeStyleName( sStyle ) );
                }
            }
            SvXMLElementExport aSpan( GetExport(), bHasStyle,
                                        XML_NAMESPACE_TEXT, XML_SPAN,
                                        false, false);
            if (xBookmark.is())
            {
                GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_NAME, xBookmark->getName());
            }
            Reference< css::text::XFormField > xFormField(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
            if (xFormField.is())
            {
                GetExport().AddAttribute(XML_NAMESPACE_FIELD, XML_TYPE, xFormField->getFieldType());
            }
            GetExport().StartElement(XML_NAMESPACE_FIELD, XML_FIELDMARK, false);
            if (xFormField.is())
            {
                FieldParamExporter(&GetExport(), xFormField->getParameters()).Export();
            }
            GetExport().EndElement(XML_NAMESPACE_FIELD, XML_FIELDMARK, false);
        }
        else
        {
            Reference<XNamed> xBookmark(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
            if (xBookmark.is())
            {
                GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_NAME, xBookmark->getName());
                SvXMLElementExport aElem( GetExport(), !bAutoStyles,
                    XML_NAMESPACE_TEXT, XML_BOOKMARK,
                    false, false );
            }
        }
    }
    else
    {
        Reference<XTextContent> xBookmark(xPropSet->getPropertyValue(gsBookmark), UNO_QUERY);
        if (xBookmark.is())
        {
            Reference<XPropertySet> xRangePropSet(xBookmark->getAnchor(), UNO_QUERY);
            GetExport().GetTextParagraphExport()->Add(XmlStyleFamily::TEXT_TEXT,
                                                        xRangePropSet);
        }
    }
}

void XMLTextParagraphExport::exportSoftPageBreak()
{
    SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_TEXT,
                              XML_SOFT_PAGE_BREAK, false,
                              false );
}

void XMLTextParagraphExport::exportTextLineBreak(
    const uno::Reference<beans::XPropertySet>& xPropSet)
{
    static const XMLTokenEnum aLineBreakClears[] = {
        XML_NONE,
        XML_LEFT,
        XML_RIGHT,
        XML_ALL,
    };

    uno::Reference<text::XTextContent> xLineBreak;
    xPropSet->getPropertyValue(u"LineBreak"_ustr) >>= xLineBreak;
    if (!xLineBreak.is())
    {
        return;
    }

    uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
    if (!xLineBreakProps.is())
    {
        return;
    }

    sal_Int16 eClear{};
    xLineBreakProps->getPropertyValue(u"Clear"_ustr) >>= eClear;
    if (eClear >= 0 && o3tl::make_unsigned(eClear) < SAL_N_ELEMENTS(aLineBreakClears))
    {
        GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_CLEAR,
                                 GetXMLToken(aLineBreakClears[eClear]));
    }
    SvXMLElementExport aElem(GetExport(), XML_NAMESPACE_TEXT, XML_LINE_BREAK,
                             /*bIgnWSOutside=*/false, /*bIgnWSInside=*/false);
}

void XMLTextParagraphExport::exportTextMark(
    const Reference<XPropertySet> & rPropSet,
    const OUString& rProperty,
    const ::xmloff::token::XMLTokenEnum pElements[],
    bool bAutoStyles)
{
    // mib said: "Hau wech!"

    // (Originally, I'd export a span element in case the (book|reference)mark
    //  was formatted. This actually makes a difference in case some pervert
    //  sets a point reference mark in the document and, say, formats it bold.
    //  This basically meaningless formatting will now been thrown away
    //  (aka cleaned up), since mib said: ...                   dvo

    if (bAutoStyles)
        return;

    // name element
    Reference<XNamed> xName(rPropSet->getPropertyValue(rProperty), UNO_QUERY);
    GetExport().AddAttribute(XML_NAMESPACE_TEXT, XML_NAME,
                             xName->getName());

    // start, end, or point-reference?
    sal_Int8 nElement;
    if( *o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsIsCollapsed)) )
    {
        nElement = 0;
    }
    else
    {
        nElement = *o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsIsStart)) ? 1 : 2;
    }

    // bookmark, bookmark-start: xml:id and RDFa for RDF metadata
    if( nElement < 2 ) {
        GetExport().AddAttributeXmlId(xName);
        const uno::Reference<text::XTextContent> xTextContent(
                xName, uno::UNO_QUERY_THROW);
        GetExport().AddAttributesRDFa(xTextContent);
    }

    // bookmark-start: add attributes hidden and condition
    if (nElement == 1)
    {
        Reference<XPropertySet> bkmkProps(rPropSet->getPropertyValue(rProperty), UNO_QUERY);
        Reference<XPropertySetInfo> bkmkPropInfo = bkmkProps->getPropertySetInfo();
        OUString sHidden(u"BookmarkHidden"_ustr);
        if (bkmkPropInfo->hasPropertyByName(sHidden))
        {
            bool bHidden = false;
            bkmkProps->getPropertyValue(sHidden) >>= bHidden;
            if (bHidden)
            {
                GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, u"hidden"_ustr, u"true"_ustr);
                OUString sCondition(u"BookmarkCondition"_ustr);
                if (bkmkPropInfo->hasPropertyByName(sCondition))
                {
                    OUString sBookmarkCondition;
                    bkmkProps->getPropertyValue(sCondition) >>= sBookmarkCondition;
                    GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, u"condition"_ustr, sBookmarkCondition);
                }
            }
        }
    }

    // export element
    assert(pElements != nullptr);
    assert(0 <= nElement && nElement <= 2);
    SvXMLElementExport aElem(GetExport(),
                             XML_NAMESPACE_TEXT, pElements[nElement],
                             false, false);
    // else: no styles. (see above)
}

static bool lcl_txtpara_isBoundAsChar(
        const Reference < XPropertySet > & rPropSet,
        const Reference < XPropertySetInfo > & rPropSetInfo )
{
    bool bIsBoundAsChar = false;
    OUString sAnchorType( u"AnchorType"_ustr  );
    if( rPropSetInfo->hasPropertyByName( sAnchorType ) )
    {
        TextContentAnchorType eAnchor;
        rPropSet->getPropertyValue( sAnchorType ) >>= eAnchor;
        bIsBoundAsChar = TextContentAnchorType_AS_CHARACTER == eAnchor;
    }

    return bIsBoundAsChar;
}

XMLShapeExportFlags XMLTextParagraphExport::addTextFrameAttributes(
    const Reference < XPropertySet >& rPropSet,
    bool bShape,
    basegfx::B2DPoint* pCenter,
    OUString* pMinHeightValue,
    OUString* pMinWidthValue)
{
    XMLShapeExportFlags nShapeFeatures = SEF_DEFAULT;

    // draw:name (#97662#: not for shapes, since those names will be
    // treated in the shape export)
    if( !bShape )
    {
        Reference < XNamed > xNamed( rPropSet, UNO_QUERY );
        if( xNamed.is() )
        {
            OUString sName( xNamed->getName() );
            if( !sName.isEmpty() )
                GetExport().AddAttribute( XML_NAMESPACE_DRAW, XML_NAME,
                                          xNamed->getName() );
        }
    }

    OUStringBuffer sValue;

    // text:anchor-type
    TextContentAnchorType eAnchor = TextContentAnchorType_AT_PARAGRAPH;
    rPropSet->getPropertyValue( gsAnchorType ) >>= eAnchor;
    {
        XMLAnchorTypePropHdl aAnchorTypeHdl;
        OUString sTmp;
        aAnchorTypeHdl.exportXML( sTmp, cpo::uno::Any(eAnchor),
                                  GetExport().GetMM100UnitConverter() );
        GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_ANCHOR_TYPE, sTmp );
    }

    // text:anchor-page-number
    if( TextContentAnchorType_AT_PAGE == eAnchor )
    {
        sal_Int16 nPage = 0;
        rPropSet->getPropertyValue( gsAnchorPageNo ) >>= nPage;
        SAL_WARN_IF(nPage <= 0, "xmloff",
                "ERROR: writing invalid anchor-page-number 0");
        GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_ANCHOR_PAGE_NUMBER,
                                  OUString::number( nPage ) );
    }
    else
    {
        nShapeFeatures |= XMLShapeExportFlags::NO_WS;
    }

    // OD 2004-06-01 #i27691# - correction: no export of svg:x, if object
    // is anchored as-character.
    if ( !bShape &&
         eAnchor != TextContentAnchorType_AS_CHARACTER )
    {
        // svg:x
        sal_Int16 nHoriOrient =  HoriOrientation::NONE;
        rPropSet->getPropertyValue( gsHoriOrient ) >>= nHoriOrient;
        if( HoriOrientation::NONE == nHoriOrient )
        {
            sal_Int32 nPos = 0;
            rPropSet->getPropertyValue( gsHoriOrientPosition ) >>= nPos;
            GetExport().GetMM100UnitConverter().convertMeasureToXML(
                    sValue, nPos );
            GetExport().AddAttribute( XML_NAMESPACE_SVG, XML_X,
                                      sValue.makeStringAndClear() );
            if(nullptr != pCenter)
            {
                // add left edge to Center
                pCenter->setX(pCenter->getX() + nPos);
            }
        }
    }
    else if( TextContentAnchorType_AS_CHARACTER == eAnchor )
        nShapeFeatures = (nShapeFeatures & ~XMLShapeExportFlags::X);

    if( !bShape || TextContentAnchorType_AS_CHARACTER == eAnchor  )
    {
        // svg:y
        sal_Int16 nVertOrient =  VertOrientation::NONE;
        rPropSet->getPropertyValue( gsVertOrient ) >>= nVertOrient;
        if( VertOrientation::NONE == nVertOrient )
        {
            sal_Int32 nPos = 0;
            rPropSet->getPropertyValue( gsVertOrientPosition ) >>= nPos;
            GetExport().GetMM100UnitConverter().convertMeasureToXML(
                    sValue, nPos );
            GetExport().AddAttribute( XML_NAMESPACE_SVG, XML_Y,
                                      sValue.makeStringAndClear() );
            if(nullptr != pCenter)
            {
                // add top edge to Center
                pCenter->setY(pCenter->getY() + nPos);
            }
        }
        if( bShape )
            nShapeFeatures = (nShapeFeatures & ~XMLShapeExportFlags::Y);
    }

    Reference< XPropertySetInfo > xPropSetInfo(rPropSet->getPropertySetInfo());

    bool bSyncWidth = false;
    if (xPropSetInfo->hasPropertyByName(gsIsSyncWidthToHeight))
    {
        bSyncWidth = *o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsIsSyncWidthToHeight));
    }
    sal_Int16 nRelWidth = 0;
    if (!bSyncWidth && xPropSetInfo->hasPropertyByName(gsRelativeWidth))
    {
        rPropSet->getPropertyValue(gsRelativeWidth) >>= nRelWidth;
    }
    bool bSyncHeight = false;
    if (xPropSetInfo->hasPropertyByName(gsIsSyncHeightToWidth))
    {
        bSyncHeight = *o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsIsSyncHeightToWidth));
    }
    sal_Int16 nRelHeight = 0;
    if (!bSyncHeight && xPropSetInfo->hasPropertyByName(gsRelativeHeight))
    {
        rPropSet->getPropertyValue(gsRelativeHeight) >>= nRelHeight;
    }
    awt::Size aLayoutSize;
    if ((nRelWidth > 0 || nRelHeight > 0) && xPropSetInfo->hasPropertyByName(u"LayoutSize"_ustr))
    {
        rPropSet->getPropertyValue(u"LayoutSize"_ustr) >>= aLayoutSize;
    }

    bool bUseLayoutSize = true;
    if (bSyncWidth && bSyncHeight)
    {
        // This is broken, width depends on height and height depends on width. Don't use the
        // invalid layout size we got.
        bUseLayoutSize = false;
    }
    if (aLayoutSize.Width <= 0 || aLayoutSize.Height <= 0)
    {
        // This is broken, Writer frames have a minimal size, see MINFLY.
        bUseLayoutSize = false;
    }

    // svg:width
    sal_Int16 nWidthType = SizeType::FIX;
    if( xPropSetInfo->hasPropertyByName( gsWidthType ) )
    {
        rPropSet->getPropertyValue( gsWidthType ) >>= nWidthType;
    }
    if( xPropSetInfo->hasPropertyByName( gsWidth ) )
    {
        sal_Int32 nWidth =  0;
        // VAR size will be written as zero min-size
        if( SizeType::VARIABLE != nWidthType )
        {
            rPropSet->getPropertyValue( gsWidth ) >>= nWidth;
        }
        GetExport().GetMM100UnitConverter().convertMeasureToXML(sValue, nWidth);
        if( SizeType::FIX != nWidthType )
        {
            assert(pMinWidthValue);
            if (pMinWidthValue)
            {
                *pMinWidthValue = sValue.makeStringAndClear();
            }
        }
        else
        {
            if ((nRelWidth > 0 || bSyncWidth) && bUseLayoutSize)
            {
                // Relative width: write the layout size for the fallback width.
                sValue.setLength(0);
                GetExport().GetMM100UnitConverter().convertMeasureToXML(sValue, aLayoutSize.Width);
            }

            GetExport().AddAttribute( XML_NAMESPACE_SVG, XML_WIDTH,
                                      sValue.makeStringAndClear() );
            if(nullptr != pCenter)
            {
                // add half width to Center
                pCenter->setX(pCenter->getX() + (0.5 * nWidth));
            }
        }
    }
    if( xPropSetInfo->hasPropertyByName( gsIsSyncWidthToHeight ) )
    {
        if( bSyncWidth )
            GetExport().AddAttribute( XML_NAMESPACE_STYLE, XML_REL_WIDTH,
                                      XML_SCALE );
    }
    if( !bSyncWidth && xPropSetInfo->hasPropertyByName( gsRelativeWidth ) )
    {
        SAL_WARN_IF( nRelWidth < 0 || nRelWidth > 254, "xmloff",
                    "Got illegal relative width from API" );
        if( nRelWidth > 0 )
        {
            ::sax::Converter::convertPercent( sValue, nRelWidth );
            GetExport().AddAttribute( XML_NAMESPACE_STYLE, XML_REL_WIDTH,
                                      sValue.makeStringAndClear() );
        }
    }

    // svg:height, fo:min-height or style:rel-height
    sal_Int16 nSizeType = SizeType::FIX;
    if( xPropSetInfo->hasPropertyByName( gsSizeType ) )
    {
        rPropSet->getPropertyValue( gsSizeType ) >>= nSizeType;
    }
    if( xPropSetInfo->hasPropertyByName( gsHeight ) )
    {
        sal_Int32 nHeight =  0;
        if( SizeType::VARIABLE != nSizeType )
        {
            rPropSet->getPropertyValue( gsHeight ) >>= nHeight;
        }
        GetExport().GetMM100UnitConverter().convertMeasureToXML( sValue,
                                                            nHeight );
        if( SizeType::FIX != nSizeType && 0==nRelHeight && !bSyncHeight &&
             pMinHeightValue )
        {
            *pMinHeightValue = sValue.makeStringAndClear();
        }
        else
        {
            if ((nRelHeight > 0 || bSyncHeight) && bUseLayoutSize)
            {
                // Relative height: write the layout size for the fallback height.
                sValue.setLength(0);
                GetExport().GetMM100UnitConverter().convertMeasureToXML(sValue, aLayoutSize.Height);
            }

            GetExport().AddAttribute( XML_NAMESPACE_SVG, XML_HEIGHT,
                                      sValue.makeStringAndClear() );
            if(nullptr != pCenter)
            {
                // add half height to Center
                pCenter->setY(pCenter->getY() + (0.5 * nHeight));
            }
        }
    }
    if( bSyncHeight )
    {
        GetExport().AddAttribute( XML_NAMESPACE_STYLE, XML_REL_HEIGHT,
                SizeType::MIN == nSizeType ? XML_SCALE_MIN : XML_SCALE );

    }
    else if( nRelHeight > 0 )
    {
        ::sax::Converter::convertPercent( sValue, nRelHeight );
        if( SizeType::MIN == nSizeType )
        {
            assert(pMinHeightValue);
            if (pMinHeightValue)
            {
                *pMinHeightValue = sValue.makeStringAndClear();
            }
        }
        else
            GetExport().AddAttribute( XML_NAMESPACE_STYLE, XML_REL_HEIGHT,
                                      sValue.makeStringAndClear() );
    }

    OUString sZOrder( u"ZOrder"_ustr  );
    if( xPropSetInfo->hasPropertyByName( sZOrder ) )
    {
        sal_Int32 nZIndex{-1};
        if (rPropSet->getPropertyValue(sZOrder) >>= nZIndex)
        {
            assert(0 <= nZIndex);
            GetExport().AddAttribute( XML_NAMESPACE_DRAW, XML_ZINDEX,
                                      OUString::number( nZIndex ) );
        }
    }

    if (xPropSetInfo->hasPropertyByName(u"IsSplitAllowed"_ustr)
        && rPropSet->getPropertyValue(u"IsSplitAllowed"_ustr).get<bool>())
    {
        GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_MAY_BREAK_BETWEEN_PAGES, XML_TRUE);
    }

    return nShapeFeatures;
}

void XMLTextParagraphExport::exportAnyTextFrame(
        const Reference < XTextContent > & rTxtCntnt,
        FrameType eType,
        bool bAutoStyles,
        bool bIsProgress,
        bool bExportContent,
        const Reference < XPropertySet > *pRangePropSet)
{
    Reference < XPropertySet > xPropSet( rTxtCntnt, UNO_QUERY );

    if( bAutoStyles )
    {
        if( FrameType::Embedded == eType )
            _collectTextEmbeddedAutoStyles( xPropSet );
        // No text frame style for shapes (#i28745#)
        else if ( FrameType::Shape != eType )
            Add( XmlStyleFamily::TEXT_FRAME, xPropSet );

        if( pRangePropSet && lcl_txtpara_isBoundAsChar( xPropSet,
                                            xPropSet->getPropertySetInfo() ) )
            Add( XmlStyleFamily::TEXT_TEXT, *pRangePropSet );

        switch( eType )
        {
        case FrameType::Text:
            {
                // frame bound frames
                if ( bExportContent )
                {
                    Reference < XTextFrame > xTxtFrame( rTxtCntnt, UNO_QUERY );
                    bool bAlreadySeen = !maFrameRecurseGuard.insert(xTxtFrame).second;
                    if (bAlreadySeen)
                    {
                        SAL_WARN("xmloff", "loop in frame export, ditching");
                    }
                    else
                    {
                        comphelper::ScopeGuard const g([this, xTxtFrame]() {
                            maFrameRecurseGuard.erase(xTxtFrame);
                        });
                        Reference < XText > xTxt(xTxtFrame->getText());
                        exportFrameFrames( true, bIsProgress, xTxtFrame );
                        exportText( xTxt, bAutoStyles, bIsProgress, true );
                    }
                }
            }
            break;
        case FrameType::Shape:
            {
                Reference < XShape > xShape( rTxtCntnt, UNO_QUERY );
                bool bAlreadySeen = !maShapeRecurseGuard.insert(xShape).second;
                if (bAlreadySeen)
                {
                    SAL_WARN("xmloff", "loop in shape export, ditching");
                }
                else
                {
                    comphelper::ScopeGuard const g([this, xShape]() {
                        maShapeRecurseGuard.erase(xShape);
                    });
                    GetExport().GetShapeExport()->collectShapeAutoStyles( xShape );
                }
            }
            break;
        default:
            break;
        }
    }
    else
    {
        Reference< XPropertySetInfo > xPropSetInfo(xPropSet->getPropertySetInfo());
        {
            bool bAddCharStyles = pRangePropSet &&
                lcl_txtpara_isBoundAsChar( xPropSet, xPropSetInfo );

            bool bIsUICharStyle;
            bool bHasAutoStyle = false;

            OUString sStyle;

            if( bAddCharStyles )
                sStyle = FindTextStyle( *pRangePropSet, bIsUICharStyle, bHasAutoStyle );
            else
                bIsUICharStyle = false;

            bool bDoSomething = bIsUICharStyle
                && m_aCharStyleNamesPropInfoCache.hasProperty( *pRangePropSet );
            XMLTextCharStyleNamesElementExport aCharStylesExport(
                GetExport(), bDoSomething, bHasAutoStyle,
                bDoSomething ? *pRangePropSet : Reference<XPropertySet>(),
                gsCharStyleNames );

            if( !sStyle.isEmpty() )
                GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                                  GetExport().EncodeStyleName( sStyle ) );
            {
                SvXMLElementExport aElem( GetExport(), !sStyle.isEmpty(),
                    XML_NAMESPACE_TEXT, XML_SPAN, false, false );
                {
                    SvXMLElementExport aElement( GetExport(),
                        FrameType::Shape != eType &&
                            HyperlinkData(xPropSet).addHyperlinkAttributes(GetExport()),
                        XML_NAMESPACE_DRAW, XML_A, false, false );
                    switch( eType )
                    {
                    case FrameType::Text:
                        _exportTextFrame( xPropSet, xPropSetInfo, bIsProgress );
                        break;
                    case FrameType::Graphic:
                        _exportTextGraphic( xPropSet, xPropSetInfo );
                        break;
                    case FrameType::Embedded:
                        _exportTextEmbedded( xPropSet, xPropSetInfo );
                        break;
                    case FrameType::Shape:
                        {
                            Reference < XShape > xShape( rTxtCntnt, UNO_QUERY );
                            XMLShapeExportFlags nFeatures =
                                addTextFrameAttributes( xPropSet, true );
                            GetExport().GetShapeExport()
                                ->exportShape( xShape, nFeatures );
                        }
                        break;
                    }
                }
            }
        }
    }
}

void XMLTextParagraphExport::_exportTextFrame(
        const Reference < XPropertySet > & rPropSet,
        const Reference < XPropertySetInfo > & rPropSetInfo,
        bool bIsProgress )
{
    Reference < XTextFrame > xTxtFrame( rPropSet, UNO_QUERY );
    Reference < XText > xTxt(xTxtFrame->getText());

    OUString sStyle;
    if( rPropSetInfo->hasPropertyByName( gsFrameStyleName ) )
    {
        rPropSet->getPropertyValue( gsFrameStyleName ) >>= sStyle;
    }

    OUString aMinHeightValue;
    OUString sMinWidthValue;
    OUString sAutoStyle = Find( XmlStyleFamily::TEXT_FRAME, rPropSet, sStyle );
    if ( sAutoStyle.isEmpty() )
        sAutoStyle = sStyle;
    if( !sAutoStyle.isEmpty() )
        GetExport().AddAttribute( XML_NAMESPACE_DRAW, XML_STYLE_NAME,
                              GetExport().EncodeStyleName( sAutoStyle ) );
    addTextFrameAttributes(rPropSet, false, nullptr, &aMinHeightValue, &sMinWidthValue);

    SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_DRAW,
                              XML_FRAME, false, true );

    if( !aMinHeightValue.isEmpty() )
        GetExport().AddAttribute( XML_NAMESPACE_FO, XML_MIN_HEIGHT,
                                  aMinHeightValue );

    if (!sMinWidthValue.isEmpty())
    {
        GetExport().AddAttribute( XML_NAMESPACE_FO, XML_MIN_WIDTH,
                                  sMinWidthValue );
    }

    // draw:chain-next-name
    if( rPropSetInfo->hasPropertyByName( gsChainNextName ) )
    {
        OUString sNext;
        if( (rPropSet->getPropertyValue( gsChainNextName ) >>= sNext) && !sNext.isEmpty() )
            GetExport().AddAttribute( XML_NAMESPACE_DRAW,
                                      XML_CHAIN_NEXT_NAME,
                                      sNext );
    }

    {
        SvXMLElementExport aElement( GetExport(), XML_NAMESPACE_DRAW,
                                  XML_TEXT_BOX, true, true );

        // frames bound to frame
        exportFrameFrames( false, bIsProgress, xTxtFrame );

        exportText( xTxt, false, bIsProgress, true );
    }

    // script:events
    Reference<XEventsSupplier> xEventsSupp( xTxtFrame, UNO_QUERY );
    GetExport().GetEventExport().Export(xEventsSupp);

    // image map
    GetExport().GetImageMapExport().Export( rPropSet );

    // svg:title and svg:desc (#i73249#)
    exportTitleAndDescription( rPropSet, rPropSetInfo );
}

void XMLTextParagraphExport::exportContour(
    const Reference < XPropertySet > & rPropSet,
    const Reference < XPropertySetInfo > & rPropSetInfo )
{
    if( !rPropSetInfo->hasPropertyByName( gsContourPolyPolygon ) )
    {
        return;
    }

    PointSequenceSequence aSourcePolyPolygon;
    rPropSet->getPropertyValue( gsContourPolyPolygon ) >>= aSourcePolyPolygon;
    const basegfx::B2DPolyPolygon aPolyPolygon(
        basegfx::utils::UnoPointSequenceSequenceToB2DPolyPolygon(
            aSourcePolyPolygon));
    const sal_uInt32 nPolygonCount(aPolyPolygon.count());

    if(!nPolygonCount)
    {
        return;
    }

    const basegfx::B2DRange aPolyPolygonRange(aPolyPolygon.getB2DRange());
    bool bPixel(false);

    if( rPropSetInfo->hasPropertyByName( gsIsPixelContour ) )
    {
        bPixel = *o3tl::doAccess<bool>(rPropSet->getPropertyValue( gsIsPixelContour ));
    }

    // svg: width
    OUStringBuffer aStringBuffer( 10 );

    if(bPixel)
    {
        ::sax::Converter::convertMeasurePx(aStringBuffer, basegfx::fround(aPolyPolygonRange.getWidth()));
    }
    else
    {
        GetExport().GetMM100UnitConverter().convertMeasureToXML(aStringBuffer, basegfx::fround(aPolyPolygonRange.getWidth()));
    }

    GetExport().AddAttribute(XML_NAMESPACE_SVG, XML_WIDTH, aStringBuffer.makeStringAndClear());

    // svg: height
    if(bPixel)
    {
        ::sax::Converter::convertMeasurePx(aStringBuffer, basegfx::fround(aPolyPolygonRange.getHeight()));
    }
    else
    {
        GetExport().GetMM100UnitConverter().convertMeasureToXML(aStringBuffer, basegfx::fround(aPolyPolygonRange.getHeight()));
    }

    GetExport().AddAttribute(XML_NAMESPACE_SVG, XML_HEIGHT, aStringBuffer.makeStringAndClear());

    // svg:viewbox
    SdXMLImExViewBox aViewBox(0.0, 0.0, aPolyPolygonRange.getWidth(), aPolyPolygonRange.getHeight());
    GetExport().AddAttribute(XML_NAMESPACE_SVG, XML_VIEWBOX, aViewBox.GetExportString());
    enum XMLTokenEnum eElem = XML_TOKEN_INVALID;

    if(1 == nPolygonCount )
    {
        // simple polygon shape, can be written as svg:points sequence
        const OUString aPointString(
            basegfx::utils::exportToSvgPoints(
                aPolyPolygon.getB2DPolygon(0)));

        // write point array
        GetExport().AddAttribute(XML_NAMESPACE_DRAW, XML_POINTS, aPointString);
        eElem = XML_CONTOUR_POLYGON;
    }
    else
    {
        // polypolygon, needs to be written as a svg:path sequence
        const OUString aPolygonString(
            basegfx::utils::exportToSvgD(
                aPolyPolygon,
                true,           // bUseRelativeCoordinates
                false,          // bDetectQuadraticBeziers: not used in old, but maybe activated now
                true));         // bHandleRelativeNextPointCompatible

        // write point array
        GetExport().AddAttribute( XML_NAMESPACE_SVG, XML_D, aPolygonString);
        eElem = XML_CONTOUR_PATH;
    }

    if( rPropSetInfo->hasPropertyByName( gsIsAutomaticContour ) )
    {
        bool bTmp = *o3tl::doAccess<bool>(rPropSet->getPropertyValue(
                                            gsIsAutomaticContour ));
        GetExport().AddAttribute( XML_NAMESPACE_DRAW,
                      XML_RECREATE_ON_EDIT, bTmp ? XML_TRUE : XML_FALSE );
    }

    // write object now
    SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_DRAW, eElem,
                              true, true );
}

void XMLTextParagraphExport::_exportTextGraphic(
        const Reference < XPropertySet > & rPropSet,
        const Reference < XPropertySetInfo > & rPropSetInfo )
{
    // skip objects anchored at page in master documents,
    // if they are imported from the subdocuments
    TextContentAnchorType eAnchor;
    rPropSet->getPropertyValue(u"AnchorType"_ustr) >>= eAnchor;
    if( TextContentAnchorType_AT_PAGE == eAnchor )
    {
        Reference<XServiceInfo> xServiceInfo(GetExport().GetModel(), UNO_QUERY);
        if( xServiceInfo->supportsService(u"com.sun.star.text.GlobalDocument"_ustr) )
        {
            Reference<XNamed> xNamed( rPropSet, UNO_QUERY );
            if( xNamed.is() && xNamed->getName().indexOf(" (file://") > -1 )
                return;
        }
    }

    OUString sStyle;
    if( rPropSetInfo->hasPropertyByName( gsFrameStyleName ) )
    {
        rPropSet->getPropertyValue( gsFrameStyleName ) >>= sStyle;
    }

    OUString sAutoStyle = Find( XmlStyleFamily::TEXT_FRAME, rPropSet, sStyle );
    if ( sAutoStyle.isEmpty() )
        sAutoStyle = sStyle;
    if( !sAutoStyle.isEmpty() )
        GetExport().AddAttribute( XML_NAMESPACE_DRAW, XML_STYLE_NAME,
                                  GetExport().EncodeStyleName( sAutoStyle ) );

    // check if we need to use svg:transform
    sal_Int16 nRotation(0);
    rPropSet->getPropertyValue( gsGraphicRotation ) >>= nRotation;
    const bool bUseRotation(0 != nRotation);
    basegfx::B2DPoint aCenter(0.0, 0.0);

    // add TextFrame attributes like svg:x/y/width/height, also get back
    // object's center point if rotation is used and has to be exported
    addTextFrameAttributes(rPropSet, false, bUseRotation ? &aCenter : nullptr);

    // svg:transform
    if(bUseRotation)
    {
        // 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;

        // Convert from 10th degree integer to deg.
        // CAUTION: internal rotation is classically mathematically 'wrong' defined by ignoring that
        // we have a right-handed coordinate system, so need to correct this by mirroring
        // the rotation to get the correct transformation. See also case XML_TOK_TEXT_FRAME_TRANSFORM
        // in XMLTextFrameContext_Impl::XMLTextFrameContext_Impl and #i78696#
        // CAUTION-II: due to tdf#115782 it is better for current ODF to indeed write it with the wrong
        // orientation as in all other cases - ARGH! We will need to correct this in future ODF ASAP!
        const double fRotate(basegfx::deg2rad<10>(nRotation));

        // transform to rotation center which is the object's center
        aSdXMLImExTransform2D.AddTranslate(-aCenter);

        // add rotation itself
        // tdf#115529 but correct value modulo 2PI to have it positive and in the range of [0.0 .. 2PI[
        aSdXMLImExTransform2D.AddRotate(basegfx::normalizeToRange(fRotate, 2 * M_PI));

        // back-transform after rotation
        aSdXMLImExTransform2D.AddTranslate(aCenter);

        // 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.
        GetExport().AddAttribute(
            XML_NAMESPACE_DRAW,
            XML_TRANSFORM,
            aSdXMLImExTransform2D.GetExportString(GetExport().GetMM100UnitConverter()));
    }

    // original content
    SvXMLElementExport aElem(GetExport(), XML_NAMESPACE_DRAW, XML_FRAME, false, true);

    OUString sOutMimeType;
    {
        // xlink:href
        uno::Reference<graphic::XGraphic> xGraphic;
        rPropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;

        OUString sInternalURL;

        if (xGraphic.is())
        {
            sInternalURL = GetExport().AddEmbeddedXGraphic(xGraphic, sOutMimeType);
        }

        // If there still is no url, then graphic is empty
        if (!sInternalURL.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_HREF, sInternalURL);
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE);
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_SHOW, XML_EMBED);
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_ACTUATE, XML_ONLOAD);
        }

        // draw:filter-name
        OUString sGrfFilter;
        rPropSet->getPropertyValue( gsGraphicFilter ) >>= sGrfFilter;
        if( !sGrfFilter.isEmpty() )
            GetExport().AddAttribute( XML_NAMESPACE_DRAW, XML_FILTER_NAME,
                                      sGrfFilter );

        if (GetExport().getSaneDefaultVersion() > SvtSaveOptions::ODFSVER_012)
        {
            if (sOutMimeType.isEmpty())
            {
                GetExport().GetGraphicMimeTypeFromStream(xGraphic, sOutMimeType);
            }
            if (!sOutMimeType.isEmpty())
            {   // ODF 1.3 OFFICE-3943
                GetExport().AddAttribute(
                    SvtSaveOptions::ODFSVER_013 <= GetExport().getSaneDefaultVersion()
                        ? XML_NAMESPACE_DRAW
                        : XML_NAMESPACE_LO_EXT,
                    u"mime-type"_ustr, sOutMimeType);
            }
        }


        // optional office:binary-data
        if (xGraphic.is())
        {
            SvXMLElementExport aElement(GetExport(), XML_NAMESPACE_DRAW, XML_IMAGE, false, true );
            GetExport().AddEmbeddedXGraphicAsBase64(xGraphic);
        }
    }

    // SVG has been in ODF long enough that current readers handle it,
    // so the PNG fallback is no longer worth carrying. ODF 1.4 is the
    // cautious cutoff: from 1.4 onwards we drop the fallback, older
    // targets still get it.
    const bool bSkipSvgFallback
        = sOutMimeType == "image/svg+xml"
          && GetExport().getSaneDefaultVersion() >= SvtSaveOptions::ODFSVER_014;

    const bool bAddReplacementImages
        = !bSkipSvgFallback
          && officecfg::Office::Common::Save::Graphic::AddReplacementImages::get();

    if (bAddReplacementImages)
    {
        // replacement graphic for backwards compatibility, but
        // only for SVG and metafiles currently
        uno::Reference<graphic::XGraphic> xReplacementGraphic;
        rPropSet->getPropertyValue(u"ReplacementGraphic"_ustr) >>= xReplacementGraphic;

        OUString sInternalURL;
        OUString sReplacementMimeType;

        //Resolves: fdo#62461 put preferred image first above, followed by
        //fallback here
        if (xReplacementGraphic.is())
        {
            sInternalURL = GetExport().AddEmbeddedXGraphic(xReplacementGraphic, sReplacementMimeType);
        }

        // If there is no url, then graphic is empty
        if (!sInternalURL.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_HREF, sInternalURL);
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE);
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_SHOW, XML_EMBED);
            GetExport().AddAttribute(XML_NAMESPACE_XLINK, XML_ACTUATE, XML_ONLOAD);
        }

        if (GetExport().getSaneDefaultVersion() > SvtSaveOptions::ODFSVER_012)
        {
            if (sReplacementMimeType.isEmpty())
            {
                GetExport().GetGraphicMimeTypeFromStream(xReplacementGraphic, sReplacementMimeType);
            }
            if (!sReplacementMimeType.isEmpty())
            {   // ODF 1.3 OFFICE-3943
                GetExport().AddAttribute(
                    SvtSaveOptions::ODFSVER_013 <= GetExport().getSaneDefaultVersion()
                        ? XML_NAMESPACE_DRAW
                        : XML_NAMESPACE_LO_EXT,
                    u"mime-type"_ustr, sReplacementMimeType);
            }
        }


        // optional office:binary-data
        if (xReplacementGraphic.is())
        {
            SvXMLElementExport aElement(GetExport(), XML_NAMESPACE_DRAW, XML_IMAGE, true, true);
            GetExport().AddEmbeddedXGraphicAsBase64(xReplacementGraphic);
        }
    }

    // script:events
    Reference<XEventsSupplier> xEventsSupp( rPropSet, UNO_QUERY );
    GetExport().GetEventExport().Export(xEventsSupp);

    // image map
    GetExport().GetImageMapExport().Export( rPropSet );

    // svg:title and svg:desc (#i73249#)
    exportTitleAndDescription( rPropSet, rPropSetInfo );

    // draw:contour
    exportContour( rPropSet, rPropSetInfo );
}

void XMLTextParagraphExport::_collectTextEmbeddedAutoStyles(const Reference < XPropertySet > & )
{
    SAL_WARN( "xmloff", "no API implementation available" );
}

void XMLTextParagraphExport::_exportTextEmbedded(
        const Reference < XPropertySet > &,
        const Reference < XPropertySetInfo > & )
{
    SAL_WARN( "xmloff", "no API implementation available" );
}

void XMLTextParagraphExport::exportEvents( const Reference < XPropertySet > & rPropSet )
{
    // script:events
    Reference<XEventsSupplier> xEventsSupp( rPropSet, UNO_QUERY );
    GetExport().GetEventExport().Export(xEventsSupp);

    // image map
    if (rPropSet->getPropertySetInfo()->hasPropertyByName(u"ImageMap"_ustr))
        GetExport().GetImageMapExport().Export( rPropSet );
}

// Implement Title/Description Elements UI (#i73249#)
void XMLTextParagraphExport::exportTitleAndDescription(
        const Reference < XPropertySet > & rPropSet,
        const Reference < XPropertySetInfo > & rPropSetInfo )
{
    // svg:title
    if( rPropSetInfo->hasPropertyByName( gsTitle ) )
    {
        OUString sObjTitle;
        rPropSet->getPropertyValue( gsTitle ) >>= sObjTitle;
        if( !sObjTitle.isEmpty() )
        {
            SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_SVG,
                                      XML_TITLE, true, false );
            GetExport().Characters( sObjTitle );
        }
    }

    // svg:description
    if( rPropSetInfo->hasPropertyByName( gsDescription ) )
    {
        OUString sObjDesc;
        rPropSet->getPropertyValue( gsDescription ) >>= sObjDesc;
        if( !sObjDesc.isEmpty() )
        {
            SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_SVG,
                                      XML_DESC, true, false );
            GetExport().Characters( sObjDesc );
        }
    }
}

void XMLTextParagraphExport::exportTextRangeSpan(
    const css::uno::Reference< css::text::XTextRange > & rTextRange,
    Reference< XPropertySet > const & xPropSet,
    Reference < XPropertySetInfo > & xPropSetInfo,
    const bool bIsUICharStyle,
    const bool bHasAutoStyle,
    const OUString& sStyle,
    bool& rPrevCharIsSpace,
    FieldmarkType& openFieldMark )
{
    XMLTextCharStyleNamesElementExport aCharStylesExport(
            GetExport(),
            bIsUICharStyle && m_aCharStyleNamesPropInfoCache.hasProperty( xPropSet, xPropSetInfo ),
            bHasAutoStyle,
            xPropSet,
            gsCharStyleNames );

    if ( !sStyle.isEmpty() )
    {
        GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME, GetExport().EncodeStyleName( sStyle ) );
    }
    {
        SvXMLElementExport aElement( GetExport(), !sStyle.isEmpty(), XML_NAMESPACE_TEXT, XML_SPAN, false, false );
        const OUString aText( rTextRange->getString() );
        SvXMLElementExport aElem2( GetExport(), TEXT == openFieldMark,
            XML_NAMESPACE_TEXT, XML_TEXT_INPUT,
            false, false );
        exportCharacterData(aText, rPrevCharIsSpace);
        openFieldMark = NONE;
    }
}

void XMLTextParagraphExport::exportTextRange(
        const Reference< XTextRange > & rTextRange,
        bool bAutoStyles,
        bool& rPrevCharIsSpace,
        FieldmarkType& openFieldMark )
{
    Reference< XPropertySet > xPropSet( rTextRange, UNO_QUERY );
    if ( bAutoStyles )
    {
        Add( XmlStyleFamily::TEXT_TEXT, xPropSet );
    }
    else
    {
        bool bIsUICharStyle = false;
        bool bHasAutoStyle = false;
        const OUString sStyle(
            FindTextStyle( xPropSet, bIsUICharStyle, bHasAutoStyle ) );

        Reference < XPropertySetInfo > xPropSetInfo;
        exportTextRangeSpan( rTextRange, xPropSet, xPropSetInfo, bIsUICharStyle, bHasAutoStyle, sStyle, rPrevCharIsSpace, openFieldMark );
    }
}

void XMLTextParagraphExport::exportCharacterData(const OUString& rText,
                                           bool& rPrevCharIsSpace )
{
    sal_Int32 nExpStartPos = 0;
    sal_Int32 nEndPos = rText.getLength();
    sal_Int32 nSpaceChars = 0;
    for( sal_Int32 nPos = 0; nPos < nEndPos; nPos++ )
    {
        sal_Unicode cChar = rText[nPos];
        bool bExpCharAsText = true;
        bool bExpCharAsElement = false;
        bool bCurrCharIsSpace = false;
        switch( cChar )
        {
        case 0x0009:    // Tab
        case 0x000A:    // LF
            // These characters are exported as text.
            bExpCharAsElement = true;
            bExpCharAsText = false;
            break;
        case 0x000D:
            break;  // legal character
        case 0x0020:    // Blank
            if( rPrevCharIsSpace )
            {
                // If the previous character is a space character,
                // too, export a special space element.
                bExpCharAsText = false;
            }
            bCurrCharIsSpace = true;
            break;
        default:
            if( cChar < 0x0020 )
            {
#ifdef DBG_UTIL
                OSL_ENSURE( txtparae_bContainsIllegalCharacters ||
                            cChar >= 0x0020,
                            "illegal character in text content" );
                txtparae_bContainsIllegalCharacters = true;
#endif
                bExpCharAsText = false;
            }
            break;
        }

        // If the current character is not exported as text
           // the text that has not been exported by now has to be exported now.
        if( nPos > nExpStartPos && !bExpCharAsText )
        {
            SAL_WARN_IF( 0 != nSpaceChars, "xmloff", "pending spaces" );
            OUString sExp( rText.copy( nExpStartPos, nPos - nExpStartPos ) );
            GetExport().Characters( sExp );
            nExpStartPos = nPos;
        }

        // If there are spaces left that have not been exported and the
        // current character is not a space , the pending spaces have to be
        // exported now.
        if( nSpaceChars > 0 && !bCurrCharIsSpace )
        {
            SAL_WARN_IF( nExpStartPos != nPos, "xmloff", " pending characters" );

            if( nSpaceChars > 1 )
            {
                GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_C,
                              OUString::number(nSpaceChars) );
            }

            SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_TEXT,
                                      XML_S, false, false );

            nSpaceChars = 0;
        }

        // If the current character has to be exported as a special
        // element, the element will be exported now.
        if( bExpCharAsElement )
        {
            switch( cChar )
            {
            case 0x0009:    // Tab
                {
                    SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_TEXT,
                                              XML_TAB, false,
                                              false );
                }
                break;
            case 0x000A:    // LF
                {
                    SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_TEXT,
                                              XML_LINE_BREAK, false,
                                              false );
                }
                break;
            }
        }

        // If the current character is a space, and the previous one
        // is a space, too, the number of pending spaces is incremented
        // only.
        if( bCurrCharIsSpace && rPrevCharIsSpace )
            nSpaceChars++;
        rPrevCharIsSpace = bCurrCharIsSpace;

        // If the current character is not exported as text, the start
        // position for text is the position behind the current position.
        if( !bExpCharAsText )
        {
            SAL_WARN_IF( nExpStartPos != nPos, "xmloff", "wrong export start pos" );
            nExpStartPos = nPos+1;
        }
    }

    if( nExpStartPos < nEndPos )
    {
        SAL_WARN_IF( 0 != nSpaceChars, "xmloff", " pending spaces " );
        OUString sExp( rText.copy( nExpStartPos, nEndPos - nExpStartPos ) );
        GetExport().Characters( sExp );
    }

    // If there are some spaces left, they have to be exported now.
    if( nSpaceChars > 0 )
    {
        if( nSpaceChars > 1 )
        {
            GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_C,
                          OUString::number(nSpaceChars) );
        }

        SvXMLElementExport aElem( GetExport(), XML_NAMESPACE_TEXT, XML_S,
                                  false, false );
    }
}

void XMLTextParagraphExport::exportTextDeclarations()
{
    m_pFieldExport->ExportFieldDeclarations();

    // get XPropertySet from the document and ask for AutoMarkFileURL.
    // If it exists, export the auto-mark-file element.
    Reference<XPropertySet> xPropertySet( GetExport().GetModel(), UNO_QUERY );
    if (!xPropertySet.is())
        return;

    OUString sUrl;
    OUString sIndexAutoMarkFileURL(
        u"IndexAutoMarkFileURL"_ustr);
    if (!xPropertySet->getPropertySetInfo()->hasPropertyByName(
        sIndexAutoMarkFileURL))
        return;

    xPropertySet->getPropertyValue(sIndexAutoMarkFileURL) >>= sUrl;
    if (!sUrl.isEmpty())
    {
        GetExport().AddAttribute( XML_NAMESPACE_XLINK, XML_HREF,
                                  GetExport().GetRelativeReference(sUrl) );
        SvXMLElementExport aAutoMarkElement(
            GetExport(), XML_NAMESPACE_TEXT,
            XML_ALPHABETICAL_INDEX_AUTO_MARK_FILE,
            true, true );
    }
}

void XMLTextParagraphExport::exportTextDeclarations(
    const Reference<XText> & rText )
{
    m_pFieldExport->ExportFieldDeclarations(rText);
}

void XMLTextParagraphExport::exportUsedDeclarations()
{
    m_pFieldExport->SetExportOnlyUsedFieldDeclarations( false/*bOnlyUsed*/ );
}

void XMLTextParagraphExport::exportTrackedChanges(bool bAutoStyles)
{
    if (nullptr != m_pRedlineExport)
        m_pRedlineExport->ExportChangesList( bAutoStyles );
}

void XMLTextParagraphExport::exportTrackedChanges(
    const Reference<XText> & rText,
    bool bAutoStyle)
{
    if (nullptr != m_pRedlineExport)
        m_pRedlineExport->ExportChangesList(rText, bAutoStyle);
}

void XMLTextParagraphExport::recordTrackedChangesForXText(
    const Reference<XText> & rText )
{
    if (nullptr != m_pRedlineExport)
        m_pRedlineExport->SetCurrentXText(rText);
}

void XMLTextParagraphExport::recordTrackedChangesNoXText()
{
    if (nullptr != m_pRedlineExport)
        m_pRedlineExport->SetCurrentXText();
}

void XMLTextParagraphExport::exportTableAutoStyles() {}

void XMLTextParagraphExport::exportTextAutoStyles()
{
    // tdf#135942: do not collect styles during their export: this may modify iterated containers
    mbCollected = true;
    exportTableAutoStyles();

    GetAutoStylePool().exportXML( XmlStyleFamily::TEXT_PARAGRAPH );

    GetAutoStylePool().exportXML( XmlStyleFamily::TEXT_TEXT );

    GetAutoStylePool().exportXML( XmlStyleFamily::TEXT_FRAME );

    GetAutoStylePool().exportXML( XmlStyleFamily::TEXT_SECTION );

    GetAutoStylePool().exportXML( XmlStyleFamily::TEXT_RUBY );

    maListAutoPool.exportXML();
}

void XMLTextParagraphExport::exportRuby(
    const Reference<XPropertySet> & rPropSet,
    bool bAutoStyles )
{
    // early out: a collapsed ruby makes no sense
    if (*o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsIsCollapsed)))
        return;

    // start value ?
    bool bStart = *o3tl::doAccess<bool>(rPropSet->getPropertyValue(gsIsStart));

    if (bAutoStyles)
    {
        // ruby auto styles
        if (bStart)
            Add( XmlStyleFamily::TEXT_RUBY, rPropSet );
    }
    else
    {
        if (bStart)
        {
            // ruby start

            // we can only start a ruby if none is open
            assert(!m_bOpenRuby && "Can't open a ruby inside of ruby!");
            if( m_bOpenRuby )
                return;

            // save ruby text + ruby char style
            rPropSet->getPropertyValue(gsRubyText) >>= m_sOpenRubyText;
            rPropSet->getPropertyValue(gsRubyCharStyleName) >>= m_sOpenRubyCharStyle;

            // ruby style
            GetExport().CheckAttrList();
            OUString sStyleName(Find(XmlStyleFamily::TEXT_RUBY, rPropSet, u""_ustr));
            SAL_WARN_IF(sStyleName.isEmpty(), "xmloff", "Can't find ruby style!");
            GetExport().AddAttribute(XML_NAMESPACE_TEXT,
                                     XML_STYLE_NAME, sStyleName);

            // export <text:ruby> and <text:ruby-base> start elements
            GetExport().StartElement( XML_NAMESPACE_TEXT, XML_RUBY, false);
            GetExport().ClearAttrList();
            GetExport().StartElement( XML_NAMESPACE_TEXT, XML_RUBY_BASE,
                                      false );
            m_bOpenRuby = true;
        }
        else
        {
            // ruby end

            // check for an open ruby
            assert(m_bOpenRuby && "Can't close a ruby if none is open!");
            if( !m_bOpenRuby )
                return;

            // close <text:ruby-base>
            GetExport().EndElement(XML_NAMESPACE_TEXT, XML_RUBY_BASE,
                                   false);

            // write the ruby text (with char style)
            {
                if (!m_sOpenRubyCharStyle.isEmpty())
                    GetExport().AddAttribute(
                        XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                        GetExport().EncodeStyleName( m_sOpenRubyCharStyle) );

                SvXMLElementExport aRubyElement(
                    GetExport(), XML_NAMESPACE_TEXT, XML_RUBY_TEXT,
                    false, false);

                GetExport().Characters(m_sOpenRubyText);
            }

            // and finally, close the ruby
            GetExport().EndElement(XML_NAMESPACE_TEXT, XML_RUBY, false);
            m_bOpenRuby = false;
        }
    }
}

void XMLTextParagraphExport::exportMeta(
    const Reference<XPropertySet> & i_xPortion,
    bool i_bAutoStyles, bool i_isProgress, bool & rPrevCharIsSpace)
{
    bool doExport(!i_bAutoStyles); // do not export element if autostyles
    // check version >= 1.2
    switch (GetExport().getSaneDefaultVersion()) {
        case SvtSaveOptions::ODFSVER_011: // fall through
        case SvtSaveOptions::ODFSVER_010: doExport = false; break;
        default: break;
    }

    const Reference< XTextContent > xTextContent(
            i_xPortion->getPropertyValue(u"InContentMetadata"_ustr), UNO_QUERY_THROW);
    const Reference< XEnumerationAccess > xEA( xTextContent, UNO_QUERY_THROW );
    const Reference< XEnumeration > xTextEnum( xEA->createEnumeration() );

    if (doExport)
    {
        const Reference<rdf::XMetadatable> xMeta(xTextContent, UNO_QUERY_THROW);

        // text:meta with neither xml:id nor RDFa is invalid
        xMeta->ensureMetadataReference();

        // xml:id and RDFa for RDF metadata
        GetExport().AddAttributeXmlId(xMeta);
        GetExport().AddAttributesRDFa(xTextContent);
    }

    SvXMLElementExport aElem( GetExport(), doExport,
        XML_NAMESPACE_TEXT, XML_META, false, false );

    // recurse to export content
    exportTextRangeEnumeration(xTextEnum, i_bAutoStyles, i_isProgress, rPrevCharIsSpace);
}

void XMLTextParagraphExport::ExportContentControl(
    const uno::Reference<beans::XPropertySet>& xPortion, bool bAutoStyles, bool isProgress,
    bool& rPrevCharIsSpace)
{
    // Do not export the element in the autostyle case.
    bool bExport = !bAutoStyles;
    if (!(GetExport().getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED))
    {
        bExport = false;
    }

    uno::Reference<text::XTextContent> xTextContent(xPortion->getPropertyValue(u"ContentControl"_ustr),
                                                    uno::UNO_QUERY_THROW);
    uno::Reference<container::XEnumerationAccess> xEA(xTextContent, uno::UNO_QUERY_THROW);
    uno::Reference<container::XEnumeration> xTextEnum = xEA->createEnumeration();

    uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY_THROW);
    if (bExport)
    {
        bool bShowingPlaceHolder = false;
        xPropertySet->getPropertyValue(u"ShowingPlaceHolder"_ustr) >>= bShowingPlaceHolder;
        if (bShowingPlaceHolder)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bShowingPlaceHolder);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_SHOWING_PLACE_HOLDER,
                                     aBuffer.makeStringAndClear());
        }

        bool bCheckbox = false;
        xPropertySet->getPropertyValue(u"Checkbox"_ustr) >>= bCheckbox;
        if (bCheckbox)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bCheckbox);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_CHECKBOX, aBuffer.makeStringAndClear());
        }

        bool bChecked = false;
        xPropertySet->getPropertyValue(u"Checked"_ustr) >>= bChecked;
        if (bChecked)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bChecked);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_CHECKED, aBuffer.makeStringAndClear());
        }

        OUString aCheckedState;
        xPropertySet->getPropertyValue(u"CheckedState"_ustr) >>= aCheckedState;
        if (!aCheckedState.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_CHECKED_STATE, aCheckedState);
        }

        OUString aUncheckedState;
        xPropertySet->getPropertyValue(u"UncheckedState"_ustr) >>= aUncheckedState;
        if (!aUncheckedState.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_UNCHECKED_STATE, aUncheckedState);
        }

        bool bPicture = false;
        xPropertySet->getPropertyValue(u"Picture"_ustr) >>= bPicture;
        if (bPicture)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bPicture);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_PICTURE,
                                     aBuffer.makeStringAndClear());
        }

        bool bDate = false;
        xPropertySet->getPropertyValue(u"Date"_ustr) >>= bDate;
        if (bDate)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bDate);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATE, aBuffer.makeStringAndClear());
        }

        OUString aDateFormat;
        xPropertySet->getPropertyValue(u"DateFormat"_ustr) >>= aDateFormat;
        if (!aDateFormat.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATE_FORMAT, aDateFormat);
        }

        OUString aDateLanguage;
        xPropertySet->getPropertyValue(u"DateLanguage"_ustr) >>= aDateLanguage;
        if (!aDateLanguage.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DATE_RFC_LANGUAGE_TAG, aDateLanguage);
        }
        OUString aCurrentDate;
        xPropertySet->getPropertyValue(u"CurrentDate"_ustr) >>= aCurrentDate;
        if (!aCurrentDate.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_CURRENT_DATE, aCurrentDate);
        }

        bool bPlainText = false;
        xPropertySet->getPropertyValue(u"PlainText"_ustr) >>= bPlainText;
        if (bPlainText)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bPlainText);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_PLAIN_TEXT, aBuffer.makeStringAndClear());
        }

        bool bComboBox = false;
        xPropertySet->getPropertyValue(u"ComboBox"_ustr) >>= bComboBox;
        if (bComboBox)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bComboBox);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_COMBOBOX, aBuffer.makeStringAndClear());
        }

        bool bDropDown = false;
        xPropertySet->getPropertyValue(u"DropDown"_ustr) >>= bDropDown;
        if (bDropDown)
        {
            OUStringBuffer aBuffer;
            sax::Converter::convertBool(aBuffer, bDropDown);
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DROPDOWN, aBuffer.makeStringAndClear());
        }

        OUString aAlias;
        xPropertySet->getPropertyValue(u"Alias"_ustr) >>= aAlias;
        if (!aAlias.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_ALIAS, aAlias);
        }

        OUString aTag;
        xPropertySet->getPropertyValue(u"Tag"_ustr) >>= aTag;
        if (!aTag.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_TAG, aTag);
        }

        sal_Int32 nId = 0;
        xPropertySet->getPropertyValue(u"Id"_ustr) >>= nId;
        if (nId)
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_ID, OUString::number(nId));
        }

        sal_uInt32 nTabIndex = 0;
        if ((xPropertySet->getPropertyValue(u"TabIndex"_ustr) >>= nTabIndex) && nTabIndex)
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_TAB_INDEX,
                                     OUString::number(nTabIndex));
        }

        OUString aLock;
        xPropertySet->getPropertyValue(u"Lock"_ustr) >>= aLock;
        if (!aLock.isEmpty())
        {
            GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_LOCK, aLock);
        }
    }

    SvXMLElementExport aElem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, XML_CONTENT_CONTROL, false,
                             false);

    if (bExport)
    {
        // Export list items of dropdowns.
        cpo::uno::Sequence<beans::PropertyValues> aListItems;
        xPropertySet->getPropertyValue(u"ListItems"_ustr) >>= aListItems;
        for (const auto& rListItem : aListItems)
        {
            comphelper::SequenceAsHashMap aMap(rListItem);
            auto it = aMap.find(u"DisplayText"_ustr);
            OUString aValue;
            if (it != aMap.end() && (it->second >>= aValue) && !aValue.isEmpty())
            {
                GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_DISPLAY_TEXT, aValue);
            }

            it = aMap.find(u"Value"_ustr);
            if (it != aMap.end() && (it->second >>= aValue))
            {
                GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, XML_VALUE, aValue);
            }

            SvXMLElementExport aItem(GetExport(), bExport, XML_NAMESPACE_LO_EXT, XML_LIST_ITEM, false,
                    false);
        }
    }

    // Recurse to export content.
    exportTextRangeEnumeration(xTextEnum, bAutoStyles, isProgress, rPrevCharIsSpace);
}

void XMLTextParagraphExport::PreventExportOfControlsInMuteSections(
    const Reference<XIndexAccess> & rShapes,
    const rtl::Reference<xmloff::OFormLayerXMLExport>& xFormExport   )
{
    // check parameters ad pre-conditions
    if( ( ! rShapes.is() ) || ( ! xFormExport.is() ) )
    {
        // if we don't have shapes or a form export, there's nothing to do
        return;
    }
    SAL_WARN_IF( m_pSectionExport == nullptr, "xmloff", "We need the section export." );

    Reference<XEnumeration> xShapesEnum = m_pBoundFrameSets->GetShapes()->createEnumeration();
    if(!xShapesEnum.is())
        return;
    while( xShapesEnum->hasMoreElements() )
    {
        // now we need to check
        // 1) if this is a control shape, and
        // 2) if it's in a mute section
        // if both answers are 'yes', notify the form layer export

        // we join accessing the shape and testing for control
        Reference<XControlShape> xControlShape(xShapesEnum->nextElement(), UNO_QUERY);
        if( xControlShape.is() )
        {
            //            Reference<XPropertySet> xPropSet( xControlShape, UNO_QUERY );
            //            Reference<XTextContent> xTextContent;
            //            xPropSet->getPropertyValue("TextRange") >>= xTextContent;

            Reference<XTextContent> xTextContent( xControlShape, UNO_QUERY );
            if( xTextContent.is() )
            {
                if( m_pSectionExport->IsMuteSection( xTextContent, false ) )
                {
                    // Ah, we've found a shape that
                    // 1) is a control shape
                    // 2) is anchored in a mute section
                    // so: don't export it!
                    xFormExport->excludeFromExport(
                        xControlShape->getControl() );
                }
                // else: not in mute section -> should be exported -> nothing
                // to do
            }
            // else: no anchor -> ignore
        }
        // else: no control shape -> nothing to do
    }
}

void XMLTextParagraphExport::PushNewTextListsHelper()
{
    maTextListsHelperStack.emplace_back( new XMLTextListsHelper() );
    mpTextListsHelper = maTextListsHelperStack.back().get();
}

void XMLTextParagraphExport::PopTextListsHelper()
{
    mpTextListsHelper = nullptr;
    maTextListsHelperStack.pop_back();
    if ( !maTextListsHelperStack.empty() )
    {
        mpTextListsHelper = maTextListsHelperStack.back().get();
    }
}

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