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

    #include <memory>
    #include "docxattributeoutput.hxx"
    #include "docxhelper.hxx"
    #include "docxsdrexport.hxx"
    #include "docxexportfilter.hxx"
    #include "docxfootnotes.hxx"
    #include "writerwordglue.hxx"
    #include "ww8par.hxx"
    #include <fmtcntnt.hxx>
    #include <fmtftn.hxx>
    #include <fchrfmt.hxx>
    #include <tgrditem.hxx>
    #include <fmtruby.hxx>
    #include <fmtanchr.hxx>
    #include <breakit.hxx>
    #include <redline.hxx>
    #include <unocoll.hxx>
    #include <unoframe.hxx>
    #include <unodraw.hxx>
    #include <textboxhelper.hxx>
    #include <rdfhelper.hxx>
    #include "wrtww8.hxx"

    #include <comphelper/processfactory.hxx>
    #include <comphelper/random.hxx>
    #include <comphelper/string.hxx>
    #include <comphelper/flagguard.hxx>
    #include <comphelper/sequence.hxx>
    #include <oox/token/namespaces.hxx>
    #include <oox/token/tokens.hxx>
    #include <oox/export/utils.hxx>
    #include <oox/mathml/export.hxx>
    #include <oox/drawingml/drawingmltypes.hxx>
    #include <oox/token/relationship.hxx>
    #include <oox/export/vmlexport.hxx>
    #include <oox/ole/olehelper.hxx>

    #include <editeng/autokernitem.hxx>
    #include <editeng/unoprnms.hxx>
    #include <editeng/fontitem.hxx>
    #include <editeng/tstpitem.hxx>
    #include <editeng/spltitem.hxx>
    #include <editeng/widwitem.hxx>
    #include <editeng/shaditem.hxx>
    #include <editeng/brushitem.hxx>
    #include <editeng/postitem.hxx>
    #include <editeng/wghtitem.hxx>
    #include <editeng/kernitem.hxx>
    #include <editeng/crossedoutitem.hxx>
    #include <editeng/cmapitem.hxx>
    #include <editeng/udlnitem.hxx>
    #include <editeng/langitem.hxx>
    #include <editeng/lspcitem.hxx>
    #include <editeng/escapementitem.hxx>
    #include <editeng/fhgtitem.hxx>
    #include <editeng/colritem.hxx>
    #include <editeng/hyphenzoneitem.hxx>
    #include <editeng/ulspitem.hxx>
    #include <editeng/contouritem.hxx>
    #include <editeng/shdditem.hxx>
    #include <editeng/emphasismarkitem.hxx>
    #include <editeng/twolinesitem.hxx>
    #include <editeng/charscaleitem.hxx>
    #include <editeng/charrotateitem.hxx>
    #include <editeng/charreliefitem.hxx>
    #include <editeng/paravertalignitem.hxx>
    #include <editeng/pgrditem.hxx>
    #include <editeng/frmdiritem.hxx>
    #include <editeng/blinkitem.hxx>
    #include <editeng/charhiddenitem.hxx>
    #include <editeng/editobj.hxx>
    #include <editeng/keepitem.hxx>
    #include <svx/xfillit0.hxx>
    #include <svx/xflgrit.hxx>
    #include <svx/fmglob.hxx>
    #include <svx/svdouno.hxx>
    #include <svl/grabbagitem.hxx>
    #include <sfx2/sfxbasemodel.hxx>
    #include <tools/datetimeutils.hxx>
    #include <svl/whiter.hxx>
    #include <rtl/tencinfo.h>
    #include <sal/log.hxx>

    #include <docufld.hxx>
    #include <authfld.hxx>
    #include <flddropdown.hxx>
    #include <fmtclds.hxx>
    #include <fmtinfmt.hxx>
    #include <fmtrowsplt.hxx>
    #include <fmtline.hxx>
    #include <ftninfo.hxx>
    #include <htmltbl.hxx>
    #include <lineinfo.hxx>
    #include <ndgrf.hxx>
    #include <ndole.hxx>
    #include <ndtxt.hxx>
    #include <pagedesc.hxx>
    #include <paratr.hxx>
    #include <charatr.hxx>
    #include <swmodule.hxx>
    #include <swtable.hxx>
    #include <txtftn.hxx>
    #include <txtinet.hxx>
    #include <fmtautofmt.hxx>
    #include <docsh.hxx>
    #include <docary.hxx>
    #include <fmtclbl.hxx>
    #include <fmthdft.hxx>
    #include <IDocumentSettingAccess.hxx>
    #include <IDocumentStylePoolAccess.hxx>
    #include <IDocumentRedlineAccess.hxx>
    #include <grfatr.hxx>
    #include <frmatr.hxx>
    #include <txtatr.hxx>

    #include <osl/file.hxx>
    #include <vcl/embeddedfontshelper.hxx>
    #include <svtools/miscopt.hxx>

    #include <com/sun/star/i18n/ScriptType.hpp>
    #include <com/sun/star/chart2/XChartDocument.hpp>
    #include <com/sun/star/drawing/ShadingPattern.hpp>
    #include <com/sun/star/text/GraphicCrop.hpp>
    #include <com/sun/star/drawing/LineStyle.hpp>
    #include <com/sun/star/embed/EmbedStates.hpp>

    #include <algorithm>

    using ::editeng::SvxBorderLine;

    using namespace oox;
    using namespace docx;
    using namespace sax_fastparser;
    using namespace nsSwDocInfoSubType;
    using namespace sw::util;
    using namespace ::com::sun::star;
    using namespace ::com::sun::star::drawing;

    static const sal_Int32 Tag_StartParagraph_1 = 1;
    static const sal_Int32 Tag_StartParagraph_2 = 2;
    static const sal_Int32 Tag_WriteSdtBlock = 3;
    static const sal_Int32 Tag_StartParagraphProperties = 4;
    static const sal_Int32 Tag_InitCollectedParagraphProperties = 5;
    static const sal_Int32 Tag_StartRun_1 = 6;
    static const sal_Int32 Tag_StartRun_2 = 7;
    static const sal_Int32 Tag_StartRun_3 = 8;
    static const sal_Int32 Tag_EndRun_1 = 9;
    static const sal_Int32 Tag_EndRun_2 = 10;
    static const sal_Int32 Tag_StartRunProperties = 11;
    static const sal_Int32 Tag_InitCollectedRunProperties = 12;
    static const sal_Int32 Tag_Redline_1 = 13;
    static const sal_Int32 Tag_Redline_2 = 14;
    static const sal_Int32 Tag_TableDefinition = 15;
    static const sal_Int32 Tag_OutputFlyFrame = 16;
    static const sal_Int32 Tag_StartSection = 17;

    class FFDataWriterHelper
    {
        ::sax_fastparser::FSHelperPtr m_pSerializer;
        void writeCommonStart( const OUString& rName )
        {
            m_pSerializer->startElementNS( XML_w, XML_ffData, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_name,
                FSNS( XML_w, XML_val ), OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ).getStr(),
                FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_enabled, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_calcOnExit,
                FSNS( XML_w, XML_val ),
                "0", FSEND );
        }
        void writeFinish()
        {
            m_pSerializer->endElementNS( XML_w, XML_ffData );
        }
    public:
        explicit FFDataWriterHelper( const ::sax_fastparser::FSHelperPtr& rSerializer ) : m_pSerializer( rSerializer ){}
        void WriteFormCheckbox( const OUString& rName, bool bChecked )
        {
           writeCommonStart( rName );
           // Checkbox specific bits
           m_pSerializer->startElementNS( XML_w, XML_checkBox, FSEND );
           // currently hardcoding autosize
           // #TODO check if this defaulted
           m_pSerializer->startElementNS( XML_w, XML_sizeAuto, FSEND );
           m_pSerializer->endElementNS( XML_w, XML_sizeAuto );
           if ( bChecked )
                m_pSerializer->singleElementNS( XML_w, XML_checked, FSEND );
            m_pSerializer->endElementNS( XML_w, XML_checkBox );
           writeFinish();
        }
        void WriteFormText(  const OUString& rName )
        {
           writeCommonStart( rName );
           writeFinish();
        }
    };

    class FieldMarkParamsHelper
    {
        const sw::mark::IFieldmark& mrFieldmark;
        public:
        explicit FieldMarkParamsHelper( const sw::mark::IFieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {}
        OUString const & getName() { return mrFieldmark.GetName(); }
        template < typename T >
        bool extractParam( const OUString& rKey, T& rResult )
        {
            bool bResult = false;
            if ( mrFieldmark.GetParameters() )
            {
                sw::mark::IFieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey );
                if ( it != mrFieldmark.GetParameters()->end() )
                    bResult = ( it->second >>= rResult );
            }
            return bResult;
        }
    };
    void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ )
    {
        if (bIsRTL)
            m_pSerializer->singleElementNS( XML_w, XML_rtl, FSNS( XML_w, XML_val ), "true", FSEND );
    }

    /// Are multiple paragraphs disallowed inside this type of SDT?
    static bool lcl_isOnelinerSdt(const OUString& rName)
    {
        return rName == "Title" || rName == "Subtitle" || rName == "Company";
    }

    void DocxAttributeOutput::StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo )
    {
        if ( m_nColBreakStatus == COLBRK_POSTPONE )
            m_nColBreakStatus = COLBRK_WRITE;

        // Output table/table row/table cell starts if needed
        if ( pTextNodeInfo.get() )
        {
            // New cell/row?
            if ( m_tableReference->m_nTableDepth > 0 && !m_tableReference->m_bTableCellOpen )
            {
                ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference->m_nTableDepth ) );
                if ( pDeepInner->getCell() == 0 )
                    StartTableRow( pDeepInner );

                const sal_uInt32 nCell = pDeepInner->getCell();
                const sal_uInt32 nRow = pDeepInner->getRow();

                SyncNodelessCells(pDeepInner, nCell, nRow);
                StartTableCell(pDeepInner, nCell, nRow);
            }

            sal_uInt32 nRow = pTextNodeInfo->getRow();
            sal_uInt32 nCell = pTextNodeInfo->getCell();
            if (nCell == 0)
            {
                // Do we have to start the table?
                // [If we are at the right depth already, it means that we
                // continue the table cell]
                sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth();

                if ( nCurrentDepth > m_tableReference->m_nTableDepth )
                {
                    // Start all the tables that begin here
                    for ( sal_uInt32 nDepth = m_tableReference->m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth )
                    {
                        ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) );

                        StartTable( pInner );
                        StartTableRow( pInner );

                        StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0);
                    }

                    m_tableReference->m_nTableDepth = nCurrentDepth;
                }
            }
        }

        // Look up the "sdt end before this paragraph" property early, when it
        // would normally arrive, it would be too late (would be after the
        // paragraph start has been written).
        bool bEndParaSdt = false;
        SwTextNode* pTextNode = m_rExport.m_pCurPam->GetNode().GetTextNode();
        if (pTextNode && pTextNode->GetpSwAttrSet())
        {
            const SfxItemSet* pSet = pTextNode->GetpSwAttrSet();
            if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG))
            {
                const SfxGrabBagItem& rParaGrabBag = static_cast<const SfxGrabBagItem&>(*pItem);
                const std::map<OUString, css::uno::Any>& rMap = rParaGrabBag.GetGrabBag();
                bEndParaSdt = m_bStartedParaSdt && rMap.find("ParaSdtEndBefore") != rMap.end();
            }
        }
        // TODO also avoid multiline paragraphs in those SDT types for shape text
        bool bOneliner = m_bStartedParaSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias);
        if (bEndParaSdt || (m_bStartedParaSdt && m_bHadSectPr) || bOneliner)
        {
            // This is the common case: "close sdt before the current paragraph" was requested by the next paragraph.
            EndSdtBlock();
            m_bStartedParaSdt = false;
            m_aStartedParagraphSdtPrAlias.clear();
        }
        m_bHadSectPr = false;

        // this mark is used to be able to enclose the paragraph inside a sdr tag.
        // We will only know if we have to do that later.
        m_pSerializer->mark(Tag_StartParagraph_1);

        m_pSerializer->startElementNS( XML_w, XML_p, FSEND );

        // postpone the output of the run (we get it before the paragraph
        // properties, but must write it after them)
        m_pSerializer->mark(Tag_StartParagraph_2);

        // no section break in this paragraph yet; can be set in SectionBreak()
        m_pSectionInfo.reset();

        m_bParagraphOpened = true;
        m_bIsFirstParagraph = false;
    }

    static void lcl_deleteAndResetTheLists( rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrTokenChildren, rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrDataBindingAttrs, OUString& rSdtPrAlias)
    {
        if( pSdtPrTokenChildren.is() )
            pSdtPrTokenChildren.clear();
        if( pSdtPrDataBindingAttrs.is() )
            pSdtPrDataBindingAttrs.clear();
        if (!rSdtPrAlias.isEmpty())
            rSdtPrAlias.clear();
    }

    void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize)
    {

        sax_fastparser::FastAttributeList* attrList = FastSerializerHelper::createAttrList();

        awt::Point aPos(pFrameFormat->GetHoriOrient().GetPos(), pFrameFormat->GetVertOrient().GetPos());

        attrList->add( FSNS( XML_w, XML_w), OString::number(rSize.Width()));
        attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height()));

        attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X));
        attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y));

        const char* relativeFromH;
        const char* relativeFromV;
        switch (pFrameFormat->GetVertOrient().GetRelationOrient())
        {
            case text::RelOrientation::PAGE_PRINT_AREA:
                relativeFromV = "margin";
                break;
            case text::RelOrientation::PAGE_FRAME:
                relativeFromV = "page";
                break;
            case text::RelOrientation::FRAME:
            case text::RelOrientation::TEXT_LINE:
            default:
                relativeFromV = "text";
                break;
        }

        switch (pFrameFormat->GetHoriOrient().GetRelationOrient())
        {
            case text::RelOrientation::PAGE_PRINT_AREA:
                relativeFromH = "margin";
                break;
            case text::RelOrientation::PAGE_FRAME:
                relativeFromH = "page";
                break;
            case text::RelOrientation::CHAR:
            case text::RelOrientation::PAGE_RIGHT:
            case text::RelOrientation::FRAME:
            default:
                relativeFromH = "text";
                break;
        }

        switch (pFrameFormat->GetSurround().GetValue())
        {
        case css::text::WrapTextMode_NONE:
            attrList->add( FSNS( XML_w, XML_wrap), "none");
            break;
        case css::text::WrapTextMode_THROUGH:
            attrList->add( FSNS( XML_w, XML_wrap), "through");
            break;
        case css::text::WrapTextMode_PARALLEL:
            attrList->add( FSNS( XML_w, XML_wrap), "notBeside");
            break;
        case css::text::WrapTextMode_DYNAMIC:
        default:
            attrList->add( FSNS( XML_w, XML_wrap), "auto");
            break;
        }
        attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV);
        attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH);
        attrList->add( FSNS( XML_w, XML_hRule), "exact");

        sax_fastparser::XFastAttributeListRef xAttrList(attrList);
        m_pSerializer->singleElementNS( XML_w, XML_framePr, xAttrList );
    }

    bool DocxAttributeOutput::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat)
    {
        uno::Reference< drawing::XShape > xShape;
        const SdrObject* pSdrObj = rFrameFormat.FindRealSdrObject();
        if (pSdrObj)
            xShape.set(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
        uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY);
        uno::Reference< beans::XPropertySetInfo > xPropSetInfo;
        if (xPropertySet.is())
            xPropSetInfo = xPropertySet->getPropertySetInfo();
        uno::Any aFrameProperties ;
        if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("FrameInteropGrabBag"))
        {
            uno::Sequence< beans::PropertyValue > propList;
            xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= propList;
            for (sal_Int32 nProp=0; nProp < propList.getLength(); ++nProp)
            {
                OUString propName = propList[nProp].Name;
                if (propName == "ParaFrameProperties")
                {
                    aFrameProperties = propList[nProp].Value ;
                    break;
                }
            }
        }
        bool bFrameProperties = false;
        aFrameProperties >>= bFrameProperties;
        return bFrameProperties;
    }

    void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
    {
        // write the paragraph properties + the run, already in the correct order
        m_pSerializer->mergeTopMarks(Tag_StartParagraph_2);
        std::vector<  std::shared_ptr <ww8::Frame> > aFramePrTextbox;
        // Write the anchored frame if any
        // Word can't handle nested text boxes, so write them on the same level.
        ++m_nTextFrameLevel;
        if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
        {
            comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_bStartedParaSdt, false);

            assert(!m_pPostponedCustomShape);
            m_pPostponedCustomShape.reset(new std::vector<PostponedDrawing>);
            for (size_t nIndex = 0; nIndex < m_aFramesOfParagraph.size(); ++nIndex)
            {
                m_bParagraphFrameOpen = true;
                ww8::Frame aFrame = m_aFramesOfParagraph[nIndex];
                const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat();

                if (!TextBoxIsFramePr(rFrameFormat) || m_bWritingHeaderFooter)
                {
                    if (m_bStartedCharSdt)
                    {
                        // Run-level SDT still open? Close it before AlternateContent.
                        EndSdtBlock();
                        m_bStartedCharSdt = false;
                    }
                    m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
                    m_pSerializer->startElementNS(XML_mc, XML_AlternateContent, FSEND);
                    m_pSerializer->startElementNS(XML_mc, XML_Choice,
                            XML_Requires, "wps",
                            FSEND);
                    /**
                        This is to avoid AlternateContent within another AlternateContent.
                           So when Choice is Open, only write the DML Drawing instead of both DML
                           and VML Drawing in another AlternateContent.
                     **/
                    SetAlternateContentChoiceOpen( true );
                    /** Save the table info's before writing the shape
                            as there might be a new table that might get
                            spawned from within the VML & DML block and alter
                            the contents.
                    */
                    ww8::WW8TableInfo::Pointer_t pOldTableInfo = m_rExport.m_pTableInfo;
                    //Reset the table infos after saving.
                    m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();

                    /** FDO#71834 :
                           Save the table reference attributes before calling WriteDMLTextFrame,
                           otherwise the StartParagraph function will use the previous existing
                           table reference attributes since the variable is being shared.
                    */
                    {
                        DocxTableExportContext aDMLTableExportContext(*this);
                        m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++);
                    }
                    m_pSerializer->endElementNS(XML_mc, XML_Choice);
                    SetAlternateContentChoiceOpen( false );

                    // Reset table infos, otherwise the depth of the cells will be incorrect,
                    // in case the text frame had table(s) and we try to export the
                    // same table second time.
                    m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
                    //reset the tableReference.

                    m_pSerializer->startElementNS(XML_mc, XML_Fallback, FSEND);
                    {
                        DocxTableExportContext aVMLTableExportContext(*this);
                        m_rExport.SdrExporter().writeVMLTextFrame(&aFrame);
                    }
                    m_rExport.m_pTableInfo = pOldTableInfo;

                    m_pSerializer->endElementNS(XML_mc, XML_Fallback);
                    m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
                    m_pSerializer->endElementNS( XML_w, XML_r );
                    m_bParagraphFrameOpen = false;
                }
                else
                {
                    std::shared_ptr<ww8::Frame>  pFramePr;
                    pFramePr.reset(new ww8::Frame(aFrame));
                    aFramePrTextbox.push_back(pFramePr);
                }
            }
            if (!m_pPostponedCustomShape->empty())
            {
                m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
                WritePostponedCustomShape();
                m_pSerializer->endElementNS( XML_w, XML_r );
            }
            m_pPostponedCustomShape.reset(nullptr);

            m_aFramesOfParagraph.clear();
        }

        --m_nTextFrameLevel;

        /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed.
         * This is due to nested hyperlink tags. So close it before end of paragraph.
         */
        if(m_nHyperLinkCount > 0)
        {
            for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount; ++nHyperLinkToClose)
                m_pSerializer->endElementNS( XML_w, XML_hyperlink );
            m_nHyperLinkCount = 0;
        }

        if (m_bStartedCharSdt)
        {
            // Run-level SDT still open? Close it now.
            EndSdtBlock();
            m_bStartedCharSdt = false;
        }

        m_pSerializer->endElementNS( XML_w, XML_p );
        // on export sdt blocks are never nested ATM
        if( !m_bAnchorLinkedToNode && !m_bStartedParaSdt )
            WriteSdtBlock( m_nParagraphSdtPrToken, m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrTokenAttributes, m_pParagraphSdtPrDataBindingAttrs, m_aParagraphSdtPrAlias, /*bPara=*/true );
        else
        {
            //These should be written out to the actual Node and not to the anchor.
            //Clear them as they will be repopulated when the node is processed.
            m_nParagraphSdtPrToken = 0;
            m_bParagraphSdtHasId = false;
            lcl_deleteAndResetTheLists( m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrDataBindingAttrs, m_aParagraphSdtPrAlias );
        }

        //sdtcontent is written so Set m_bParagraphHasDrawing to false
        m_rExport.SdrExporter().setParagraphHasDrawing( false );
        m_bRunTextIsOn = false;
        m_pSerializer->mergeTopMarks(Tag_StartParagraph_1);

        // Write framePr
        if(!aFramePrTextbox.empty())
        {
            for ( const auto & pFrame : aFramePrTextbox )
            {
                DocxTableExportContext aTableExportContext(*this);
                m_pCurrentFrame = pFrame.get();
                m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get());
                m_pCurrentFrame = nullptr;
            }
            aFramePrTextbox.clear();
        }
        // Check for end of cell, rows, tables here
        FinishTableRowCell( pTextNodeInfoInner );

        if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
            m_bParagraphOpened = false;

        // Clear bookmarks of the current paragraph
        m_aBookmarksOfParagraphStart.clear();
        m_aBookmarksOfParagraphEnd.clear();
    }

    void DocxAttributeOutput::WriteSdtBlock( sal_Int32& nSdtPrToken,
                                             rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrTokenChildren,
                                             rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrTokenAttributes,
                                             rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrDataBindingAttrs,
                                             OUString& rSdtPrAlias,
                                             bool bPara )
    {
        if( nSdtPrToken <= 0 && !pSdtPrDataBindingAttrs.is() )
            return;

        // sdt start mark
        m_pSerializer->mark(Tag_WriteSdtBlock);

        m_pSerializer->startElementNS( XML_w, XML_sdt, FSEND );

        // output sdt properties
        m_pSerializer->startElementNS( XML_w, XML_sdtPr, FSEND );

        if( nSdtPrToken > 0 && pSdtPrTokenChildren.is() )
        {
            if (!pSdtPrTokenAttributes.is())
                m_pSerializer->startElement( nSdtPrToken, FSEND );
            else
            {
                XFastAttributeListRef xAttrList(pSdtPrTokenAttributes.get());
                pSdtPrTokenAttributes.clear();
                m_pSerializer->startElement(nSdtPrToken, xAttrList);
            }

            if (nSdtPrToken ==  FSNS( XML_w, XML_date ) || nSdtPrToken ==  FSNS( XML_w, XML_docPartObj ) || nSdtPrToken ==  FSNS( XML_w, XML_docPartList ) || nSdtPrToken ==  FSNS( XML_w14, XML_checkbox )) {
                uno::Sequence<xml::FastAttribute> aChildren = pSdtPrTokenChildren->getFastAttributes();
                for( sal_Int32 i=0; i < aChildren.getLength(); ++i )
                    m_pSerializer->singleElement( aChildren[i].Token,
                                                  FSNS(XML_w, XML_val),
                                                  OUStringToOString( aChildren[i].Value, RTL_TEXTENCODING_UTF8 ).getStr(),
                                                  FSEND );
            }

            m_pSerializer->endElement( nSdtPrToken );
        }
        else if( (nSdtPrToken > 0) && nSdtPrToken != FSNS( XML_w, XML_id ) && !(m_bRunTextIsOn && m_rExport.SdrExporter().IsParagraphHasDrawing()))
        {
            if (!pSdtPrTokenAttributes.is())
                m_pSerializer->singleElement( nSdtPrToken, FSEND );
            else
            {
                XFastAttributeListRef xAttrList(pSdtPrTokenAttributes.get());
                pSdtPrTokenAttributes.clear();
                m_pSerializer->singleElement(nSdtPrToken, xAttrList);
            }
        }

        if( nSdtPrToken == FSNS( XML_w, XML_id ) || ( bPara && m_bParagraphSdtHasId ) )
            //Word won't open a document with an empty id tag, we fill it with a random number
            m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val),
                                          OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())),
                                          FSEND);

        if( pSdtPrDataBindingAttrs.is() && !m_rExport.SdrExporter().IsParagraphHasDrawing())
        {
            XFastAttributeListRef xAttrList( pSdtPrDataBindingAttrs.get() );
            pSdtPrDataBindingAttrs.clear();
            m_pSerializer->singleElementNS( XML_w, XML_dataBinding, xAttrList );
        }

        if (!rSdtPrAlias.isEmpty())
            m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val),
                                           OUStringToOString(rSdtPrAlias, RTL_TEXTENCODING_UTF8).getStr(),
                                           FSEND);

        m_pSerializer->endElementNS( XML_w, XML_sdtPr );

        // sdt contents start tag
        m_pSerializer->startElementNS( XML_w, XML_sdtContent, FSEND );

        // prepend the tags since the sdt start mark before the paragraph
        m_pSerializer->mergeTopMarks(Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND);

        // write the ending tags after the paragraph
        if (bPara)
        {
            m_bStartedParaSdt = true;
            if (m_tableReference->m_bTableCellOpen)
                m_tableReference->m_bTableCellParaSdtOpen = true;
            if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
                m_rExport.SdrExporter().setParagraphSdtOpen(true);
        }
        else
            // Support multiple runs inside a run-level SDT: don't close the SDT block yet.
            m_bStartedCharSdt = true;

        // clear sdt status
        nSdtPrToken = 0;
        pSdtPrTokenChildren.clear();
        pSdtPrDataBindingAttrs.clear();
        rSdtPrAlias.clear();

    }

    void DocxAttributeOutput::EndSdtBlock()
    {
        m_pSerializer->endElementNS( XML_w, XML_sdtContent );
        m_pSerializer->endElementNS( XML_w, XML_sdt );
    }

    #define MAX_CELL_IN_WORD 62

    void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow)
    {
        sal_Int32 nOpenCell = lastOpenCell.back();
        if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD)
            EndTableCell(nOpenCell);

        sal_Int32 nClosedCell = lastClosedCell.back();
        for (sal_Int32 i = nClosedCell+1; i < nCell; ++i)
        {
            if (i >= MAX_CELL_IN_WORD)
                break;

            if (i == 0)
                StartTableRow(pInner);

            StartTableCell(pInner, i, nRow);
            m_pSerializer->singleElementNS( XML_w, XML_p, FSEND );
            EndTableCell(i);
        }
    }

    void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph )
    {
        if ( pInner.get() )
        {
            // Where are we in the table
            sal_uInt32 nRow = pInner->getRow();
            sal_Int32 nCell = pInner->getCell();

            InitTableHelper( pInner );

            // HACK
            // msoffice seems to have an internal limitation of 63 columns for tables
            // and refuses to load .docx with more, even though the spec seems to allow that;
            // so simply if there are more columns, don't close the last one msoffice will handle
            // and merge the contents of the remaining ones into it (since we don't close the cell
            // here, following ones will not be opened)
            const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine());
            const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround;
            const bool bEndRow = pInner->isEndOfLine();

            if (bEndCell)
            {
                while (pInner->getDepth() < m_tableReference->m_nTableDepth)
                {
                    //we expect that the higher depth row was closed, and
                    //we are just missing the table close
                    assert(lastOpenCell.back() == -1 && lastClosedCell.back() == -1);
                    EndTable();
                }

                SyncNodelessCells(pInner, nCell, nRow);

                sal_Int32 nClosedCell = lastClosedCell.back();
                if (nCell == nClosedCell)
                {
                    //Start missing trailing cell
                    ++nCell;
                    StartTableCell(pInner, nCell, nRow);
                }

                if (bForceEmptyParagraph)
                {
                    m_pSerializer->singleElementNS( XML_w, XML_p, FSEND );
                }

                EndTableCell(nCell);
            }

            // This is a line end
            if (bEndRow)
                EndTableRow();

            // This is the end of the table
            if (pInner->isFinalEndOfLine())
                EndTable();
        }
    }

    void DocxAttributeOutput::EmptyParagraph()
    {
        m_pSerializer->singleElementNS( XML_w, XML_p, FSEND );
    }

    void DocxAttributeOutput::SectionBreaks(const SwNode& rNode)
    {
        // output page/section breaks
        // Writer can have them at the beginning of a paragraph, or at the end, but
        // in docx, we have to output them in the paragraph properties of the last
        // paragraph in a section.  To get it right, we have to switch to the next
        // paragraph, and detect the section breaks there.
        SwNodeIndex aNextIndex( rNode, 1 );

        if (rNode.IsTextNode())
        {
            if (aNextIndex.GetNode().IsTextNode())
            {
                const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode());
                m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference->m_bTableCellOpen, pTextNode->GetText().isEmpty());
            }
            else if (aNextIndex.GetNode().IsTableNode())
            {
                const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
                const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
                m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
            }
        }
        else if (rNode.IsEndNode())
        {
            if (aNextIndex.GetNode().IsTextNode())
            {
                // Handle section break between a table and a text node following it.
                // Also handle section endings
                const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode();
                if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode())
                    m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference->m_bTableCellOpen, pTextNode->GetText().isEmpty());
            }
        }
    }

    void DocxAttributeOutput::StartParagraphProperties()
    {
        m_pSerializer->mark(Tag_StartParagraphProperties);

        m_pSerializer->startElementNS( XML_w, XML_pPr, FSEND );

        // and output the section break now (if it appeared)
        if ( m_pSectionInfo && (!m_setFootnote))
        {
            m_rExport.SectionProperties( *m_pSectionInfo );
            m_pSectionInfo.reset();
        }

        InitCollectedParagraphProperties();
    }

    void DocxAttributeOutput::InitCollectedParagraphProperties()
    {
        m_pParagraphSpacingAttrList.clear();

        // Write the elements in the spec order
        static const sal_Int32 aOrder[] =
        {
            FSNS( XML_w, XML_pStyle ),
            FSNS( XML_w, XML_keepNext ),
            FSNS( XML_w, XML_keepLines ),
            FSNS( XML_w, XML_pageBreakBefore ),
            FSNS( XML_w, XML_framePr ),
            FSNS( XML_w, XML_widowControl ),
            FSNS( XML_w, XML_numPr ),
            FSNS( XML_w, XML_suppressLineNumbers ),
            FSNS( XML_w, XML_pBdr ),
            FSNS( XML_w, XML_shd ),
            FSNS( XML_w, XML_tabs ),
            FSNS( XML_w, XML_suppressAutoHyphens ),
            FSNS( XML_w, XML_kinsoku ),
            FSNS( XML_w, XML_wordWrap ),
            FSNS( XML_w, XML_overflowPunct ),
            FSNS( XML_w, XML_topLinePunct ),
            FSNS( XML_w, XML_autoSpaceDE ),
            FSNS( XML_w, XML_autoSpaceDN ),
            FSNS( XML_w, XML_bidi ),
            FSNS( XML_w, XML_adjustRightInd ),
            FSNS( XML_w, XML_snapToGrid ),
            FSNS( XML_w, XML_spacing ),
            FSNS( XML_w, XML_ind ),
            FSNS( XML_w, XML_contextualSpacing ),
            FSNS( XML_w, XML_mirrorIndents ),
            FSNS( XML_w, XML_suppressOverlap ),
            FSNS( XML_w, XML_jc ),
            FSNS( XML_w, XML_textDirection ),
            FSNS( XML_w, XML_textAlignment ),
            FSNS( XML_w, XML_textboxTightWrap ),
            FSNS( XML_w, XML_outlineLvl ),
            FSNS( XML_w, XML_divId ),
            FSNS( XML_w, XML_cnfStyle ),
            FSNS( XML_w, XML_rPr ),
            FSNS( XML_w, XML_sectPr ),
            FSNS( XML_w, XML_pPrChange )
        };

        // postpone the output so that we can later [in EndParagraphProperties()]
        // prepend the properties before the run
        m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder));
    }

    void DocxAttributeOutput::WriteCollectedParagraphProperties()
    {
        if ( m_rExport.SdrExporter().getFlyAttrList().is() )
        {
            XFastAttributeListRef xAttrList( m_rExport.SdrExporter().getFlyAttrList().get() );
            m_rExport.SdrExporter().getFlyAttrList().clear();

            m_pSerializer->singleElementNS( XML_w, XML_framePr, xAttrList );
        }

        if ( m_pParagraphSpacingAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pParagraphSpacingAttrList.get() );
            m_pParagraphSpacingAttrList.clear();

            m_pSerializer->singleElementNS( XML_w, XML_spacing, xAttrList );
        }

        if ( m_pBackgroundAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pBackgroundAttrList.get() );
            m_pBackgroundAttrList.clear();

            m_pSerializer->singleElementNS( XML_w, XML_shd, xAttrList );
        }
    }

    namespace
    {

    /// Outputs an item set, that contains the formatting of the paragraph marker.
    void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties)
    {
        SfxWhichIter aIter(rParagraphMarkerProperties);
        sal_uInt16 nWhichId = aIter.FirstWhich();
        const SfxPoolItem* pItem = nullptr;
        // Did we already produce a <w:sz> element?
        bool bFontSizeWritten = false;
        while (nWhichId)
        {
            if (rParagraphMarkerProperties.GetItemState(nWhichId, true, &pItem) == SfxItemState::SET)
            {
                if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT)
                {
                    // Will this item produce a <w:sz> element?
                    bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE;
                    if (!bFontSizeWritten || !bFontSizeItem)
                        rAttributeOutput.OutputItem(*pItem);
                    if (bFontSizeItem)
                        bFontSizeWritten = true;
                }
                else if (nWhichId == RES_TXTATR_AUTOFMT)
                {
                    const SwFormatAutoFormat* pAutoFormat = static_cast<const SwFormatAutoFormat*>(pItem);
                    lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat->GetStyleHandle());
                }
            }
            nWhichId = aIter.NextWhich();
        }
    }

    const char *RubyAlignValues[] =
    {
        "center",
        "distributeLetter",
        "distributeSpace",
        "left",
        "right",
        "rightVertical"
    };

    const char *lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC)
    {
        const sal_Int32 nElements = SAL_N_ELEMENTS(RubyAlignValues);
        if ( nJC >=0 && nJC < nElements )
            return RubyAlignValues[nJC];
        return RubyAlignValues[0];
    }

    }

    void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted)
    {
        // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties.
        // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.

        // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline().
        // As there will be another pPr for redline and LO might mix both.
        if(pRedlineData)
            WriteCollectedParagraphProperties();
        Redline( pRedlineData );

        WriteCollectedParagraphProperties();

        // Merge the marks for the ordered elements
        m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);

        // Write 'Paragraph Mark' properties
        m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
        // mark() before paragraph mark properties child elements.
        InitCollectedRunProperties();

        // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information
        // that should be collected by different properties in the core, and are all flushed together
        // to the DOCX when the function 'WriteCollectedRunProperties' gets called.
        // So we need to store the current status of these lists, so that we can revert back to them when
        // we are done exporting the redline attributes.
        rtl::Reference<sax_fastparser::FastAttributeList> pFontsAttrList_Original(m_pFontsAttrList);
        m_pFontsAttrList.clear();
        rtl::Reference<sax_fastparser::FastAttributeList> pEastAsianLayoutAttrList_Original(m_pEastAsianLayoutAttrList);
        m_pEastAsianLayoutAttrList.clear();
        rtl::Reference<sax_fastparser::FastAttributeList> pCharLangAttrList_Original(m_pCharLangAttrList);
        m_pCharLangAttrList.clear();

        lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties);

        // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
        WriteCollectedRunProperties();

        // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
        m_pFontsAttrList = pFontsAttrList_Original.get();
        m_pEastAsianLayoutAttrList = pEastAsianLayoutAttrList_Original.get();
        m_pCharLangAttrList = pCharLangAttrList_Original.get();

        if ( pRedlineParagraphMarkerDeleted )
        {
            StartRedline( pRedlineParagraphMarkerDeleted );
            EndRedline( pRedlineParagraphMarkerDeleted );
        }
        if ( pRedlineParagraphMarkerInserted )
        {
            StartRedline( pRedlineParagraphMarkerInserted );
            EndRedline( pRedlineParagraphMarkerInserted );
        }

        // mergeTopMarks() after paragraph mark properties child elements.
        m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
        m_pSerializer->endElementNS( XML_w, XML_rPr );

        if (!m_bWritingHeaderFooter && m_pCurrentFrame)
        {
            const SwFrameFormat& rFrameFormat = m_pCurrentFrame->GetFrameFormat();
            if (TextBoxIsFramePr(rFrameFormat))
            {
                const Size aSize = m_pCurrentFrame->GetSize();
                PopulateFrameProperties(&rFrameFormat, aSize);
            }
        }

        m_pSerializer->endElementNS( XML_w, XML_pPr );

        // RDF metadata for this text node.
        SwTextNode* pTextNode = m_rExport.m_pCurPam->GetNode().GetTextNode();
        std::map<OUString, OUString> aStatements = SwRDFHelper::getTextNodeStatements("urn:bails", *pTextNode);
        if (!aStatements.empty())
        {
            m_pSerializer->startElementNS(XML_w, XML_smartTag,
                                          FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
                                          FSNS(XML_w, XML_element), "RDF",
                                          FSEND);
            m_pSerializer->startElementNS(XML_w, XML_smartTagPr, FSEND);
            for (const auto& rStatement : aStatements)
                m_pSerializer->singleElementNS(XML_w, XML_attr,
                                               FSNS(XML_w, XML_name), rStatement.first.toUtf8(),
                                               FSNS(XML_w, XML_val), rStatement.second.toUtf8(),
                                               FSEND);
            m_pSerializer->endElementNS(XML_w, XML_smartTagPr);
            m_pSerializer->endElementNS(XML_w, XML_smartTag);
        }

        if ( m_nColBreakStatus == COLBRK_WRITE )
        {
            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_br,
                    FSNS( XML_w, XML_type ), "column", FSEND );
            m_pSerializer->endElementNS( XML_w, XML_r );

            m_nColBreakStatus = COLBRK_NONE;
        }

        if ( m_bPostponedPageBreak )
        {
            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_br,
                    FSNS( XML_w, XML_type ), "page", FSEND );
            m_pSerializer->endElementNS( XML_w, XML_r );

            m_bPostponedPageBreak = false;
        }

        // merge the properties _before_ the run (strictly speaking, just
        // after the start of the paragraph)
        m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND);
    }

    void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame )
    {
        m_nStateOfFlyFrame = nStateOfFlyFrame;
    }

    void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode )
    {
        m_bAnchorLinkedToNode = bAnchorLinkedToNode ;
    }

    void DocxAttributeOutput::ResetFlyProcessingFlag()
    {
        m_bPostponedProcessingFly = false ;
    }

    bool DocxAttributeOutput::IsFlyProcessingPostponed()
    {
        return m_bPostponedProcessingFly;
    }

    void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ )
    {
        // Don't start redline data here, possibly there is a hyperlink later, and
        // that has to be started first.
        m_pRedlineData = pRedlineData;

        // this mark is used to be able to enclose the run inside a sdr tag.
        m_pSerializer->mark(Tag_StartRun_1);

        // postpone the output of the start of a run (there are elements that need
        // to be written before the start of the run, but we learn which they are
        // _inside_ of the run)
        m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start"

        // postpone the output of the text (we get it before the run properties,
        // but must write it after them)
        m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
    }

    void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool /*bLastRun*/)
    {
        int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
        // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
        if ( m_pHyperlinkAttrList.is() )
        {
            m_nFieldsInHyperlink = 0;
        }

        // Write field starts
        for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); )
        {
            // Add the fields starts for all but hyperlinks and TOCs
            if ( pIt->bOpen && pIt->pField )
            {
                StartField_Impl( pNode, nPos, *pIt );

                // Remove the field from the stack if only the start has to be written
                // Unknown fields should be removed too
                if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) )
                {
                    pIt = m_Fields.erase( pIt );
                    continue;
                }

                if (m_startedHyperlink || m_pHyperlinkAttrList.is())
                {
                    ++m_nFieldsInHyperlink;
                }
            }
            ++pIt;
        }

        // write the run properties + the text, already in the correct order
        m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above

        // level down, to be able to prepend the actual run start attribute (just
        // before "postponed run start")
        m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start"
        bool bCloseEarlierSDT = false;

        if (m_bEndCharSdt)
        {
            // This is the common case: "close sdt before the current run" was requested by the next run.

            // if another sdt starts in this run, then wait
            // as closing the sdt now, might cause nesting of sdts
            if (m_nRunSdtPrToken > 0)
                bCloseEarlierSDT = true;
            else
                EndSdtBlock();
            m_bEndCharSdt = false;
            m_bStartedCharSdt = false;
        }

        if ( m_closeHyperlinkInPreviousRun )
        {
            if ( m_startedHyperlink )
            {
                for ( int i = 0; i < nFieldsInPrevHyperlink; i++ )
                {
                    // If fields begin before hyperlink then
                    // it should end before hyperlink close
                    EndField_Impl( pNode, nPos, m_Fields.back( ) );
                    m_Fields.pop_back();
                }
                m_pSerializer->endElementNS( XML_w, XML_hyperlink );
                m_startedHyperlink = false;
                m_endPageRef = false;
                m_nHyperLinkCount--;
            }
            m_closeHyperlinkInPreviousRun = false;
        }

        // Write the hyperlink and toc fields starts
        for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); )
        {
            // Add the fields starts for hyperlinks, TOCs and index marks
            if ( pIt->bOpen && !pIt->pField )
            {
                StartField_Impl( pNode, nPos, *pIt, true );

                if (m_startedHyperlink)
                    ++m_nFieldsInHyperlink;

                // Remove the field if no end needs to be written
                if ( !pIt->bClose ) {
                    pIt = m_Fields.erase( pIt );
                    continue;
                }
            }
            ++pIt;
        }

        // Start the hyperlink after the fields separators or we would generate invalid file
        bool newStartedHyperlink(false);
        if ( m_pHyperlinkAttrList.is() )
        {
            // if we are ending a hyperlink and there's another one starting here,
            // don't do this, so that the fields are closed further down when
            // the end hyperlink is handled, which is more likely to put the end in
            // the right place, as far as i can tell (not very far in this muck)
            if (!m_closeHyperlinkInThisRun)
            {
                // end ToX fields that want to end _before_ starting the hyperlink
                for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
                {
                    if (it->bClose && !it->pField)
                    {
                        EndField_Impl( pNode, nPos, *it );
                        it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
                    }
                    else
                    {
                        ++it;
                    }
                }
            }
            newStartedHyperlink = true;

            XFastAttributeListRef xAttrList ( m_pHyperlinkAttrList.get() );
            m_pHyperlinkAttrList.clear();

            m_pSerializer->startElementNS( XML_w, XML_hyperlink, xAttrList );
            m_startedHyperlink = true;
            m_nHyperLinkCount++;
        }

        // if there is some redlining in the document, output it
        StartRedline( m_pRedlineData );

        // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks.
        // The same is applied for permission ranges.
        // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end.
        DoWriteBookmarksStart(m_rBookmarksStart);
        DoWriteBookmarksEnd(m_rBookmarksEnd);
        DoWritePermissionsStart();
        DoWriteAnnotationMarks();

        if( m_closeHyperlinkInThisRun && m_startedHyperlink && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc"))
        {
            OUString sToken;
            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND );
            m_pSerializer->endElementNS( XML_w, XML_rPr );
            m_pSerializer->startElementNS( XML_w, XML_fldChar,
                    FSNS( XML_w, XML_fldCharType ), "begin",
                    FSEND );
            m_pSerializer->endElementNS( XML_w, XML_fldChar );
            m_pSerializer->endElementNS( XML_w, XML_r );

            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND );
            m_pSerializer->endElementNS( XML_w, XML_rPr );
            sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph.
            DoWriteCmd( sToken );
            m_pSerializer->endElementNS( XML_w, XML_r );

            // Write the Field separator
            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND );
            m_pSerializer->endElementNS( XML_w, XML_rPr );
            m_pSerializer->singleElementNS( XML_w, XML_fldChar,
                    FSNS( XML_w, XML_fldCharType ), "separate",
                    FSEND );
            m_pSerializer->endElementNS( XML_w, XML_r );
            // At start of every "PAGEREF" field m_endPageRef value should be true.
            m_endPageRef = true;
        }

        DoWriteBookmarkStartIfExist(nPos);

        m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
        if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is())
        {
            RunText("\t") ;
        }
        m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above

        if ( !m_sRawText.isEmpty() )
        {
            RunText( m_sRawText );
            m_sRawText.clear();
        }

        // write the run start + the run content
        m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start"
        // append the actual run end
        m_pSerializer->endElementNS( XML_w, XML_r );

        // if there is some redlining in the document, output it
        EndRedline( m_pRedlineData );

        // enclose in a sdt block, if necessary: if one is already started, then don't do it for now
        // (so on export sdt blocks are never nested ATM)
        if ( !m_bAnchorLinkedToNode && !m_bStartedCharSdt )
        {
            rtl::Reference<sax_fastparser::FastAttributeList> pRunSdtPrTokenAttributes;
            WriteSdtBlock( m_nRunSdtPrToken, m_pRunSdtPrTokenChildren, pRunSdtPrTokenAttributes, m_pRunSdtPrDataBindingAttrs, m_aRunSdtPrAlias, /*bPara=*/false );
        }
        else
        {
            //These should be written out to the actual Node and not to the anchor.
            //Clear them as they will be repopulated when the node is processed.
            m_nRunSdtPrToken = 0;
            lcl_deleteAndResetTheLists( m_pRunSdtPrTokenChildren, m_pRunSdtPrDataBindingAttrs, m_aRunSdtPrAlias );
        }

        if (bCloseEarlierSDT)
        {
            m_pSerializer->mark(Tag_EndRun_2);
            EndSdtBlock();
            m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND);
        }

        m_pSerializer->mergeTopMarks(Tag_StartRun_1);

        // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission.
        DoWritePermissionsEnd();

        for (std::vector<const SwOLENode*>::iterator it = m_aPostponedMaths.begin(); it != m_aPostponedMaths.end(); ++it)
            WritePostponedMath(*it);
        m_aPostponedMaths.clear();

        for (std::vector<const SdrObject*>::iterator it = m_aPostponedFormControls.begin(); it != m_aPostponedFormControls.end(); ++it)
            WritePostponedFormControl(*it);
        m_aPostponedFormControls.clear();

        WritePostponedActiveXControl(false);

        WritePendingPlaceholder();

        m_pRedlineData = nullptr;

        if ( m_closeHyperlinkInThisRun )
        {
            if ( m_startedHyperlink )
            {
                if( m_endPageRef )
                {
                    // Hyperlink is started and fldchar "end" needs to be written for PAGEREF
                    m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
                    m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
                    m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND );
                    m_pSerializer->endElementNS( XML_w, XML_rPr );
                    m_pSerializer->singleElementNS( XML_w, XML_fldChar,
                            FSNS( XML_w, XML_fldCharType ), "end",
                            FSEND );
                    m_pSerializer->endElementNS( XML_w, XML_r );
                    m_endPageRef = false;
                    m_hyperLinkAnchor.clear();
                }
                for ( int i = 0; i < m_nFieldsInHyperlink; i++ )
                {
                    // If fields begin after hyperlink start then
                    // it should end before hyperlink close
                    EndField_Impl( pNode, nPos, m_Fields.back( ) );
                    m_Fields.pop_back();
                }
                m_nFieldsInHyperlink = 0;

                m_pSerializer->endElementNS( XML_w, XML_hyperlink );
                m_startedHyperlink = false;
                m_nHyperLinkCount--;
            }
            m_closeHyperlinkInThisRun = false;
        }

        if (!newStartedHyperlink)
        {
            while ( m_Fields.begin() != m_Fields.end() )
            {
                EndField_Impl( pNode, nPos, m_Fields.front( ) );
                m_Fields.erase( m_Fields.begin( ) );
            }
            m_nFieldsInHyperlink = 0;
        }

        DoWriteBookmarksStart(m_rFinalBookmarksStart);
        DoWriteBookmarksEnd(m_rFinalBookmarksEnd);
        DoWriteBookmarkEndIfExist(nPos);
    }

    void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString & bookmarkName)
    {
        const OString rId   = OString::number(m_nNextBookmarkId);
        const OString rName = OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8).getStr();

        m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart,
            FSNS(XML_w, XML_id), rId.getStr(),
            FSNS(XML_w, XML_name), rName.getStr(),
            FSEND);
    }

    void DocxAttributeOutput::DoWriteBookmarkTagEnd(const OUString & bookmarkName)
    {
        const auto nameToIdIter = m_rOpenedBookmarksIds.find(bookmarkName);
        if (nameToIdIter != m_rOpenedBookmarksIds.end())
        {
            const sal_Int32 nId = nameToIdIter->second;
            const OString   rId = OString::number(nId);

            m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSEND);
        }
    }

    void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
    {
        auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
        for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
        {
            DoWriteBookmarkTagStart(aIter->second);
            m_rOpenedBookmarksIds[aIter->second] = m_nNextBookmarkId;
            m_sLastOpenedBookmark = OUStringToOString(BookmarkToWord(aIter->second), RTL_TEXTENCODING_UTF8).getStr();
            m_nNextBookmarkId++;
        }
    }

    void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
    {
        auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos);
        for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
        {
            // Get the id of the bookmark
            auto pPos = m_rOpenedBookmarksIds.find(aIter->second);
            if (pPos != m_rOpenedBookmarksIds.end())
            {
                // Output the bookmark
                DoWriteBookmarkTagEnd(aIter->second);
                m_rOpenedBookmarksIds.erase(aIter->second);
            }
        }
    }

    /// Write the start bookmarks
    void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts)
    {
        for (const OUString & bookmarkName : rStarts)
        {
            // Output the bookmark
            DoWriteBookmarkTagStart(bookmarkName);

            m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId;
            m_sLastOpenedBookmark = OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8).getStr();
            m_nNextBookmarkId++;
        }
        rStarts.clear();
    }

    /// export the end bookmarks
    void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds)
    {
        for (const OUString & bookmarkName : rEnds)
        {
            // Get the id of the bookmark
            auto pPos = m_rOpenedBookmarksIds.find(bookmarkName);
            if (pPos != m_rOpenedBookmarksIds.end())
            {
                // Output the bookmark
                DoWriteBookmarkTagEnd(bookmarkName);

                m_rOpenedBookmarksIds.erase(bookmarkName);
            }
        }
        rEnds.clear();
    }

    // For construction of the special bookmark name template for permissions:
    // see, PermInsertPosition::createBookmarkName()
    //
    // Syntax:
    // - "permission-for-user:<permission-id>:<permission-user-name>"
    // - "permission-for-group:<permission-id>:<permission-group-name>"
    //
    void DocxAttributeOutput::DoWritePermissionTagStart(const OUString & permission)
    {
        OUString permissionIdAndName;

        if (permission.startsWith("permission-for-group:", &permissionIdAndName))
        {
            const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':');
            const OUString permissionId   = permissionIdAndName.copy(0, sparatorIndex);
            const OUString permissionName = permissionIdAndName.copy(sparatorIndex + 1);

            const OString rId   = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr();
            const OString rName = OUStringToOString(BookmarkToWord(permissionName), RTL_TEXTENCODING_UTF8).getStr();

            m_pSerializer->singleElementNS(XML_w, XML_permStart,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSNS(XML_w, XML_edGrp), rName.getStr(),
                FSEND);
        }
        else // if (permission.startsWith("permission-for-user:", &permissionIdAndName))
        {
            const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':');
            const OUString permissionId   = permissionIdAndName.copy(0, sparatorIndex);
            const OUString permissionName = permissionIdAndName.copy(sparatorIndex + 1);

            const OString rId   = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr();
            const OString rName = OUStringToOString(BookmarkToWord(permissionName), RTL_TEXTENCODING_UTF8).getStr();

            m_pSerializer->singleElementNS(XML_w, XML_permStart,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSNS(XML_w, XML_ed), rName.getStr(),
                FSEND);
        }
    }

    // For construction of the special bookmark name template for permissions:
    // see, PermInsertPosition::createBookmarkName()
    //
    // Syntax:
    // - "permission-for-user:<permission-id>:<permission-user-name>"
    // - "permission-for-group:<permission-id>:<permission-group-name>"
    //
    void DocxAttributeOutput::DoWritePermissionTagEnd(const OUString & permission)
    {
        OUString permissionIdAndName;

        if (permission.startsWith("permission-for-group:", &permissionIdAndName))
        {
            const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':');
            const OUString permissionId   = permissionIdAndName.copy(0, sparatorIndex);
            const OString rId             = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr();

            m_pSerializer->singleElementNS(XML_w, XML_permEnd,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSEND);
        }
        else // if (permission.startsWith("permission-for-user:", &permissionIdAndName))
        {
            const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':');
            const OUString permissionId   = permissionIdAndName.copy(0, sparatorIndex);
            const OString rId             = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr();

            m_pSerializer->singleElementNS(XML_w, XML_permEnd,
                FSNS(XML_w, XML_id), rId.getStr(),
                FSEND);
        }
    }

    /// Write the start permissions
    void DocxAttributeOutput::DoWritePermissionsStart()
    {
        for (const OUString & permission : m_rPermissionsStart)
        {
            DoWritePermissionTagStart(permission);
        }
        m_rPermissionsStart.clear();
    }

    /// export the end permissions
    void DocxAttributeOutput::DoWritePermissionsEnd()
    {
        for (const OUString & permission : m_rPermissionsEnd)
        {
            DoWritePermissionTagEnd(permission);
        }
        m_rPermissionsEnd.clear();
    }

    void DocxAttributeOutput::DoWriteAnnotationMarks()
    {
        // Write the start annotation marks
        for ( const auto & rName : m_rAnnotationMarksStart )
        {
            // Output the annotation mark
            /* Ensure that the existing Annotation Marks are not overwritten
               as it causes discrepancy when DocxAttributeOutput::PostitField
               refers to this map & while mapping comment id's in document.xml &
               comment.xml.
            */
            if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) )
            {
                const sal_Int32 nId = m_nNextAnnotationMarkId++;
                m_rOpenedAnnotationMarksIds[rName] = nId;
                m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart,
                    FSNS( XML_w, XML_id ), OString::number( nId ).getStr(  ),
                    FSEND );
                m_sLastOpenedAnnotationMark = rName;
            }
        }
        m_rAnnotationMarksStart.clear();

        // export the end annotation marks
        for ( const auto & rName : m_rAnnotationMarksEnd )
        {
            // Get the id of the annotation mark
            std::map< OString, sal_Int32 >::iterator pPos = m_rOpenedAnnotationMarksIds.find( rName );
            if ( pPos != m_rOpenedAnnotationMarksIds.end(  ) )
            {
                const sal_Int32 nId = ( *pPos ).second;
                m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd,
                    FSNS( XML_w, XML_id ), OString::number( nId ).getStr(  ),
                    FSEND );
                m_rOpenedAnnotationMarksIds.erase( rName );

                m_pSerializer->startElementNS(XML_w, XML_r, FSEND);
                m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ),
                                                OString::number( nId ).getStr(),
                                                FSEND );
                m_pSerializer->endElementNS(XML_w, XML_r);
            }
        }
        m_rAnnotationMarksEnd.clear();
    }

    void DocxAttributeOutput::WriteFFData(  const FieldInfos& rInfos )
    {
        const ::sw::mark::IFieldmark& rFieldmark = *rInfos.pFieldmark;
        if ( rInfos.eType == ww::eFORMDROPDOWN )
        {
            uno::Sequence< OUString> vListEntries;
            OUString sName, sSelected;

            FieldMarkParamsHelper params( rFieldmark );
            params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries );
            sName = params.getName();
            sal_Int32 nSelectedIndex = 0;

            if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) )
            {
                if (nSelectedIndex < vListEntries.getLength() )
                    sSelected = vListEntries[ nSelectedIndex ];
            }

            GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries );
        }
        else if ( rInfos.eType == ww::eFORMCHECKBOX )
        {
            OUString sName;
            bool bChecked = false;

            FieldMarkParamsHelper params( rFieldmark );
            params.extractParam( ODF_FORMCHECKBOX_NAME, sName );

            const sw::mark::ICheckboxFieldmark* pCheckboxFm = dynamic_cast<const sw::mark::ICheckboxFieldmark*>(&rFieldmark);
            if ( pCheckboxFm && pCheckboxFm->IsChecked() )
                bChecked = true;

            FFDataWriterHelper ffdataOut( m_pSerializer );
            ffdataOut.WriteFormCheckbox( sName, bChecked );
        }
        else if ( rInfos.eType == ww::eFORMTEXT )
        {
            FieldMarkParamsHelper params( rFieldmark );
            FFDataWriterHelper ffdataOut( m_pSerializer );
            ffdataOut.WriteFormText( params.getName() );
        }
    }

    void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
    {
        if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN )
        {
            // Expand unsupported fields
            RunText( rInfos.pField->GetFieldName() );
        }
        else if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands
        {
            if ( bWriteRun )
                m_pSerializer->startElementNS( XML_w, XML_r, FSEND );

            if ( rInfos.eType == ww::eFORMDROPDOWN )
            {
                    m_pSerializer->startElementNS( XML_w, XML_fldChar,
                        FSNS( XML_w, XML_fldCharType ), "begin",
                        FSEND );
                    if ( rInfos.pFieldmark && !rInfos.pField )
                        WriteFFData(  rInfos );
                    if ( rInfos.pField )
                    {
                        const SwDropDownField& rField2 = *static_cast<const SwDropDownField*>(rInfos.pField.get());
                        uno::Sequence<OUString> aItems =
                            rField2.GetItemSequence();
                        GetExport().DoComboBox(rField2.GetName(),
                                   rField2.GetHelp(),
                                   rField2.GetToolTip(),
                                   rField2.GetSelectedItem(), aItems);
                    }
                    m_pSerializer->endElementNS( XML_w, XML_fldChar );

                    if ( bWriteRun )
                        m_pSerializer->endElementNS( XML_w, XML_r );

                    if ( !rInfos.pField )
                        CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
            }
            else
            {
                // Write the field start
                if ( rInfos.pField && (rInfos.pField->Which() == SwFieldIds::DateTime) && rInfos.pField->GetSubType() & FIXEDFLD )
                {
                    m_pSerializer->startElementNS( XML_w, XML_fldChar,
                        FSNS( XML_w, XML_fldCharType ), "begin",
                        FSNS( XML_w, XML_fldLock ), "true",
                        FSEND );
                }
                else
                {
                    m_pSerializer->startElementNS( XML_w, XML_fldChar,
                        FSNS( XML_w, XML_fldCharType ), "begin",
                        FSEND );
                }

                if ( rInfos.pFieldmark )
                    WriteFFData(  rInfos );

                m_pSerializer->endElementNS( XML_w, XML_fldChar );

                if ( bWriteRun )
                    m_pSerializer->endElementNS( XML_w, XML_r );

                // The hyperlinks fields can't be expanded: the value is
                // normally in the text run
                if ( !rInfos.pField )
                    CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
            }
        }
    }

    void DocxAttributeOutput::DoWriteCmd( const OUString& rCmd )
    {
        OUString sCmd = rCmd.trim();
        if (sCmd.startsWith("SEQ"))
        {
            OUString sSeqName = msfilter::util::findQuotedText(sCmd, "SEQ ", '\\').trim();
            m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark);
        }
        // Write the Field command
        m_pSerializer->startElementNS( XML_w, XML_instrText, FSEND );
        m_pSerializer->writeEscaped( rCmd );
        m_pSerializer->endElementNS( XML_w, XML_instrText );

    }

    void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
    {
        bool bWriteCombChars(false);

        // Write the Field instruction
        {
            if ( bWriteRun )
            {
                m_pSerializer->startElementNS( XML_w, XML_r, FSEND );

                if (rInfos.eType == ww::eEQ)
                    bWriteCombChars = true;

                DoWriteFieldRunProperties( pNode, nPos, bWriteCombChars );
            }

            sal_Int32 nIdx { rInfos.sCmd.isEmpty() ? -1 : 0 };
            while ( nIdx >= 0 )
            {
                OUString sToken = rInfos.sCmd.getToken( 0, '\t', nIdx );
                if ( rInfos.eType ==  ww::eCREATEDATE
                  || rInfos.eType ==  ww::eSAVEDATE
                  || rInfos.eType ==  ww::ePRINTDATE
                  || rInfos.eType ==  ww::eDATE
                  || rInfos.eType ==  ww::eTIME )
                {
                   sToken = sToken.replaceAll("NNNN", "dddd");
                   sToken = sToken.replaceAll("NN", "ddd");
                }

                // Write the Field command
                DoWriteCmd( sToken );

                // Replace tabs by </instrText><tab/><instrText>
                if ( nIdx > 0 ) // Is another token expected?
                    RunText( "\t" );
            }

            if ( bWriteRun )
            {
                m_pSerializer->endElementNS( XML_w, XML_r );
            }
        }

        // Write the Field separator
        {
            if ( bWriteRun )
            {
                m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
                DoWriteFieldRunProperties( pNode, nPos );
            }

            m_pSerializer->singleElementNS( XML_w, XML_fldChar,
                  FSNS( XML_w, XML_fldCharType ), "separate",
                  FSEND );

            if ( bWriteRun )
            {
                m_pSerializer->endElementNS( XML_w, XML_r );
            }
        }
    }

    /// Writes properties for run that is used to separate field implementation.
    /// There are several runs are used:
    ///     <w:r>
    ///         <w:rPr>
    ///             <!-- properties written with StartRunProperties() / EndRunProperties().
    ///         </w:rPr>
    ///         <w:fldChar w:fldCharType="begin" />
    ///     </w:r>
    ///         <w:r>
    ///         <w:rPr>
    ///             <!-- properties written with DoWriteFieldRunProperties()
    ///         </w:rPr>
    ///         <w:instrText>TIME \@"HH:mm:ss"</w:instrText>
    ///     </w:r>
    ///     <w:r>
    ///         <w:rPr>
    ///             <!-- properties written with DoWriteFieldRunProperties()
    ///         </w:rPr>
    ///         <w:fldChar w:fldCharType="separate" />
    ///     </w:r>
    ///     <w:r>
    ///         <w:rPr>
    ///             <!-- properties written with DoWriteFieldRunProperties()
    ///         </w:rPr>
    ///         <w:t>14:01:13</w:t>
    ///         </w:r>
    ///     <w:r>
    ///         <w:rPr>
    ///             <!-- properties written with DoWriteFieldRunProperties()
    ///         </w:rPr>
    ///         <w:fldChar w:fldCharType="end" />
    ///     </w:r>
    /// See, tdf#38778
    void DocxAttributeOutput::DoWriteFieldRunProperties( const SwTextNode * pNode, sal_Int32 nPos, bool bWriteCombChars)
    {
        if (! pNode)
        {
            // nothing to do
            return;
        }

        m_bPreventDoubleFieldsHandling = true;

        {
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );

            // 1. output webHidden flag
            if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
            {
                m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND );
            }

            // 2. output color
            if ( m_pColorAttrList.is() )
            {
                XFastAttributeListRef xAttrList( m_pColorAttrList.get() );
                m_pColorAttrList.clear();

                m_pSerializer->singleElementNS( XML_w, XML_color, xAttrList );
            }

            // 3. output all other character properties
            SwWW8AttrIter aAttrIt( m_rExport, *pNode );
            aAttrIt.OutAttr( nPos, bWriteCombChars );

            // 4. explicitly write the font-properties, to ensure all runs in the field have them
            // see tdf#66401
            if ( m_pFontsAttrList.is() )
            {
                XFastAttributeListRef xAttrList( m_pFontsAttrList.get() );
                m_pFontsAttrList.clear();

                m_pSerializer->singleElementNS( XML_w, XML_rFonts, xAttrList );
            }

            m_pSerializer->endElementNS( XML_w, XML_rPr );

            // During SwWW8AttrIter::OutAttr() call the new value of the text color could be set into [m_pColorAttrList].
            // But we do not need to keep it any more and should clean up,
            // While the next run could define a new color that is different to current one.
            m_pColorAttrList.clear();
        }

        m_bPreventDoubleFieldsHandling = false;
    }

    void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos )
    {
        // The command has to be written before for the hyperlinks
        if ( rInfos.pField )
        {
            CmdField_Impl( pNode, nPos, rInfos, true );
        }

        // Write the bookmark start if any
        if ( !m_sFieldBkm.isEmpty() )
        {
            DoWriteBookmarkTagStart(m_sFieldBkm);
        }

        if (rInfos.pField ) // For hyperlinks and TOX
        {
            // Write the Field latest value
            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            DoWriteFieldRunProperties( pNode, nPos );

            OUString sExpand;
            if(rInfos.eType == ww::eCITATION)
            {
                sExpand = static_cast<SwAuthorityField const*>(rInfos.pField.get())
                            ->ExpandCitation(AUTH_FIELD_TITLE);
            }
            else
            {
                sExpand = rInfos.pField->ExpandField( true );
            }
            // newlines embedded in fields are 0x0B in MSO and 0x0A for us
            RunText(sExpand.replace(0x0A, 0x0B));

            m_pSerializer->endElementNS( XML_w, XML_r );
        }

        // Write the bookmark end if any
        if ( !m_sFieldBkm.isEmpty() )
        {
            DoWriteBookmarkTagEnd(m_sFieldBkm);

            m_nNextBookmarkId++;
        }

        // Write the Field end
        if ( rInfos.bClose  )
        {
            m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
            DoWriteFieldRunProperties( pNode, nPos );
            m_pSerializer->singleElementNS( XML_w, XML_fldChar,
                  FSNS( XML_w, XML_fldCharType ), "end",
                  FSEND );
            m_pSerializer->endElementNS( XML_w, XML_r );
        }
        // Write the ref field if a bookmark had to be set and the field
        // should be visible
        if ( rInfos.pField )
        {
            sal_uInt16 nSubType = rInfos.pField->GetSubType( );
            bool bIsSetField = rInfos.pField->GetTyp( )->Which( ) == SwFieldIds::SetExp;
            bool bShowRef = bIsSetField && ( nSubType & nsSwExtendedSubType::SUB_INVISIBLE ) == 0;

            if ( ( !m_sFieldBkm.isEmpty() ) && bShowRef )
            {
                // Write the field beginning
                m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
                m_pSerializer->singleElementNS( XML_w, XML_fldChar,
                    FSNS( XML_w, XML_fldCharType ), "begin",
                    FSEND );
                m_pSerializer->endElementNS( XML_w, XML_r );

                rInfos.sCmd = FieldString( ww::eREF );
                rInfos.sCmd += "\"";
                rInfos.sCmd += m_sFieldBkm;
                rInfos.sCmd += "\" ";

                // Clean the field bookmark data to avoid infinite loop
                m_sFieldBkm = OUString( );

                // Write the end of the field
                EndField_Impl( pNode, nPos, rInfos );
            }
        }
    }

    void DocxAttributeOutput::StartRunProperties()
    {
        // postpone the output so that we can later [in EndRunProperties()]
        // prepend the properties before the text
        m_pSerializer->mark(Tag_StartRunProperties);

        m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );

        if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
        {
            m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND );
        }
        InitCollectedRunProperties();

        assert( !m_pPostponedGraphic );
        m_pPostponedGraphic.reset(new std::vector<PostponedGraphic>);

        assert( !m_pPostponedDiagrams );
        m_pPostponedDiagrams.reset(new std::vector<PostponedDiagram>);

        assert(!m_pPostponedDMLDrawings);
        m_pPostponedDMLDrawings.reset(new std::vector<PostponedDrawing>);

        assert( !m_pPostponedOLEs );
        m_pPostponedOLEs.reset(new std::vector<PostponedOLE>);
    }

    void DocxAttributeOutput::InitCollectedRunProperties()
    {
        m_pFontsAttrList = nullptr;
        m_pEastAsianLayoutAttrList = nullptr;
        m_pCharLangAttrList = nullptr;

        // Write the elements in the spec order
        static const sal_Int32 aOrder[] =
        {
            FSNS( XML_w, XML_rStyle ),
            FSNS( XML_w, XML_rFonts ),
            FSNS( XML_w, XML_b ),
            FSNS( XML_w, XML_bCs ),
            FSNS( XML_w, XML_i ),
            FSNS( XML_w, XML_iCs ),
            FSNS( XML_w, XML_caps ),
            FSNS( XML_w, XML_smallCaps ),
            FSNS( XML_w, XML_strike ),
            FSNS( XML_w, XML_dstrike ),
            FSNS( XML_w, XML_outline ),
            FSNS( XML_w, XML_shadow ),
            FSNS( XML_w, XML_emboss ),
            FSNS( XML_w, XML_imprint ),
            FSNS( XML_w, XML_noProof ),
            FSNS( XML_w, XML_snapToGrid ),
            FSNS( XML_w, XML_vanish ),
            FSNS( XML_w, XML_webHidden ),
            FSNS( XML_w, XML_color ),
            FSNS( XML_w, XML_spacing ),
            FSNS( XML_w, XML_w ),
            FSNS( XML_w, XML_kern ),
            FSNS( XML_w, XML_position ),
            FSNS( XML_w, XML_sz ),
            FSNS( XML_w, XML_szCs ),
            FSNS( XML_w, XML_highlight ),
            FSNS( XML_w, XML_u ),
            FSNS( XML_w, XML_effect ),
            FSNS( XML_w, XML_bdr ),
            FSNS( XML_w, XML_shd ),
            FSNS( XML_w, XML_fitText ),
            FSNS( XML_w, XML_vertAlign ),
            FSNS( XML_w, XML_rtl ),
            FSNS( XML_w, XML_cs ),
            FSNS( XML_w, XML_em ),
            FSNS( XML_w, XML_lang ),
            FSNS( XML_w, XML_eastAsianLayout ),
            FSNS( XML_w, XML_specVanish ),
            FSNS( XML_w, XML_oMath ),
            FSNS( XML_w, XML_rPrChange ),
            FSNS( XML_w14, XML_glow ),
            FSNS( XML_w14, XML_shadow ),
            FSNS( XML_w14, XML_reflection ),
            FSNS( XML_w14, XML_textOutline ),
            FSNS( XML_w14, XML_textFill ),
            FSNS( XML_w14, XML_scene3d ),
            FSNS( XML_w14, XML_props3d ),
            FSNS( XML_w14, XML_ligatures ),
            FSNS( XML_w14, XML_numForm ),
            FSNS( XML_w14, XML_numSpacing ),
            FSNS( XML_w14, XML_stylisticSets ),
            FSNS( XML_w14, XML_cntxtAlts ),
        };

        // postpone the output so that we can later [in EndParagraphProperties()]
        // prepend the properties before the run
        m_pSerializer->mark(Tag_InitCollectedRunProperties, comphelper::containerToSequence(aOrder));
    }

    namespace
    {

    struct NameToId
    {
        OUString  maName;
        sal_Int32 maId;
    };

    const NameToId constNameToIdMapping[] =
    {
        { OUString("glow"),         FSNS( XML_w14, XML_glow ) },
        { OUString("shadow"),       FSNS( XML_w14, XML_shadow ) },
        { OUString("reflection"),   FSNS( XML_w14, XML_reflection ) },
        { OUString("textOutline"),  FSNS( XML_w14, XML_textOutline ) },
        { OUString("textFill"),     FSNS( XML_w14, XML_textFill ) },
        { OUString("scene3d"),      FSNS( XML_w14, XML_scene3d ) },
        { OUString("props3d"),      FSNS( XML_w14, XML_props3d ) },
        { OUString("ligatures"),    FSNS( XML_w14, XML_ligatures ) },
        { OUString("numForm"),      FSNS( XML_w14, XML_numForm ) },
        { OUString("numSpacing"),   FSNS( XML_w14, XML_numSpacing ) },
        { OUString("stylisticSets"),FSNS( XML_w14, XML_stylisticSets ) },
        { OUString("cntxtAlts"),    FSNS( XML_w14, XML_cntxtAlts ) },

        { OUString("val"),          FSNS( XML_w14, XML_val ) },
        { OUString("rad"),          FSNS( XML_w14, XML_rad ) },
        { OUString("blurRad"),      FSNS( XML_w14, XML_blurRad ) },
        { OUString("stA"),          FSNS( XML_w14, XML_stA ) },
        { OUString("stPos"),        FSNS( XML_w14, XML_stPos ) },
        { OUString("endA"),         FSNS( XML_w14, XML_endA ) },
        { OUString("endPos"),       FSNS( XML_w14, XML_endPos ) },
        { OUString("dist"),         FSNS( XML_w14, XML_dist ) },
        { OUString("dir"),          FSNS( XML_w14, XML_dir ) },
        { OUString("fadeDir"),      FSNS( XML_w14, XML_fadeDir ) },
        { OUString("sx"),           FSNS( XML_w14, XML_sx ) },
        { OUString("sy"),           FSNS( XML_w14, XML_sy ) },
        { OUString("kx"),           FSNS( XML_w14, XML_kx ) },
        { OUString("ky"),           FSNS( XML_w14, XML_ky ) },
        { OUString("algn"),         FSNS( XML_w14, XML_algn ) },
        { OUString("w"),            FSNS( XML_w14, XML_w ) },
        { OUString("cap"),          FSNS( XML_w14, XML_cap ) },
        { OUString("cmpd"),         FSNS( XML_w14, XML_cmpd ) },
        { OUString("pos"),          FSNS( XML_w14, XML_pos ) },
        { OUString("ang"),          FSNS( XML_w14, XML_ang ) },
        { OUString("scaled"),       FSNS( XML_w14, XML_scaled ) },
        { OUString("path"),         FSNS( XML_w14, XML_path ) },
        { OUString("l"),            FSNS( XML_w14, XML_l ) },
        { OUString("t"),            FSNS( XML_w14, XML_t ) },
        { OUString("r"),            FSNS( XML_w14, XML_r ) },
        { OUString("b"),            FSNS( XML_w14, XML_b ) },
        { OUString("lim"),          FSNS( XML_w14, XML_lim ) },
        { OUString("prst"),         FSNS( XML_w14, XML_prst ) },
        { OUString("rig"),          FSNS( XML_w14, XML_rig ) },
        { OUString("lat"),          FSNS( XML_w14, XML_lat ) },
        { OUString("lon"),          FSNS( XML_w14, XML_lon ) },
        { OUString("rev"),          FSNS( XML_w14, XML_rev ) },
        { OUString("h"),            FSNS( XML_w14, XML_h ) },
        { OUString("extrusionH"),   FSNS( XML_w14, XML_extrusionH ) },
        { OUString("contourW"),     FSNS( XML_w14, XML_contourW ) },
        { OUString("prstMaterial"), FSNS( XML_w14, XML_prstMaterial ) },
        { OUString("id"),           FSNS( XML_w14, XML_id ) },

        { OUString("schemeClr"),    FSNS( XML_w14, XML_schemeClr ) },
        { OUString("srgbClr"),      FSNS( XML_w14, XML_srgbClr ) },
        { OUString("tint"),         FSNS( XML_w14, XML_tint ) },
        { OUString("shade"),        FSNS( XML_w14, XML_shade ) },
        { OUString("alpha"),        FSNS( XML_w14, XML_alpha ) },
        { OUString("hueMod"),       FSNS( XML_w14, XML_hueMod ) },
        { OUString("sat"),          FSNS( XML_w14, XML_sat ) },
        { OUString("satOff"),       FSNS( XML_w14, XML_satOff ) },
        { OUString("satMod"),       FSNS( XML_w14, XML_satMod ) },
        { OUString("lum"),          FSNS( XML_w14, XML_lum ) },
        { OUString("lumOff"),       FSNS( XML_w14, XML_lumOff ) },
        { OUString("lumMod"),       FSNS( XML_w14, XML_lumMod ) },
        { OUString("noFill"),       FSNS( XML_w14, XML_noFill ) },
        { OUString("solidFill"),    FSNS( XML_w14, XML_solidFill ) },
        { OUString("gradFill"),     FSNS( XML_w14, XML_gradFill ) },
        { OUString("gsLst"),        FSNS( XML_w14, XML_gsLst ) },
        { OUString("gs"),           FSNS( XML_w14, XML_gs ) },
        { OUString("pos"),          FSNS( XML_w14, XML_pos ) },
        { OUString("lin"),          FSNS( XML_w14, XML_lin ) },
        { OUString("path"),         FSNS( XML_w14, XML_path ) },
        { OUString("fillToRect"),   FSNS( XML_w14, XML_fillToRect ) },
        { OUString("prstDash"),     FSNS( XML_w14, XML_prstDash ) },
        { OUString("round"),        FSNS( XML_w14, XML_round ) },
        { OUString("bevel"),        FSNS( XML_w14, XML_bevel ) },
        { OUString("miter"),        FSNS( XML_w14, XML_miter ) },
        { OUString("camera"),       FSNS( XML_w14, XML_camera ) },
        { OUString("lightRig"),     FSNS( XML_w14, XML_lightRig ) },
        { OUString("rot"),          FSNS( XML_w14, XML_rot ) },
        { OUString("bevelT"),       FSNS( XML_w14, XML_bevelT ) },
        { OUString("bevelB"),       FSNS( XML_w14, XML_bevelB ) },
        { OUString("extrusionClr"), FSNS( XML_w14, XML_extrusionClr ) },
        { OUString("contourClr"),   FSNS( XML_w14, XML_contourClr ) },
        { OUString("styleSet"),     FSNS( XML_w14, XML_styleSet ) },
    };

    boost::optional<sal_Int32> lclGetElementIdForName(const OUString& rName)
    {
        for (auto const & i : constNameToIdMapping)
        {
            if (rName == i.maName)
            {
                return i.maId;
            }
        }
        return boost::optional<sal_Int32>();
    }

    void lclProcessRecursiveGrabBag(sal_Int32 aElementId, const css::uno::Sequence<css::beans::PropertyValue>& rElements, sax_fastparser::FSHelperPtr const & pSerializer)
    {
        css::uno::Sequence<css::beans::PropertyValue> aAttributes;
        FastAttributeList* pAttributes = FastSerializerHelper::createAttrList();

        for (sal_Int32 j=0; j < rElements.getLength(); ++j)
        {
            if (rElements[j].Name == "attributes")
            {
                rElements[j].Value >>= aAttributes;
            }
        }

        for (sal_Int32 j=0; j < aAttributes.getLength(); ++j)
        {
            uno::Any aAny = aAttributes[j].Value;
            OString aValue;

            if(aAny.getValueType() == cppu::UnoType<sal_Int32>::get())
            {
                aValue = OString::number(aAny.get<sal_Int32>());
            }
            else if(aAny.getValueType() == cppu::UnoType<OUString>::get())
            {
                aValue =  OUStringToOString(aAny.get<OUString>(), RTL_TEXTENCODING_ASCII_US);
            }

            boost::optional<sal_Int32> aSubElementId = lclGetElementIdForName(aAttributes[j].Name);
            if(aSubElementId)
                pAttributes->add(*aSubElementId, aValue.getStr());
        }

        XFastAttributeListRef xAttributesList( pAttributes );

        pSerializer->startElement(aElementId, xAttributesList);

        for (sal_Int32 j=0; j < rElements.getLength(); ++j)
        {
            css::uno::Sequence<css::beans::PropertyValue> aSumElements;

            boost::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rElements[j].Name);
            if(aSubElementId)
            {
                rElements[j].Value >>= aSumElements;
                lclProcessRecursiveGrabBag(*aSubElementId, aSumElements, pSerializer);
            }
        }

        pSerializer->endElement(aElementId);
    }

    }

    void DocxAttributeOutput::WriteCollectedRunProperties()
    {
        // Write all differed properties
        if ( m_pFontsAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pFontsAttrList.get() );
            m_pFontsAttrList.clear();
            m_pSerializer->singleElementNS( XML_w, XML_rFonts, xAttrList );
        }

        if ( m_pColorAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pColorAttrList.get() );
            m_pColorAttrList.clear();

            m_pSerializer->singleElementNS( XML_w, XML_color, xAttrList );
        }

        if ( m_pEastAsianLayoutAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pEastAsianLayoutAttrList.get() );
            m_pEastAsianLayoutAttrList.clear();
            m_pSerializer->singleElementNS( XML_w, XML_eastAsianLayout, xAttrList );
        }

        if ( m_pCharLangAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pCharLangAttrList.get() );
            m_pCharLangAttrList.clear();
            m_pSerializer->singleElementNS( XML_w, XML_lang, xAttrList );
        }

        if (!m_aTextEffectsGrabBag.empty())
        {
            for (beans::PropertyValue & i : m_aTextEffectsGrabBag)
            {
                boost::optional<sal_Int32> aElementId = lclGetElementIdForName(i.Name);
                if(aElementId)
                {
                    uno::Sequence<beans::PropertyValue> aGrabBagSeq;
                    i.Value >>= aGrabBagSeq;
                    lclProcessRecursiveGrabBag(*aElementId, aGrabBagSeq, m_pSerializer);
                }
            }
            m_aTextEffectsGrabBag.clear();
        }
    }

    void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
    {
        // Call the 'Redline' function. This will add redline (change-tracking) information that regards to run properties.
        // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
        Redline( pRedlineData );

        WriteCollectedRunProperties();

        // Merge the marks for the ordered elements
        m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);

        m_pSerializer->endElementNS( XML_w, XML_rPr );

        // write footnotes/endnotes if we have any
        FootnoteEndnoteReference();

        WritePostponedGraphic();

        WritePostponedDiagram();
        //We need to write w:drawing tag after the w:rPr.
        WritePostponedChart();

        //We need to write w:pict tag after the w:rPr.
        WritePostponedDMLDrawing();

        WritePostponedOLE();

        WritePostponedActiveXControl(true);

        // merge the properties _before_ the run text (strictly speaking, just
        // after the start of the run)
        m_pSerializer->mergeTopMarks(Tag_StartRunProperties, sax_fastparser::MergeMarks::PREPEND);
    }

    void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj)
    {
        if (pSdrObj)
        {
            uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY_THROW);
            uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
            if( xPropSet.is() )
            {
                uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
                uno::Sequence< beans::PropertyValue > aGrabBag;
                if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("FrameInteropGrabBag"))
                {
                    xPropSet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
                }
                else if(xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag"))
                {
                    xPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
                }

                for (sal_Int32 nProp=0; nProp < aGrabBag.getLength(); ++nProp)
                {
                    if ("SdtEndBefore" == aGrabBag[nProp].Name && m_bStartedCharSdt && !m_bEndCharSdt)
                    {
                        aGrabBag[nProp].Value >>= m_bEndCharSdt;
                        break;
                    }
                }
            }
        }
    }

    void DocxAttributeOutput::WritePostponedGraphic()
    {
        for (const auto & rPostponedDiagram : *m_pPostponedGraphic)
            FlyFrameGraphic(rPostponedDiagram.grfNode, rPostponedDiagram.size,
                nullptr, nullptr,
                rPostponedDiagram.pSdrObj);
        m_pPostponedGraphic.reset(nullptr);
    }

    void DocxAttributeOutput::WritePostponedDiagram()
    {
        for( const auto & rPostponedDiagram : *m_pPostponedDiagrams )
            m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object,
                *rPostponedDiagram.frame, m_anchorId++);
        m_pPostponedDiagrams.reset(nullptr);
    }

    bool DocxAttributeOutput::FootnoteEndnoteRefTag()
    {
        if( m_footnoteEndnoteRefTag == 0 )
            return false;

        // output the character style for MS Word's benefit
        const SwEndNoteInfo& rInfo = m_footnoteEndnoteRefTag == XML_footnoteRef ?
            m_rExport.m_pDoc->GetFootnoteInfo() : m_rExport.m_pDoc->GetEndNoteInfo();
        const SwCharFormat* pCharFormat = rInfo.GetCharFormat( *m_rExport.m_pDoc );
        if ( pCharFormat )
        {
            const OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_rStyle, FSNS( XML_w, XML_val ), aStyleId.getStr(), FSEND );
            m_pSerializer->endElementNS( XML_w, XML_rPr );
        }

        m_pSerializer->singleElementNS( XML_w, m_footnoteEndnoteRefTag, FSEND );
        m_footnoteEndnoteRefTag = 0;
        return true;
    }

    /** Output sal_Unicode* as a run text (<t>the text</t>).

        When bMove is true, update rBegin to point _after_ the end of the text +
        1, meaning that it skips one character after the text.  This is to make
        the switch in DocxAttributeOutput::RunText() nicer ;-)
     */
    static bool impl_WriteRunText( FSHelperPtr const & pSerializer, sal_Int32 nTextToken,
            const sal_Unicode* &rBegin, const sal_Unicode* pEnd, bool bMove = true )
    {
        const sal_Unicode *pBegin = rBegin;

        // skip one character after the end
        if ( bMove )
            rBegin = pEnd + 1;

        if ( pBegin >= pEnd )
            return false; // we want to write at least one character

        // we have to add 'preserve' when starting/ending with space
        if ( *pBegin == ' ' || *( pEnd - 1 ) == ' ' )
        {
            pSerializer->startElementNS( XML_w, nTextToken, FSNS( XML_xml, XML_space ), "preserve", FSEND );
        }
        else
            pSerializer->startElementNS( XML_w, nTextToken, FSEND );

        pSerializer->writeEscaped( OUString( pBegin, pEnd - pBegin ) );

        pSerializer->endElementNS( XML_w, nTextToken );

        return true;
    }

    void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCharSet*/ )
    {
        if( m_closeHyperlinkInThisRun )
        {
            m_closeHyperlinkInPreviousRun = true;
        }
        m_bRunTextIsOn = true;
        // one text can be split into more <w:t>blah</w:t>'s by line breaks etc.
        const sal_Unicode *pBegin = rText.getStr();
        const sal_Unicode *pEnd = pBegin + rText.getLength();

        // the text run is usually XML_t, with the exception of the deleted text
        sal_Int32 nTextToken = XML_t;
        if ( m_pRedlineData && m_pRedlineData->GetType() == nsRedlineType_t::REDLINE_DELETE )
            nTextToken = XML_delText;

        sal_Unicode prevUnicode = *pBegin;

        for ( const sal_Unicode *pIt = pBegin; pIt < pEnd; ++pIt )
        {
            switch ( *pIt )
            {
                case 0x09: // tab
                    impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
                    m_pSerializer->singleElementNS( XML_w, XML_tab, FSEND );
                    prevUnicode = *pIt;
                    break;
                case 0x0b: // line break
                    {
                        if (impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ) || (prevUnicode == *pIt))
                        {
                            m_pSerializer->singleElementNS( XML_w, XML_br, FSEND );
                            prevUnicode = *pIt;
                        }
                    }
                    break;
                case 0x1E: //non-breaking hyphen
                    impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
                    m_pSerializer->singleElementNS( XML_w, XML_noBreakHyphen, FSEND );
                    prevUnicode = *pIt;
                    break;
                case 0x1F: //soft (on demand) hyphen
                    impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
                    m_pSerializer->singleElementNS( XML_w, XML_softHyphen, FSEND );
                    prevUnicode = *pIt;
                    break;
                default:
                    if ( *pIt < 0x0020 ) // filter out the control codes
                    {
                        impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
                        SAL_INFO("sw.ww8", "Ignored control code in a text run: " << *pIt );
                    }
                    prevUnicode = *pIt;
                    break;
            }
        }

        impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pEnd, false );
    }

    void DocxAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding /*eCharSet*/)
    {
        m_sRawText = rText;
    }

    void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, const SwFormatRuby& rRuby )
    {
        WW8Ruby aWW8Ruby( rNode, rRuby, GetExport() );
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRuby( const SwTextNode& rNode, const SwFormatRuby& rRuby )" );
        EndRun( &rNode, nPos ); // end run before starting ruby to avoid nested runs, and overlap
        assert(!m_closeHyperlinkInThisRun); // check that no hyperlink overlaps ruby
        assert(!m_closeHyperlinkInPreviousRun);
        m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
        m_pSerializer->startElementNS( XML_w, XML_ruby, FSEND );
        m_pSerializer->startElementNS( XML_w, XML_rubyPr, FSEND );

        m_pSerializer->singleElementNS( XML_w, XML_rubyAlign,
                FSNS( XML_w, XML_val ), lclConvertWW8JCToOOXMLRubyAlign(aWW8Ruby.GetJC()), FSEND );
        sal_uInt32   nHps = (aWW8Ruby.GetRubyHeight() + 5) / 10;
        sal_uInt32   nHpsBaseText = (aWW8Ruby.GetBaseHeight() + 5) / 10;
        m_pSerializer->singleElementNS( XML_w, XML_hps,
                FSNS( XML_w, XML_val ), OString::number(nHps).getStr(), FSEND );

        m_pSerializer->singleElementNS( XML_w, XML_hpsRaise,
                FSNS( XML_w, XML_val ), OString::number(nHpsBaseText).getStr(), FSEND );

        m_pSerializer->singleElementNS( XML_w, XML_hpsBaseText,
                FSNS( XML_w, XML_val ), OString::number(nHpsBaseText).getStr(), FSEND );

        lang::Locale aLocale( SwBreakIt::Get()->GetLocale(
                    rNode.GetLang( nPos ) ) );
        OUString sLang( LanguageTag::convertToBcp47( aLocale) );
        m_pSerializer->singleElementNS( XML_w, XML_lid,
                FSNS( XML_w, XML_val ),
                OUStringToOString( sLang, RTL_TEXTENCODING_UTF8 ).getStr( ), FSEND );

        m_pSerializer->endElementNS( XML_w, XML_rubyPr );

        m_pSerializer->startElementNS( XML_w, XML_rt, FSEND );
        StartRun( nullptr, nPos );
        StartRunProperties( );

        if (rRuby.GetTextRuby() && rRuby.GetTextRuby()->GetCharFormat())
        {
            const SwCharFormat* pFormat = rRuby.GetTextRuby()->GetCharFormat();
            sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
            sal_uInt16 nWhichFont = (nScript == i18n::ScriptType::LATIN) ?  RES_CHRATR_FONT : RES_CHRATR_CJK_FONT;
            sal_uInt16 nWhichFontSize = (nScript == i18n::ScriptType::LATIN) ?  RES_CHRATR_FONTSIZE : RES_CHRATR_CJK_FONTSIZE;

            CharFont(ItemGet<SvxFontItem>(*pFormat, nWhichFont));
            CharFontSize(ItemGet<SvxFontHeightItem>(*pFormat, nWhichFontSize));
            CharFontSize(ItemGet<SvxFontHeightItem>(*pFormat, RES_CHRATR_CTL_FONTSIZE));
        }

        EndRunProperties( nullptr );
        RunText( rRuby.GetText( ) );
        EndRun( &rNode, nPos );
        m_pSerializer->endElementNS( XML_w, XML_rt );

        m_pSerializer->startElementNS( XML_w, XML_rubyBase, FSEND );
        StartRun( nullptr, nPos );
    }

    void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
    {
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRuby()" );
        EndRun( &rNode, nPos );
        m_pSerializer->endElementNS( XML_w, XML_rubyBase );
        m_pSerializer->endElementNS( XML_w, XML_ruby );
        m_pSerializer->endElementNS( XML_w, XML_r );
        StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it
    }

    bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
    {
        bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );

        if ( !pMark->isEmpty() )
        {
            OUString sURL = *pLinkURL;

            if ( bBookMarkOnly )
                sURL = FieldString( ww::eHYPERLINK );
            else
                sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";

            sURL += " \\l \"" + *pMark + "\"";

            if ( !rTarget.isEmpty() )
                sURL += " \\n " + rTarget;

            *pLinkURL = sURL;
        }

        return bBookMarkOnly;
    }

    void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
    {
        m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
        m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
    }

    bool DocxAttributeOutput::StartURL( const OUString& rUrl, const OUString& rTarget )
    {
        OUString sMark;
        OUString sUrl;

        bool bBookmarkOnly = AnalyzeURL( rUrl, rTarget, &sUrl, &sMark );

        m_hyperLinkAnchor = sMark;

        if ( !sMark.isEmpty() && !bBookmarkOnly )
        {
            m_rExport.OutputField( nullptr, ww::eHYPERLINK, sUrl );
        }
        else
        {
            // Output a hyperlink XML element
            m_pHyperlinkAttrList = FastSerializerHelper::createAttrList();

            if ( !bBookmarkOnly )
            {
                OString sId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
                            oox::getRelationship(Relationship::HYPERLINK),
                            sUrl, true ), RTL_TEXTENCODING_UTF8 );

                m_pHyperlinkAttrList->add( FSNS( XML_r, XML_id), sId.getStr());
            }
            else
            {
                // Is this a link to a sequence? Then try to replace that with a
                // normal bookmark, as Word won't understand our special
                // <seqname>!<index>|sequence syntax.
                if (sMark.endsWith("|sequence"))
                {
                    sal_Int32 nPos = sMark.indexOf('!');
                    if (nPos != -1)
                    {
                        // Extract <seqname>, the field instruction text has the name quoted.
                        OUString aSequenceName = sMark.copy(0, nPos);
                        // Extract <index>.
                        sal_uInt32 nIndex = sMark.copy(nPos + 1, sMark.getLength() - nPos - sizeof("|sequence")).toUInt32();
                        std::map<OUString, std::vector<OString> >::iterator it = m_aSeqBookmarksNames.find(aSequenceName);
                        if (it != m_aSeqBookmarksNames.end())
                        {
                            std::vector<OString>& rNames = it->second;
                            if (rNames.size() > nIndex)
                                // We know the bookmark name for this sequence and this index, do the replacement.
                                sMark = OStringToOUString(rNames[nIndex], RTL_TEXTENCODING_UTF8);
                        }
                    }
                }
                m_pHyperlinkAttrList->add( FSNS( XML_w, XML_anchor ),
                        OUStringToOString( sMark, RTL_TEXTENCODING_UTF8 ).getStr( ) );
            }

            if ( !rTarget.isEmpty() )
            {
                OString soTarget = OUStringToOString( rTarget, RTL_TEXTENCODING_UTF8 );
                m_pHyperlinkAttrList->add(FSNS( XML_w, XML_tgtFrame ), soTarget.getStr());
            }
        }

        return true;
    }

    bool DocxAttributeOutput::EndURL(bool const)
    {
        m_closeHyperlinkInThisRun = true;
        if(m_startedHyperlink && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc"))
        {
            m_endPageRef = true;
        }
        return true;
    }

    void DocxAttributeOutput::FieldVanish( const OUString& rText, ww::eField eType )
    {
        WriteField_Impl( nullptr, eType, rText, FieldFlags::All );
    }

    // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
    // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
    // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
    void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData)
    {
        if ( !pRedlineData )
            return;

        OString aId( OString::number( pRedlineData->GetSeqNo() ) );
        const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
        OString aAuthor( rAuthor.toUtf8() );
        OString aDate( DateTimeToOString( pRedlineData->GetTimeStamp() ) );

        switch( pRedlineData->GetType() )
        {
        case nsRedlineType_t::REDLINE_INSERT:
            break;

        case nsRedlineType_t::REDLINE_DELETE:
            break;

        case nsRedlineType_t::REDLINE_FORMAT:
            m_pSerializer->startElementNS( XML_w, XML_rPrChange,
                    FSNS( XML_w, XML_id ), aId.getStr(),
                    FSNS( XML_w, XML_author ), aAuthor.getStr(),
                    FSNS( XML_w, XML_date ), aDate.getStr(),
                    FSEND );

            // Check if there is any extra data stored in the redline object
            if (pRedlineData->GetExtraData())
            {
                const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
                const SwRedlineExtraData_FormattingChanges* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormattingChanges*>(pExtraData);

                // Check if the extra data is of type 'formatting changes'
                if (pFormattingChanges)
                {
                    // Get the item set that holds all the changes properties
                    const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
                    if (pChangesSet)
                    {
                        m_pSerializer->mark(Tag_Redline_1);

                        m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );

                        // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information
                        // that should be collected by different properties in the core, and are all flushed together
                        // to the DOCX when the function 'WriteCollectedRunProperties' gets called.
                        // So we need to store the current status of these lists, so that we can revert back to them when
                        // we are done exporting the redline attributes.
                        rtl::Reference<sax_fastparser::FastAttributeList> pFontsAttrList_Original(m_pFontsAttrList);
                        m_pFontsAttrList.clear();
                        rtl::Reference<sax_fastparser::FastAttributeList> pEastAsianLayoutAttrList_Original(m_pEastAsianLayoutAttrList);
                        m_pEastAsianLayoutAttrList.clear();
                        rtl::Reference<sax_fastparser::FastAttributeList> pCharLangAttrList_Original(m_pCharLangAttrList);
                        m_pCharLangAttrList.clear();

                        // Output the redline item set
                        m_rExport.OutputItemSet( *pChangesSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );

                        // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
                        WriteCollectedRunProperties();

                        // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
                        m_pFontsAttrList = pFontsAttrList_Original;
                        m_pEastAsianLayoutAttrList = pEastAsianLayoutAttrList_Original;
                        m_pCharLangAttrList = pCharLangAttrList_Original;

                        m_pSerializer->endElementNS( XML_w, XML_rPr );

                        m_pSerializer->mergeTopMarks(Tag_Redline_1, sax_fastparser::MergeMarks::PREPEND);
                    }
                }
            }
            m_pSerializer->endElementNS( XML_w, XML_rPrChange );
            break;

        case nsRedlineType_t::REDLINE_PARAGRAPH_FORMAT:
            m_pSerializer->startElementNS( XML_w, XML_pPrChange,
                    FSNS( XML_w, XML_id ), aId.getStr(),
                    FSNS( XML_w, XML_author ), aAuthor.getStr(),
                    FSNS( XML_w, XML_date ), aDate.getStr(),
                    FSEND );

            // Check if there is any extra data stored in the redline object
            if (pRedlineData->GetExtraData())
            {
                const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
                const SwRedlineExtraData_FormattingChanges* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormattingChanges*>(pExtraData);

                // Check if the extra data is of type 'formatting changes'
                if (pFormattingChanges)
                {
                    // Get the item set that holds all the changes properties
                    const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
                    if (pChangesSet)
                    {
                        m_pSerializer->mark(Tag_Redline_2);

                        m_pSerializer->startElementNS( XML_w, XML_pPr, FSEND );

                        // The 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' are used to hold information
                        // that should be collected by different properties in the core, and are all flushed together
                        // to the DOCX when the function 'WriteCollectedParagraphProperties' gets called.
                        // So we need to store the current status of these lists, so that we can revert back to them when
                        // we are done exporting the redline attributes.
                        rtl::Reference<sax_fastparser::FastAttributeList> pFlyAttrList_Original(m_rExport.SdrExporter().getFlyAttrList());
                        m_rExport.SdrExporter().getFlyAttrList().clear();
                        rtl::Reference<sax_fastparser::FastAttributeList> pParagraphSpacingAttrList_Original(m_pParagraphSpacingAttrList);
                        m_pParagraphSpacingAttrList.clear();

                        // Output the redline item set
                        m_rExport.OutputItemSet( *pChangesSet, true, false, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );

                        // Write the collected paragraph properties that are stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
                        WriteCollectedParagraphProperties();

                        // Revert back the original values that were stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
                        m_rExport.SdrExporter().getFlyAttrList() = pFlyAttrList_Original;
                        m_pParagraphSpacingAttrList = pParagraphSpacingAttrList_Original;

                        m_pSerializer->endElementNS( XML_w, XML_pPr );

                        m_pSerializer->mergeTopMarks(Tag_Redline_2, sax_fastparser::MergeMarks::PREPEND);
                    }
                }
            }
            m_pSerializer->endElementNS( XML_w, XML_pPrChange );
            break;

        default:
            SAL_WARN("sw.ww8", "Unhandled redline type for export " << pRedlineData->GetType());
            break;
        }
    }

    // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
    // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
    // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
    void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData )
    {
        if ( !pRedlineData )
            return;

        // FIXME check if it's necessary to travel over the Next()'s in pRedlineData

        OString aId( OString::number( m_nRedlineId++ ) );

        const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
        OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) );

        OString aDate( DateTimeToOString( pRedlineData->GetTimeStamp() ) );

        switch ( pRedlineData->GetType() )
        {
            case nsRedlineType_t::REDLINE_INSERT:
                m_pSerializer->startElementNS( XML_w, XML_ins,
                        FSNS( XML_w, XML_id ), aId.getStr(),
                        FSNS( XML_w, XML_author ), aAuthor.getStr(),
                        FSNS( XML_w, XML_date ), aDate.getStr(),
                        FSEND );
                break;

            case nsRedlineType_t::REDLINE_DELETE:
                m_pSerializer->startElementNS( XML_w, XML_del,
                        FSNS( XML_w, XML_id ), aId.getStr(),
                        FSNS( XML_w, XML_author ), aAuthor.getStr(),
                        FSNS( XML_w, XML_date ), aDate.getStr(),
                        FSEND );
                break;

            case nsRedlineType_t::REDLINE_FORMAT:
                SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRedline()" );
                break;
            default:
                break;
        }
    }

    void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData )
    {
        if ( !pRedlineData )
            return;

        switch ( pRedlineData->GetType() )
        {
            case nsRedlineType_t::REDLINE_INSERT:
                m_pSerializer->endElementNS( XML_w, XML_ins );
                break;

            case nsRedlineType_t::REDLINE_DELETE:
                m_pSerializer->endElementNS( XML_w, XML_del );
                break;

            case nsRedlineType_t::REDLINE_FORMAT:
                SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRedline()" );
                break;
            default:
                break;
        }
    }

    void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t )
    {
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" );
    }

    void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle )
    {
        OString aStyleId(m_rExport.m_pStyles->GetStyleId(nStyle));

        m_pSerializer->singleElementNS( XML_w, XML_pStyle, FSNS( XML_w, XML_val ), aStyleId.getStr(), FSEND );
    }

    static void impl_borderLine( FSHelperPtr const & pSerializer, sal_Int32 elementToken, const SvxBorderLine* pBorderLine, sal_uInt16 nDist,
                                 bool bWriteShadow, const table::BorderLine2* rStyleProps = nullptr )
    {
        // Compute val attribute value
        // Can be one of:
        //      single, double,
        //      basicWideOutline, basicWideInline
        // OOXml also supports those types of borders, but we'll try to play with the first ones.
        //      thickThinMediumGap, thickThinLargeGap, thickThinSmallGap
        //      thinThickLargeGap, thinThickMediumGap, thinThickSmallGap
        const char* pVal = "nil";
        if ( pBorderLine && !pBorderLine->isEmpty( ) )
        {
            switch (pBorderLine->GetBorderLineStyle())
            {
                case SvxBorderLineStyle::SOLID:
                    pVal = "single";
                    break;
                case SvxBorderLineStyle::DOTTED:
                    pVal = "dotted";
                    break;
                case SvxBorderLineStyle::DASHED:
                    pVal = "dashed";
                    break;
                case SvxBorderLineStyle::DOUBLE:
                    pVal = "double";
                    break;
                case SvxBorderLineStyle::THINTHICK_SMALLGAP:
                    pVal = "thinThickSmallGap";
                    break;
                case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
                    pVal = "thinThickMediumGap";
                    break;
                case SvxBorderLineStyle::THINTHICK_LARGEGAP:
                    pVal = "thinThickLargeGap";
                    break;
                case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
                    pVal = "thickThinSmallGap";
                    break;
                case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
                    pVal = "thickThinMediumGap";
                    break;
                case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
                    pVal = "thickThinLargeGap";
                    break;
                case SvxBorderLineStyle::EMBOSSED:
                    pVal = "threeDEmboss";
                    break;
                case SvxBorderLineStyle::ENGRAVED:
                    pVal = "threeDEngrave";
                    break;
                case SvxBorderLineStyle::OUTSET:
                    pVal = "outset";
                    break;
                case SvxBorderLineStyle::INSET:
                    pVal = "inset";
                    break;
                case SvxBorderLineStyle::FINE_DASHED:
                    pVal = "dashSmallGap";
                    break;
                case SvxBorderLineStyle::NONE:
                default:
                    break;
            }
        }
        else if ( !rStyleProps || !rStyleProps->LineWidth )
            // no line, and no line set by the style either:
            // there is no need to write the property
            return;

        // compare the properties with the theme properties before writing them:
        // if they are equal, it means that they were style-defined and there is
        // no need to write them.
        if( rStyleProps != nullptr && pBorderLine && !pBorderLine->isEmpty() &&
                pBorderLine->GetBorderLineStyle() == static_cast<SvxBorderLineStyle>(rStyleProps->LineStyle) &&
                pBorderLine->GetColor() == Color(rStyleProps->Color) &&
                pBorderLine->GetWidth() == convertMm100ToTwip( rStyleProps->LineWidth ) )
            return;

        FastAttributeList* pAttr = FastSerializerHelper::createAttrList();
        pAttr->add( FSNS( XML_w, XML_val ), OString( pVal ) );

        if ( pBorderLine && !pBorderLine->isEmpty() )
        {
            // Compute the sz attribute

            double const fConverted( ::editeng::ConvertBorderWidthToWord(
                    pBorderLine->GetBorderLineStyle(), pBorderLine->GetWidth()));
            // The unit is the 8th of point
            sal_Int32 nWidth = sal_Int32( fConverted / 2.5 );
            const sal_Int32 nMinWidth = 2;
            const sal_Int32 nMaxWidth = 96;

            if ( nWidth > nMaxWidth )
                nWidth = nMaxWidth;
            else if ( nWidth < nMinWidth )
                nWidth = nMinWidth;

            pAttr->add( FSNS( XML_w, XML_sz ), OString::number( nWidth ) );

            // Get the distance (in pt)
            pAttr->add( FSNS( XML_w, XML_space ), OString::number( nDist / 20 ) );

            // Get the color code as an RRGGBB hex value
            OString sColor( msfilter::util::ConvertColor( pBorderLine->GetColor( ) ) );
            pAttr->add( FSNS( XML_w, XML_color ), sColor );
        }

        if (bWriteShadow)
        {
            // Set the shadow value
            pAttr->add( FSNS( XML_w, XML_shadow ), "1" );
        }

        XFastAttributeListRef xAttrs( pAttr );
        pSerializer->singleElementNS( XML_w, elementToken, xAttrs );
    }

    static OutputBorderOptions lcl_getTableDefaultBorderOptions(bool bEcma)
    {
        OutputBorderOptions rOptions;

        rOptions.tag = XML_tblBorders;
        rOptions.bUseStartEnd = !bEcma;
        rOptions.bWriteTag = true;
        rOptions.bWriteInsideHV = true;
        rOptions.bWriteDistance = false;

        return rOptions;
    }

    static OutputBorderOptions lcl_getTableCellBorderOptions(bool bEcma)
    {
        OutputBorderOptions rOptions;

        rOptions.tag = XML_tcBorders;
        rOptions.bUseStartEnd = !bEcma;
        rOptions.bWriteTag = true;
        rOptions.bWriteInsideHV = false;
        rOptions.bWriteDistance = false;

        return rOptions;
    }

    static OutputBorderOptions lcl_getBoxBorderOptions()
    {
        OutputBorderOptions rOptions;

        rOptions.tag = XML_pBdr;
        rOptions.bUseStartEnd = false;
        rOptions.bWriteTag = false;
        rOptions.bWriteInsideHV = false;
        rOptions.bWriteDistance = true;

        return rOptions;
    }

    static void impl_borders( FSHelperPtr const & pSerializer,
                              const SvxBoxItem& rBox,
                              const OutputBorderOptions& rOptions,
                              std::map<SvxBoxItemLine,
                              css::table::BorderLine2> &rTableStyleConf )
    {
        static const SvxBoxItemLine aBorders[] =
        {
            SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
        };

        const sal_Int32 aXmlElements[] =
        {
            XML_top,
            rOptions.bUseStartEnd ? XML_start : XML_left,
            XML_bottom,
            rOptions.bUseStartEnd ? XML_end : XML_right
        };
        bool tagWritten = false;
        const SvxBoxItemLine* pBrd = aBorders;

        bool bWriteInsideH = false;
        bool bWriteInsideV = false;
        for( int i = 0; i < 4; ++i, ++pBrd )
        {
            const SvxBorderLine* pLn = rBox.GetLine( *pBrd );
            const table::BorderLine2 *aStyleProps = nullptr;
            if( rTableStyleConf.find( *pBrd ) != rTableStyleConf.end() )
                aStyleProps = &rTableStyleConf[ *pBrd ];

            if (!tagWritten && rOptions.bWriteTag)
            {
                pSerializer->startElementNS( XML_w, rOptions.tag, FSEND );
                tagWritten = true;
            }

            bool bWriteShadow = false;
            if (rOptions.aShadowLocation == SvxShadowLocation::NONE)
            {
                // The border has no shadow
            }
            else if (rOptions.aShadowLocation == SvxShadowLocation::BottomRight)
            {
                // Special case of 'Bottom-Right' shadow:
                // If the shadow location is 'Bottom-Right' - then turn on the shadow
                // for ALL the sides. This is because in Word - if you select a shadow
                // for a border - it turn on the shadow for ALL the sides (but shows only
                // the bottom-right one).
                // This is so that no information will be lost if passed through LibreOffice
                bWriteShadow = true;
            }
            else
            {
                // If there is a shadow, and it's not the regular 'Bottom-Right',
                // then write only the 'shadowed' sides of the border
                if  (
                        ( ( rOptions.aShadowLocation == SvxShadowLocation::TopLeft     || rOptions.aShadowLocation == SvxShadowLocation::TopRight      )    &&  *pBrd == SvxBoxItemLine::TOP   )  ||
                        ( ( rOptions.aShadowLocation == SvxShadowLocation::TopLeft     || rOptions.aShadowLocation == SvxShadowLocation::BottomLeft    )    &&  *pBrd == SvxBoxItemLine::LEFT  )  ||
                        ( ( rOptions.aShadowLocation == SvxShadowLocation::BottomLeft  || rOptions.aShadowLocation == SvxShadowLocation::BottomRight   )    &&  *pBrd == SvxBoxItemLine::BOTTOM)  ||
                        ( ( rOptions.aShadowLocation == SvxShadowLocation::TopRight    || rOptions.aShadowLocation == SvxShadowLocation::BottomRight   )    &&  *pBrd == SvxBoxItemLine::RIGHT )
                    )
                {
                    bWriteShadow = true;
                }
            }

            sal_uInt16 nDist = 0;
            if (rOptions.bWriteDistance)
            {
                if (rOptions.pDistances)
                {
                    if ( *pBrd == SvxBoxItemLine::TOP)
                        nDist = rOptions.pDistances->nTop;
                    else if ( *pBrd == SvxBoxItemLine::LEFT)
                        nDist = rOptions.pDistances->nLeft;
                    else if ( *pBrd == SvxBoxItemLine::BOTTOM)
                        nDist = rOptions.pDistances->nBottom;
                    else if ( *pBrd == SvxBoxItemLine::RIGHT)
                        nDist = rOptions.pDistances->nRight;
                }
                else
                {
                    nDist = rBox.GetDistance(*pBrd);
                }
            }

            impl_borderLine( pSerializer, aXmlElements[i], pLn, nDist, bWriteShadow, aStyleProps );

            // When exporting default borders, we need to export these 2 attr
            if ( rOptions.bWriteInsideHV) {
                if ( i == 2 )
                    bWriteInsideH = true;
                else if ( i == 3 )
                    bWriteInsideV = true;
            }
        }
        if (bWriteInsideH)
        {
            const table::BorderLine2 *aStyleProps = nullptr;
            if( rTableStyleConf.find( SvxBoxItemLine::BOTTOM ) != rTableStyleConf.end() )
                aStyleProps = &rTableStyleConf[ SvxBoxItemLine::BOTTOM ];
            impl_borderLine( pSerializer, XML_insideH, rBox.GetLine(SvxBoxItemLine::BOTTOM), 0, false, aStyleProps );
        }
        if (bWriteInsideV)
        {
            const table::BorderLine2 *aStyleProps = nullptr;
            if( rTableStyleConf.find( SvxBoxItemLine::RIGHT ) != rTableStyleConf.end() )
                aStyleProps = &rTableStyleConf[ SvxBoxItemLine::RIGHT ];
            impl_borderLine( pSerializer, XML_insideV, rBox.GetLine(SvxBoxItemLine::RIGHT), 0, false, aStyleProps );
        }
        if (tagWritten && rOptions.bWriteTag) {
            pSerializer->endElementNS( XML_w, rOptions.tag );
        }
    }

    static void impl_cellMargins( FSHelperPtr const & pSerializer, const SvxBoxItem& rBox, sal_Int32 tag, bool bUseStartEnd, const SvxBoxItem* pDefaultMargins = nullptr)
    {
        static const SvxBoxItemLine aBorders[] =
        {
            SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
        };

        const sal_Int32 aXmlElements[] =
        {
            XML_top,
            bUseStartEnd ? XML_start : XML_left,
            XML_bottom,
            bUseStartEnd ? XML_end : XML_right
        };
        bool tagWritten = false;
        const SvxBoxItemLine* pBrd = aBorders;
        for( int i = 0; i < 4; ++i, ++pBrd )
        {
            sal_Int32 nDist = sal_Int32( rBox.GetDistance( *pBrd ) );

            if (pDefaultMargins)
            {
                // Skip output if cell margin == table default margin
                if (sal_Int32( pDefaultMargins->GetDistance( *pBrd ) ) == nDist)
                    continue;
            }

            if ( aBorders[i] == SvxBoxItemLine::LEFT ) {
                // Office's cell margin is measured from the right of the border.
                // While LO's cell spacing is measured from the center of the border.
                // So we add half left-border width to tblIndent value
                const SvxBorderLine* pLn = rBox.GetLine( *pBrd );
                if (pLn)
                    nDist -= pLn->GetWidth() * 0.5;
            }

            if (!tagWritten) {
                pSerializer->startElementNS( XML_w, tag, FSEND );
                tagWritten = true;
            }
            pSerializer->singleElementNS( XML_w, aXmlElements[i],
                   FSNS( XML_w, XML_w ), OString::number( nDist ).getStr( ),
                   FSNS( XML_w, XML_type ), "dxa",
                   FSEND );
        }
        if (tagWritten) {
            pSerializer->endElementNS( XML_w, tag );
        }
    }

    void DocxAttributeOutput::TableCellProperties( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
    {
        m_pSerializer->startElementNS( XML_w, XML_tcPr, FSEND );

        const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );

        bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;

        // Output any table cell redlines if there are any attached to this specific cell
        TableCellRedline( pTableTextNodeInfoInner );

        // Cell preferred width
        SwTwips nWidth = GetGridCols( pTableTextNodeInfoInner )->at( nCell );
        if ( nCell )
            nWidth = nWidth - GetGridCols( pTableTextNodeInfoInner )->at( nCell - 1 );
        m_pSerializer->singleElementNS( XML_w, XML_tcW,
               FSNS( XML_w, XML_w ), OString::number( nWidth ).getStr( ),
               FSNS( XML_w, XML_type ), "dxa",
               FSEND );

        // Horizontal spans
        const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
        SwWriteTableRow *pRow = rRows[ nRow ].get();
        const SwWriteTableCells& rTableCells =  pRow->GetCells();
        if (nCell < rTableCells.size() )
        {
            const SwWriteTableCell& rCell = *rTableCells[nCell];
            const sal_uInt16 nColSpan = rCell.GetColSpan();
            if ( nColSpan > 1 )
                m_pSerializer->singleElementNS( XML_w, XML_gridSpan,
                        FSNS( XML_w, XML_val ), OString::number( nColSpan ).getStr(),
                        FSEND );
        }

        // Vertical merges
        ww8::RowSpansPtr xRowSpans = pTableTextNodeInfoInner->getRowSpansOfRow();
        sal_Int32 vSpan = (*xRowSpans)[nCell];
        if ( vSpan > 1 )
        {
            m_pSerializer->singleElementNS( XML_w, XML_vMerge,
                    FSNS( XML_w, XML_val ), "restart",
                    FSEND );
        }
        else if ( vSpan < 0 )
        {
            m_pSerializer->singleElementNS( XML_w, XML_vMerge,
                    FSNS( XML_w, XML_val ), "continue",
                    FSEND );
        }

        if (const SfxGrabBagItem* pItem = pTableBox->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
        {
            const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
            std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find("CellCnfStyle");
            if (it != rGrabBag.end())
            {
                uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
                m_pTableStyleExport->CnfStyle(aAttributes);
            }
        }

        const SvxBoxItem& rBox = pTableBox->GetFrameFormat( )->GetBox( );
        const SvxBoxItem& rDefaultBox = (*tableFirstCells.rbegin())->getTableBox( )->GetFrameFormat( )->GetBox( );
        {
            // The cell borders
            impl_borders( m_pSerializer, rBox, lcl_getTableCellBorderOptions(bEcma), m_aTableStyleConf );
        }

        TableBackgrounds( pTableTextNodeInfoInner );

        {
            // Cell margins
            impl_cellMargins( m_pSerializer, rBox, XML_tcMar, !bEcma, &rDefaultBox );
        }

        TableVerticalCell( pTableTextNodeInfoInner );

        m_pSerializer->endElementNS( XML_w, XML_tcPr );
    }

    void DocxAttributeOutput::InitTableHelper( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
    {
        const SwTable* pTable = pTableTextNodeInfoInner->getTable();
        if (m_xTableWrt && pTable == m_xTableWrt->GetTable())
            return;

        long nPageSize = 0;
        bool bRelBoxSize = false;

        // Create the SwWriteTable instance to use col spans (and maybe other infos)
        GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );

        const SwFrameFormat *pFormat = pTable->GetFrameFormat( );
        const sal_uInt32 nTableSz = static_cast<sal_uInt32>(pFormat->GetFrameSize( ).GetWidth( ));

        const SwHTMLTableLayout *pLayout = pTable->GetHTMLTableLayout();
        if( pLayout && pLayout->IsExportable() )
            m_xTableWrt.reset(new SwWriteTable(pTable, pLayout));
        else
            m_xTableWrt.reset(new SwWriteTable(pTable, pTable->GetTabLines(), nPageSize, nTableSz, false));
    }

    void DocxAttributeOutput::StartTable( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
    {
        // In case any paragraph SDT's are open, close them here.
        EndParaSdtBlock();

        m_pSerializer->startElementNS( XML_w, XML_tbl, FSEND );

        tableFirstCells.push_back(pTableTextNodeInfoInner);
        lastOpenCell.push_back(-1);
        lastClosedCell.push_back(-1);

        InitTableHelper( pTableTextNodeInfoInner );
        TableDefinition( pTableTextNodeInfoInner );
    }

    void DocxAttributeOutput::EndTable()
    {
        m_pSerializer->endElementNS( XML_w, XML_tbl );

        if ( m_tableReference->m_nTableDepth > 0 )
            --m_tableReference->m_nTableDepth;

        lastClosedCell.pop_back();
        lastOpenCell.pop_back();
        tableFirstCells.pop_back();

        // We closed the table; if it is a nested table, the cell that contains it
        // still continues
        // set to true only if we were in a nested table, not otherwise.
        if( 0 != tableFirstCells.size() )
            m_tableReference->m_bTableCellOpen = true;

        // Cleans the table helper
        m_xTableWrt.reset(nullptr);

        m_aTableStyleConf.clear();
    }

    void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
    {
        m_pSerializer->startElementNS( XML_w, XML_tr, FSEND );

        // Output the row properties
        m_pSerializer->startElementNS( XML_w, XML_trPr, FSEND );

        // Header row: tblHeader
        const SwTable *pTable = pTableTextNodeInfoInner->getTable( );
        if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) )
            m_pSerializer->singleElementNS( XML_w, XML_tblHeader,
                   FSNS( XML_w, XML_val ), "true",
                   FSEND );

        TableRowRedline( pTableTextNodeInfoInner );
        TableHeight( pTableTextNodeInfoInner );
        TableCanSplit( pTableTextNodeInfoInner );

        const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox();
        const SwTableLine* pTableLine = pTableBox->GetUpper();
        if (const SfxGrabBagItem* pItem = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
        {
            const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
            std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find("RowCnfStyle");
            if (it != rGrabBag.end())
            {
                uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
                m_pTableStyleExport->CnfStyle(aAttributes);
            }
        }

        m_pSerializer->endElementNS( XML_w, XML_trPr );
    }

    void DocxAttributeOutput::EndTableRow( )
    {
        m_pSerializer->endElementNS( XML_w, XML_tr );
        lastOpenCell.back() = -1;
        lastClosedCell.back() = -1;
    }

    void DocxAttributeOutput::StartTableCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
    {
        lastOpenCell.back() = nCell;

        InitTableHelper( pTableTextNodeInfoInner );

        m_pSerializer->startElementNS( XML_w, XML_tc, FSEND );

        // Write the cell properties here
        TableCellProperties( pTableTextNodeInfoInner, nCell, nRow );

        m_tableReference->m_bTableCellOpen = true;
    }

    void DocxAttributeOutput::EndTableCell(sal_uInt32 nCell)
    {
        lastClosedCell.back() = nCell;
        lastOpenCell.back() = -1;

        if (m_tableReference->m_bTableCellParaSdtOpen)
            EndParaSdtBlock();

        m_pSerializer->endElementNS( XML_w, XML_tc );

        m_bBtLr = false;
        m_tableReference->m_bTableCellOpen = false;
        m_tableReference->m_bTableCellParaSdtOpen = false;
    }

    void DocxAttributeOutput::TableInfoCell( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
    {
    }

    void DocxAttributeOutput::TableInfoRow( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfo*/ )
    {
    }

    namespace
    {

    /// Does the same as comphelper::string::padToLength(), but extends the start, not the end.
    OString lcl_padStartToLength(OString const & aString, sal_Int32 nLen, sal_Char cFill)
    {
        if (nLen > aString.getLength())
        {
            sal_Int32 nDiff = nLen - aString.getLength();
            OStringBuffer aBuffer;
            comphelper::string::padToLength(aBuffer, nDiff, cFill);
            aBuffer.append(aString);
            return aBuffer.makeStringAndClear();
        }
        else
            return aString;
    }

    sal_Int32 lcl_getWordCompatibilityMode( const SwDoc& rDoc )
    {
        uno::Reference< beans::XPropertySet >     xPropSet( rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
        uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();

        if ( xPropSetInfo->hasPropertyByName( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) )
        {
            uno::Sequence< beans::PropertyValue > propList;
            xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= propList;

            for ( sal_Int32 i = 0; i < propList.getLength(); ++i )
            {
                if ( propList[i].Name == "CompatSettings" )
                {
                    css::uno::Sequence< css::beans::PropertyValue > aCurrentCompatSettings;
                    propList[i].Value >>= aCurrentCompatSettings;

                    for ( sal_Int32 j = 0; j < aCurrentCompatSettings.getLength(); ++j )
                    {
                        uno::Sequence< beans::PropertyValue > aCompatSetting;
                        aCurrentCompatSettings[j].Value >>= aCompatSetting;

                        OUString sName;
                        OUString sUri;
                        OUString sVal;

                        for ( sal_Int32 k = 0; k < aCompatSetting.getLength(); ++k )
                        {
                            if ( aCompatSetting[k].Name == "name" ) aCompatSetting[k].Value >>= sName;
                            if ( aCompatSetting[k].Name == "uri" )  aCompatSetting[k].Value >>= sUri;
                            if ( aCompatSetting[k].Name == "val" )  aCompatSetting[k].Value >>= sVal;
                        }

                        if ( sName == "compatibilityMode" && sUri == "http://schemas.microsoft.com/office/word" )
                        {
                            return sVal.toInt32();
                        }
                    }
                }
            }
        }

        return -1; // Word compatibility mode not found
    }

    }

    void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;

        // Write the table properties
        m_pSerializer->startElementNS( XML_w, XML_tblPr, FSEND );

        static const sal_Int32 aOrder[] =
        {
            FSNS( XML_w, XML_tblStyle ),
            FSNS( XML_w, XML_tblpPr ),
            FSNS( XML_w, XML_tblOverlap ),
            FSNS( XML_w, XML_bidiVisual ),
            FSNS( XML_w, XML_tblStyleRowBandSize ),
            FSNS( XML_w, XML_tblStyleColBandSize ),
            FSNS( XML_w, XML_tblW ),
            FSNS( XML_w, XML_jc ),
            FSNS( XML_w, XML_tblCellSpacing ),
            FSNS( XML_w, XML_tblInd ),
            FSNS( XML_w, XML_tblBorders ),
            FSNS( XML_w, XML_shd ),
            FSNS( XML_w, XML_tblLayout ),
            FSNS( XML_w, XML_tblCellMar ),
            FSNS( XML_w, XML_tblLook ),
            FSNS( XML_w, XML_tblPrChange )
        };

        // postpone the output so that we can later []
        // prepend the properties before the run
        m_pSerializer->mark(Tag_TableDefinition, comphelper::containerToSequence(aOrder));

        long nPageSize = 0;
        const char* widthType = "dxa";

        // If actual width of table is relative it should export is as "pct".`
        const SwTable *pTable = pTableTextNodeInfoInner->getTable();
        SwFrameFormat *pTableFormat = pTable->GetFrameFormat( );
        const SwFormatFrameSize &rSize = pTableFormat->GetFrameSize();
        int nWidthPercent = rSize.GetWidthPercent();
        uno::Reference<beans::XPropertySet> xPropertySet(SwXTextTables::GetObject(*pTable->GetFrameFormat( )),uno::UNO_QUERY);
        bool isWidthRelative = false;
        xPropertySet->getPropertyValue("IsWidthRelative") >>= isWidthRelative;

        if(isWidthRelative)
        {
           /**
           * As per ECMA Specification : ECMA-376, Second Edition, Part 1 - Fundamentals And Markup Language Reference [ 17.18.90 ST_TableWidth (Table Width Units)]
           * http://www.schemacentral.com/sc/ooxml/a-w_type-7.html
           *
           * Fiftieths of a Percent :
           * http://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/
           * pct Width is in Fiftieths of a Percent
           *
           * ex. If the Table width is 50% then
           * Width in Fiftieths of a percent is (50 * 50) % or 0.5 * 5000 = 2500pct
           **/
            nPageSize = nWidthPercent * 50 ;
            widthType = "pct" ;
        }
        else
        {
            bool bRelBoxSize = false;
            // Create the SwWriteTable instance to use col spans (and maybe other infos)
            GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );
            if(nPageSize == 0)
                widthType = "auto";
        }

        // Output the table preferred width
        m_pSerializer->singleElementNS( XML_w, XML_tblW,
                FSNS( XML_w, XML_w ), OString::number( nPageSize ).getStr( ),
                FSNS( XML_w, XML_type ), widthType,
                FSEND );

        // Look for the table style property in the table grab bag
        std::map<OUString, css::uno::Any> aGrabBag =
                pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)->GetGrabBag();

        // We should clear the TableStyle map. In case of Table inside multiple tables it contains the
        // table border style of the previous table.
        if (! m_aTableStyleConf.empty())
            m_aTableStyleConf.clear();

        // Extract properties from grab bag
        for( const auto & rGrabBagElement : aGrabBag )
        {
            if( rGrabBagElement.first == "TableStyleName")
            {
                OString sStyleName = OUStringToOString( rGrabBagElement.second.get<OUString>(), RTL_TEXTENCODING_UTF8 );
                m_pSerializer->singleElementNS( XML_w, XML_tblStyle,
                        FSNS( XML_w, XML_val ), sStyleName.getStr(),
                        FSEND );
            }
            else if( rGrabBagElement.first == "TableStyleTopBorder" )
                m_aTableStyleConf[ SvxBoxItemLine::TOP ] = rGrabBagElement.second.get<table::BorderLine2>();
            else if( rGrabBagElement.first == "TableStyleBottomBorder" )
                m_aTableStyleConf[ SvxBoxItemLine::BOTTOM ] = rGrabBagElement.second.get<table::BorderLine2>();
            else if( rGrabBagElement.first == "TableStyleLeftBorder" )
                m_aTableStyleConf[ SvxBoxItemLine::LEFT ] = rGrabBagElement.second.get<table::BorderLine2>();
            else if( rGrabBagElement.first == "TableStyleRightBorder" )
                m_aTableStyleConf[ SvxBoxItemLine::RIGHT ] = rGrabBagElement.second.get<table::BorderLine2>();
            else if (rGrabBagElement.first == "TableStyleLook")
            {
                FastAttributeList* pAttributeList = FastSerializerHelper::createAttrList();
                uno::Sequence<beans::PropertyValue> aAttributeList = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();

                for (sal_Int32 i = 0; i < aAttributeList.getLength(); ++i)
                {
                    if (aAttributeList[i].Name == "val")
                        pAttributeList->add(FSNS(XML_w, XML_val), lcl_padStartToLength(OString::number(aAttributeList[i].Value.get<sal_Int32>(), 16), 4, '0'));
                    else
                    {
                        static DocxStringTokenMap const aTokens[] =
                        {
                            {"firstRow", XML_firstRow},
                            {"lastRow", XML_lastRow},
                            {"firstColumn", XML_firstColumn},
                            {"lastColumn", XML_lastColumn},
                            {"noHBand", XML_noHBand},
                            {"noVBand", XML_noVBand},
                            {nullptr, 0}
                        };

                        if (sal_Int32 nToken = DocxStringGetToken(aTokens, aAttributeList[i].Name))
                            pAttributeList->add(FSNS(XML_w, nToken), (aAttributeList[i].Value.get<sal_Int32>() ? "1" : "0"));
                    }
                }

                XFastAttributeListRef xAttributeList(pAttributeList);
                m_pSerializer->singleElementNS(XML_w, XML_tblLook, xAttributeList);
            }
            else if (rGrabBagElement.first == "TablePosition" )
            {
                FastAttributeList *attrListTablePos = FastSerializerHelper::createAttrList( );
                uno::Sequence<beans::PropertyValue> aTablePosition = rGrabBagElement.second.get<uno::Sequence<beans::PropertyValue> >();
                for (sal_Int32 i = 0; i < aTablePosition.getLength(); ++i)
                {
                    if (aTablePosition[i].Name == "vertAnchor" && !aTablePosition[i].Value.get<OUString>().isEmpty())
                    {
                        OString strTemp = OUStringToOString(aTablePosition[i].Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
                        attrListTablePos->add( FSNS( XML_w, XML_vertAnchor ), strTemp.getStr() );
                    }
                    else if (aTablePosition[i].Name == "tblpYSpec" && !aTablePosition[i].Value.get<OUString>().isEmpty())
                    {
                        OString strTemp = OUStringToOString(aTablePosition[i].Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
                        attrListTablePos->add( FSNS( XML_w, XML_tblpYSpec ), strTemp.getStr() );
                    }
                    else if (aTablePosition[i].Name == "horzAnchor" && !aTablePosition[i].Value.get<OUString>().isEmpty())
                    {
                        OString strTemp = OUStringToOString(aTablePosition[i].Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
                        attrListTablePos->add( FSNS( XML_w, XML_horzAnchor ), strTemp.getStr() );
                    }
                    else if (aTablePosition[i].Name == "tblpXSpec" && !aTablePosition[i].Value.get<OUString>().isEmpty())
                    {
                        OString strTemp = OUStringToOString(aTablePosition[i].Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
                        attrListTablePos->add( FSNS( XML_w, XML_tblpXSpec ), strTemp.getStr() );
                    }
                    else if (aTablePosition[i].Name == "bottomFromText")
                    {
                        attrListTablePos->add( FSNS( XML_w, XML_bottomFromText ), OString::number( aTablePosition[i].Value.get<sal_Int32>() ) );
                    }
                    else if (aTablePosition[i].Name == "leftFromText")
                    {
                        attrListTablePos->add( FSNS( XML_w, XML_leftFromText ), OString::number( aTablePosition[i].Value.get<sal_Int32>() ) );
                    }
                    else if (aTablePosition[i].Name == "rightFromText")
                    {
                        attrListTablePos->add( FSNS( XML_w, XML_rightFromText ), OString::number( aTablePosition[i].Value.get<sal_Int32>() ) );
                    }
                    else if (aTablePosition[i].Name == "topFromText")
                    {
                        attrListTablePos->add( FSNS( XML_w, XML_topFromText ), OString::number( aTablePosition[i].Value.get<sal_Int32>() ) );
                    }
                    else if (aTablePosition[i].Name == "tblpX")
                    {
                        attrListTablePos->add( FSNS( XML_w, XML_tblpX ), OString::number( aTablePosition[i].Value.get<sal_Int32>() ) );
                    }
                    else if (aTablePosition[i].Name == "tblpY")
                    {
                        attrListTablePos->add( FSNS( XML_w, XML_tblpY ), OString::number( aTablePosition[i].Value.get<sal_Int32>() ) );
                    }
                }

                XFastAttributeListRef xAttrListTablePosRef( attrListTablePos );

                m_pSerializer->singleElementNS( XML_w, XML_tblpPr, xAttrListTablePosRef);
                attrListTablePos = nullptr;
            }
            else
                SAL_WARN("sw.ww8", "DocxAttributeOutput::TableDefinition: unhandled property: " << rGrabBagElement.first);
        }

        // Output the table alignment
        const char* pJcVal;
        sal_Int32 nIndent = 0;
        switch ( pTableFormat->GetHoriOrient( ).GetHoriOrient( ) )
        {
            case text::HoriOrientation::CENTER:
                pJcVal = "center";
                break;
            case text::HoriOrientation::RIGHT:
                if ( bEcma )
                    pJcVal = "right";
                else
                    pJcVal = "end";
                break;
            default:
            case text::HoriOrientation::NONE:
            case text::HoriOrientation::LEFT_AND_WIDTH:
            {
                if ( bEcma )
                    pJcVal = "left";
                else
                    pJcVal = "start";
                nIndent = sal_Int32( pTableFormat->GetLRSpace().GetLeft() );

                // Table indentation has different meaning in Word, depending if the table is nested or not.
                // If nested, tblInd is added to parent table's left spacing and defines left edge position
                // If not nested, text position of left-most cell must be at absolute X = tblInd
                // so, table_spacing + table_spacing_to_content = tblInd

                // tdf#106742: since MS Word 2013 (compatibilityMode >= 15), top-level tables are handled the same as nested tables;
                // this is also the default behavior in LO when DOCX doesn't define "compatibilityMode" option
                sal_Int32 nMode = lcl_getWordCompatibilityMode( *m_rExport.m_pDoc );

                if ( nMode > 0 && nMode <= 14 && m_tableReference->m_nTableDepth == 0 )
                {
                    const SwTableBox*    pTabBox = pTableTextNodeInfoInner->getTableBox();
                    const SwFrameFormat* pFrameFormat = pTabBox->GetFrameFormat();
                    nIndent += sal_Int32( pFrameFormat->GetBox().GetDistance( SvxBoxItemLine::LEFT ) );
                }

                break;
            }
        }
        m_pSerializer->singleElementNS( XML_w, XML_jc,
                FSNS( XML_w, XML_val ), pJcVal,
                FSEND );

        // Output the table background color (although cell value still needs to be specified)
        const SvxBrushItem *pColorProp = pTableFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
        Color aColor = pColorProp ? pColorProp->GetColor() : COL_AUTO;
        if ( aColor != COL_AUTO )
        {
            OString sColor = msfilter::util::ConvertColor( aColor );
            m_pSerializer->singleElementNS( XML_w, XML_shd,
                    FSNS( XML_w, XML_fill ), sColor.getStr( ),
                    FSNS( XML_w, XML_val ), "clear",
                    FSEND );
        }

        // Output the table borders
        TableDefaultBorders( pTableTextNodeInfoInner );

        // Output the default cell margins
        TableDefaultCellMargins( pTableTextNodeInfoInner );

        TableBidi( pTableTextNodeInfoInner );

        // Table indent (need to get written even if == 0)
        m_pSerializer->singleElementNS( XML_w, XML_tblInd,
                FSNS( XML_w, XML_w ), OString::number( nIndent ).getStr( ),
                FSNS( XML_w, XML_type ), "dxa",
                FSEND );

        // Merge the marks for the ordered elements
        m_pSerializer->mergeTopMarks(Tag_TableDefinition);

        m_pSerializer->endElementNS( XML_w, XML_tblPr );

        // Write the table grid infos
        m_pSerializer->startElementNS( XML_w, XML_tblGrid, FSEND );
        sal_Int32 nPrv = 0;
        ww8::WidthsPtr pColumnWidths = GetColumnWidths( pTableTextNodeInfoInner );
        for ( auto aColumnWidth : *pColumnWidths )
        {
            sal_Int32 nWidth  =  sal_Int32( aColumnWidth ) - nPrv;
            m_pSerializer->singleElementNS( XML_w, XML_gridCol,
                   FSNS( XML_w, XML_w ), OString::number( nWidth ).getStr( ),
                   FSEND );
            nPrv = sal_Int32( aColumnWidth );
        }

        m_pSerializer->endElementNS( XML_w, XML_tblGrid );
    }

    void DocxAttributeOutput::TableDefaultBorders( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
        const SwFrameFormat * pFrameFormat = pTabBox->GetFrameFormat();

        bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;

        // Don't write table defaults based on the top-left cell if we have a table style available.
        if (m_aTableStyleConf.empty())
        {
            // the defaults of the table are taken from the top-left cell
            impl_borders(m_pSerializer, pFrameFormat->GetBox(), lcl_getTableDefaultBorderOptions(bEcma), m_aTableStyleConf);
        }
    }

    void DocxAttributeOutput::TableDefaultCellMargins( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
        const SwFrameFormat * pFrameFormat = pTabBox->GetFrameFormat();
        const SvxBoxItem& rBox = pFrameFormat->GetBox( );
        const bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;

        impl_cellMargins(m_pSerializer, rBox, XML_tblCellMar, !bEcma);
    }

    void DocxAttributeOutput::TableBackgrounds( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTable *pTable = pTableTextNodeInfoInner->getTable();
        const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );
        const SwTableLine *pTableRow = pTableBox->GetUpper();
        const SwFrameFormat *pFormat = pTableBox->GetFrameFormat( );

        const SvxBrushItem *pColorProp = pFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
        Color aColor = pColorProp ? pColorProp->GetColor() : COL_AUTO;

        const SwFrameFormat *pRowFormat = pTableRow->GetFrameFormat( );
        const SvxBrushItem *pRowColorProp = pRowFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
        if ( pRowColorProp && aColor == COL_AUTO)
            aColor = pRowColorProp->GetColor();

        const SwFrameFormat *pTableFormat = pTable->GetFrameFormat( );
        const SvxBrushItem *pTableColorProp = pTableFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
        if ( pTableColorProp && aColor == COL_AUTO )
            aColor = pTableColorProp->GetColor();

        const OString sColor = msfilter::util::ConvertColor( aColor );

        std::map<OUString, css::uno::Any> aGrabBag =
                pFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)->GetGrabBag();

        OString sOriginalColor;
        std::map<OUString, css::uno::Any>::iterator aGrabBagElement = aGrabBag.find("originalColor");
        if( aGrabBagElement != aGrabBag.end() )
            sOriginalColor = OUStringToOString( aGrabBagElement->second.get<OUString>(), RTL_TEXTENCODING_UTF8 );

        if ( sOriginalColor != sColor )
        {
            // color changed by the user, or no grab bag: write sColor
            m_pSerializer->singleElementNS( XML_w, XML_shd,
                    FSNS( XML_w, XML_fill ), sColor.getStr( ),
                    FSNS( XML_w, XML_val ), "clear",
                    FSEND );
        }
        else
        {
            rtl::Reference<sax_fastparser::FastAttributeList> pAttrList;

            for( const auto & rGrabBagElement : aGrabBag )
            {
                if (!rGrabBagElement.second.has<OUString>())
                    continue;

                OString sValue = OUStringToOString( rGrabBagElement.second.get<OUString>(), RTL_TEXTENCODING_UTF8 );
                if( rGrabBagElement.first == "themeFill")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_themeFill ), sValue.getStr() );
                else if( rGrabBagElement.first == "themeFillTint")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_themeFillTint ), sValue.getStr() );
                else if( rGrabBagElement.first == "themeFillShade")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_themeFillShade ), sValue.getStr() );
                else if( rGrabBagElement.first == "fill" )
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_fill ), sValue.getStr() );
                else if( rGrabBagElement.first == "themeColor")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_themeColor ), sValue.getStr() );
                else if( rGrabBagElement.first == "themeTint")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_themeTint ), sValue.getStr() );
                else if( rGrabBagElement.first == "themeShade")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_themeShade ), sValue.getStr() );
                else if( rGrabBagElement.first == "color")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_color ), sValue.getStr() );
                else if( rGrabBagElement.first == "val")
                    AddToAttrList( pAttrList, FSNS( XML_w, XML_val ), sValue.getStr() );
            }
            m_pSerializer->singleElementNS( XML_w, XML_shd, pAttrList.get() );
        }
    }

    void DocxAttributeOutput::TableRowRedline( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
        const SwTableLine * pTabLine = pTabBox->GetUpper();

        // search next Redline
        const SwExtraRedlineTable& aExtraRedlineTable = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetExtraRedlineTable();
        for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < aExtraRedlineTable.GetSize(); ++nCurRedlinePos )
        {
            SwExtraRedline* pExtraRedline = aExtraRedlineTable.GetRedline(nCurRedlinePos);
            const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline);
            const SwTableLine *pRedTabLine = pTableRowRedline ? &pTableRowRedline->GetTableLine() : nullptr;
            if (pRedTabLine == pTabLine)
            {
                // Redline for this table row
                const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData();
                sal_uInt16 nRedlineType = aRedlineData.GetType();
                switch (nRedlineType)
                {
                    case nsRedlineType_t::REDLINE_TABLE_ROW_INSERT:
                    case nsRedlineType_t::REDLINE_TABLE_ROW_DELETE:
                    {
                        OString aId( OString::number( m_nRedlineId++ ) );
                        const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) );
                        OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) );

                        OString aDate( DateTimeToOString( aRedlineData.GetTimeStamp() ) );

                        if (nRedlineType == nsRedlineType_t::REDLINE_TABLE_ROW_INSERT)
                            m_pSerializer->singleElementNS( XML_w, XML_ins,
                                FSNS( XML_w, XML_id ), aId.getStr(),
                                FSNS( XML_w, XML_author ), aAuthor.getStr(),
                                FSNS( XML_w, XML_date ), aDate.getStr(),
                                FSEND );
                        else if (nRedlineType == nsRedlineType_t::REDLINE_TABLE_ROW_DELETE)
                            m_pSerializer->singleElementNS( XML_w, XML_del,
                                FSNS( XML_w, XML_id ), aId.getStr(),
                                FSNS( XML_w, XML_author ), aAuthor.getStr(),
                                FSNS( XML_w, XML_date ), aDate.getStr(),
                                FSEND );
                    }
                    break;
                }
            }
        }
    }

    void DocxAttributeOutput::TableCellRedline( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();

        // search next Redline
        const SwExtraRedlineTable& aExtraRedlineTable = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetExtraRedlineTable();
        for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < aExtraRedlineTable.GetSize(); ++nCurRedlinePos )
        {
            SwExtraRedline* pExtraRedline = aExtraRedlineTable.GetRedline(nCurRedlinePos);
            const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
            const SwTableBox *pRedTabBox = pTableCellRedline ? &pTableCellRedline->GetTableBox() : nullptr;
            if (pRedTabBox == pTabBox)
            {
                // Redline for this table cell
                const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
                sal_uInt16 nRedlineType = aRedlineData.GetType();
                switch (nRedlineType)
                {
                    case nsRedlineType_t::REDLINE_TABLE_CELL_INSERT:
                    case nsRedlineType_t::REDLINE_TABLE_CELL_DELETE:
                    {
                        OString aId( OString::number( m_nRedlineId++ ) );
                        const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) );
                        OString aAuthor( OUStringToOString( rAuthor, RTL_TEXTENCODING_UTF8 ) );

                        OString aDate( DateTimeToOString( aRedlineData.GetTimeStamp() ) );

                        if (nRedlineType == nsRedlineType_t::REDLINE_TABLE_CELL_INSERT)
                            m_pSerializer->singleElementNS( XML_w, XML_cellIns,
                                FSNS( XML_w, XML_id ), aId.getStr(),
                                FSNS( XML_w, XML_author ), aAuthor.getStr(),
                                FSNS( XML_w, XML_date ), aDate.getStr(),
                                FSEND );
                        else if (nRedlineType == nsRedlineType_t::REDLINE_TABLE_CELL_DELETE)
                            m_pSerializer->singleElementNS( XML_w, XML_cellDel,
                                FSNS( XML_w, XML_id ), aId.getStr(),
                                FSNS( XML_w, XML_author ), aAuthor.getStr(),
                                FSNS( XML_w, XML_date ), aDate.getStr(),
                                FSEND );
                    }
                    break;
                }
            }
        }
    }

    void DocxAttributeOutput::TableHeight( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
        const SwTableLine * pTabLine = pTabBox->GetUpper();
        const SwFrameFormat * pLineFormat = pTabLine->GetFrameFormat();

        const SwFormatFrameSize& rLSz = pLineFormat->GetFrameSize();
        if ( ATT_VAR_SIZE != rLSz.GetHeightSizeType() && rLSz.GetHeight() )
        {
            sal_Int32 nHeight = rLSz.GetHeight();
            const char *pRule = nullptr;

            switch ( rLSz.GetHeightSizeType() )
            {
                case ATT_FIX_SIZE: pRule = "exact"; break;
                case ATT_MIN_SIZE: pRule = "atLeast"; break;
                default:           break;
            }

            if ( pRule )
                m_pSerializer->singleElementNS( XML_w, XML_trHeight,
                        FSNS( XML_w, XML_val ), OString::number( nHeight ).getStr( ),
                        FSNS( XML_w, XML_hRule ), pRule,
                        FSEND );
        }
    }

    void DocxAttributeOutput::TableCanSplit( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
        const SwTableLine * pTabLine = pTabBox->GetUpper();
        const SwFrameFormat * pLineFormat = pTabLine->GetFrameFormat();

        const SwFormatRowSplit& rSplittable = pLineFormat->GetRowSplit( );
        // if rSplittable is true then no need to write <w:cantSplit w:val="false"/>
        // as default row prop is allow row to break across page.
        if( !rSplittable.GetValue( ) )
            m_pSerializer->singleElementNS( XML_w, XML_cantSplit,
                    FSNS( XML_w, XML_val ), "true",
                    FSEND );
    }

    void DocxAttributeOutput::TableBidi( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTable * pTable = pTableTextNodeInfoInner->getTable();
        const SwFrameFormat * pFrameFormat = pTable->GetFrameFormat();

        if ( m_rExport.TrueFrameDirection( *pFrameFormat ) == SvxFrameDirection::Horizontal_RL_TB )
        {
            m_pSerializer->singleElementNS( XML_w, XML_bidiVisual,
                    FSNS( XML_w, XML_val ), "true",
                    FSEND );
        }
    }

    void DocxAttributeOutput::TableVerticalCell( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
    {
        const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
        const SwFrameFormat *pFrameFormat = pTabBox->GetFrameFormat( );

        if ( SvxFrameDirection::Vertical_RL_TB == m_rExport.TrueFrameDirection( *pFrameFormat ) )
            m_pSerializer->singleElementNS( XML_w, XML_textDirection,
                   FSNS( XML_w, XML_val ), "tbRl",
                   FSEND );
        else if ( SvxFrameDirection::Horizontal_LR_TB == m_rExport.TrueFrameDirection( *pFrameFormat ) )
        {
            // Undo the text direction mangling done by the btLr handler in writerfilter::dmapper::DomainMapperTableManager::sprm()
            const SwStartNode* pSttNd = pTabBox->GetSttNd();
            if (pSttNd)
            {
                SwPaM aPam(*pSttNd, 0);
                ++aPam.GetPoint()->nNode;
                if (aPam.GetPoint()->nNode.GetNode().IsTextNode())
                {
                    const SwTextNode& rTextNode = static_cast<const SwTextNode&>(aPam.GetPoint()->nNode.GetNode());
                    if( const SwAttrSet* pAttrSet = rTextNode.GetpSwAttrSet())
                    {
                        const SvxCharRotateItem& rCharRotate = pAttrSet->GetCharRotate();
                        if (rCharRotate.GetValue() == 900)
                        {
                            m_pSerializer->singleElementNS( XML_w, XML_textDirection, FSNS( XML_w, XML_val ), "btLr", FSEND );
                            m_bBtLr = true;
                        }
                    }
                }
            }
        }

        const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
        SwWriteTableRow *pRow = rRows[ pTableTextNodeInfoInner->getRow( ) ].get();
        sal_uInt32 nCell = pTableTextNodeInfoInner->getCell();
        const SwWriteTableCells& rTableCells =  pRow->GetCells();
        if (nCell < rTableCells.size() )
        {
            const SwWriteTableCell *const pCell = pRow->GetCells()[ nCell ].get();
            switch( pCell->GetVertOri())
            {
            case text::VertOrientation::TOP:
                break;
            case text::VertOrientation::CENTER:
                m_pSerializer->singleElementNS( XML_w, XML_vAlign,
                FSNS( XML_w, XML_val ), "center", FSEND );
                break;
            case text::VertOrientation::BOTTOM:
                m_pSerializer->singleElementNS( XML_w, XML_vAlign,
                        FSNS( XML_w, XML_val ), "bottom", FSEND );
                break;
            }
        }
    }

    void DocxAttributeOutput::TableNodeInfoInner( ww8::WW8TableNodeInfoInner::Pointer_t pNodeInfoInner )
    {
        // This is called when the nested table ends in a cell, and there's no
        // paragraph benhind that; so we must check for the ends of cell, rows,
        // tables
        // ['true' to write an empty paragraph, MS Word insists on that]
        FinishTableRowCell( pNodeInfoInner, true );
    }

    void DocxAttributeOutput::TableOrientation( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
    {
        SAL_INFO("sw.ww8", "TODO: DocxAttributeOutput::TableOrientation( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )" );
    }

    void DocxAttributeOutput::TableSpacing( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
    {
        SAL_INFO("sw.ww8", "TODO: DocxAttributeOutput::TableSpacing( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )" );
    }

    void DocxAttributeOutput::TableRowEnd( sal_uInt32 /*nDepth*/ )
    {
        SAL_INFO("sw.ww8", "TODO: DocxAttributeOutput::TableRowEnd( sal_uInt32 nDepth = 1 )" );
    }

    void DocxAttributeOutput::StartStyles()
    {
        m_pSerializer->startElementNS( XML_w, XML_styles,
                FSNS( XML_xmlns, XML_w ),   OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(doc)), RTL_TEXTENCODING_UTF8).getStr(),
                FSNS( XML_xmlns, XML_w14 ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(w14)), RTL_TEXTENCODING_UTF8).getStr(),
                FSNS( XML_xmlns, XML_mc ),  OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(mce)), RTL_TEXTENCODING_UTF8).getStr(),
                FSNS( XML_mc, XML_Ignorable ), "w14",
                FSEND );

        DocDefaults();
        LatentStyles();
    }

    sal_Int32 DocxStringGetToken(DocxStringTokenMap const * pMap, const OUString& rName)
    {
        OString sName = OUStringToOString(rName, RTL_TEXTENCODING_UTF8);
        while (pMap->pToken)
        {
            if (sName == pMap->pToken)
                return pMap->nToken;
            ++pMap;
        }
        return 0;
    }

    namespace
    {

    DocxStringTokenMap const aDefaultTokens[] = {
        {"defQFormat", XML_defQFormat},
        {"defUnhideWhenUsed", XML_defUnhideWhenUsed},
        {"defSemiHidden", XML_defSemiHidden},
        {"count", XML_count},
        {"defUIPriority", XML_defUIPriority},
        {"defLockedState", XML_defLockedState},
        {nullptr, 0}
    };

    DocxStringTokenMap const aExceptionTokens[] = {
        {"name", XML_name},
        {"locked", XML_locked},
        {"uiPriority", XML_uiPriority},
        {"semiHidden", XML_semiHidden},
        {"unhideWhenUsed", XML_unhideWhenUsed},
        {"qFormat", XML_qFormat},
        {nullptr, 0}
    };

    }

    void DocxAttributeOutput::LatentStyles()
    {
        // Do we have latent styles available?
        uno::Reference<beans::XPropertySet> xPropertySet(m_rExport.m_pDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW);
        uno::Sequence<beans::PropertyValue> aInteropGrabBag;
        xPropertySet->getPropertyValue("InteropGrabBag") >>= aInteropGrabBag;
        uno::Sequence<beans::PropertyValue> aLatentStyles;
        for (sal_Int32 i = 0; i < aInteropGrabBag.getLength(); ++i)
        {
            if (aInteropGrabBag[i].Name == "latentStyles")
            {
                aInteropGrabBag[i].Value >>= aLatentStyles;
                break;
            }
        }
        if (!aLatentStyles.getLength())
            return;

        // Extract default attributes first.
        sax_fastparser::FastAttributeList* pAttributeList = FastSerializerHelper::createAttrList();
        uno::Sequence<beans::PropertyValue> aLsdExceptions;
        for (sal_Int32 i = 0; i < aLatentStyles.getLength(); ++i)
        {
            if (sal_Int32 nToken = DocxStringGetToken(aDefaultTokens, aLatentStyles[i].Name))
                pAttributeList->add(FSNS(XML_w, nToken), OUStringToOString(aLatentStyles[i].Value.get<OUString>(), RTL_TEXTENCODING_UTF8));
            else if (aLatentStyles[i].Name == "lsdExceptions")
                aLatentStyles[i].Value >>= aLsdExceptions;
        }

        XFastAttributeListRef xAttributeList(pAttributeList);
        m_pSerializer->startElementNS(XML_w, XML_latentStyles, xAttributeList);
        pAttributeList = nullptr;

        // Then handle the exceptions.
        for (sal_Int32 i = 0; i < aLsdExceptions.getLength(); ++i)
        {
            pAttributeList = FastSerializerHelper::createAttrList();

            uno::Sequence<beans::PropertyValue> aAttributes;
            aLsdExceptions[i].Value >>= aAttributes;
            for (sal_Int32 j = 0; j < aAttributes.getLength(); ++j)
                if (sal_Int32 nToken = DocxStringGetToken(aExceptionTokens, aAttributes[j].Name))
                    pAttributeList->add(FSNS(XML_w, nToken), OUStringToOString(aAttributes[j].Value.get<OUString>(), RTL_TEXTENCODING_UTF8));

            xAttributeList = pAttributeList;
            m_pSerializer->singleElementNS(XML_w, XML_lsdException, xAttributeList);
            pAttributeList = nullptr;
        }

        m_pSerializer->endElementNS(XML_w, XML_latentStyles);
    }

    namespace
    {

    /// Should the font size we have written out as a default one?
    bool lcl_isDefaultFontSize(const SvxFontHeightItem& rFontHeight, SwDoc* pDoc)
    {
        bool bRet = rFontHeight.GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
        // Additionally, if the default para style has the same font size, then don't write it here.
        SwTextFormatColl* pDefaultStyle = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD);
        if (pDefaultStyle)
        {
            const SfxPoolItem* pItem = nullptr;
            if (pDefaultStyle->GetAttrSet().HasItem(RES_CHRATR_FONTSIZE, &pItem))
                return static_cast<const SvxFontHeightItem*>(pItem)->GetHeight() != rFontHeight.GetHeight();
        }
        return bRet;
    }

    }

    void DocxAttributeOutput::OutputDefaultItem(const SfxPoolItem& rHt)
    {
        bool bMustWrite = true;
        switch (rHt.Which())
        {
            case RES_CHRATR_CASEMAP:
                bMustWrite = static_cast< const SvxCaseMapItem& >(rHt).GetCaseMap() != SvxCaseMap::NotMapped;
                break;
            case RES_CHRATR_COLOR:
                bMustWrite = static_cast< const SvxColorItem& >(rHt).GetValue() != COL_AUTO;
                break;
            case RES_CHRATR_CONTOUR:
                bMustWrite = static_cast< const SvxContourItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_CROSSEDOUT:
                bMustWrite = static_cast< const SvxCrossedOutItem& >(rHt).GetStrikeout() != STRIKEOUT_NONE;
                break;
            case RES_CHRATR_ESCAPEMENT:
                bMustWrite = static_cast< const SvxEscapementItem& >(rHt).GetEscapement() != SvxEscapement::Off;
                break;
            case RES_CHRATR_FONT:
                bMustWrite = true;
                break;
            case RES_CHRATR_FONTSIZE:
                bMustWrite = lcl_isDefaultFontSize(static_cast< const SvxFontHeightItem& >(rHt), m_rExport.m_pDoc);
                break;
            case RES_CHRATR_KERNING:
                bMustWrite = static_cast< const SvxKerningItem& >(rHt).GetValue() != 0;
                break;
            case RES_CHRATR_LANGUAGE:
                bMustWrite = true;
                break;
            case RES_CHRATR_POSTURE:
                bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
                break;
            case RES_CHRATR_SHADOWED:
                bMustWrite = static_cast< const SvxShadowedItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_UNDERLINE:
                bMustWrite = static_cast< const SvxUnderlineItem& >(rHt).GetLineStyle() != LINESTYLE_NONE;
                break;
            case RES_CHRATR_WEIGHT:
                bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
                break;
            case RES_CHRATR_AUTOKERN:
                bMustWrite = static_cast< const SvxAutoKernItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_BLINK:
                bMustWrite = static_cast< const SvxBlinkItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_BACKGROUND:
                {
                    const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
                    bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
                                  rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
                                  rBrushItem.GetGraphic() != nullptr ||
                                  rBrushItem.GetGraphicObject() != nullptr);
                }
                break;

            case RES_CHRATR_CJK_FONT:
                bMustWrite = true;
                break;
            case RES_CHRATR_CJK_FONTSIZE:
                bMustWrite = false; // we have written it already as RES_CHRATR_FONTSIZE
                break;
            case RES_CHRATR_CJK_LANGUAGE:
                bMustWrite = true;
                break;
            case RES_CHRATR_CJK_POSTURE:
                bMustWrite = false; // we have written it already as RES_CHRATR_POSTURE
                break;
            case RES_CHRATR_CJK_WEIGHT:
                bMustWrite = false; // we have written it already as RES_CHRATR_WEIGHT
                break;

            case RES_CHRATR_CTL_FONT:
                bMustWrite = true;
                break;
            case RES_CHRATR_CTL_FONTSIZE:
                bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
                break;
            case RES_CHRATR_CTL_LANGUAGE:
                bMustWrite = true;
                break;
            case RES_CHRATR_CTL_POSTURE:
                bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
                break;
            case RES_CHRATR_CTL_WEIGHT:
                bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
                break;

            case RES_CHRATR_ROTATE:
                bMustWrite = static_cast< const SvxCharRotateItem& >(rHt).GetValue() != 0;
                break;
            case RES_CHRATR_EMPHASIS_MARK:
                bMustWrite = static_cast< const SvxEmphasisMarkItem& >(rHt).GetEmphasisMark() != FontEmphasisMark::NONE;
                break;
            case RES_CHRATR_TWO_LINES:
                bMustWrite = static_cast< const SvxTwoLinesItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_SCALEW:
                bMustWrite = static_cast< const SvxCharScaleWidthItem& >(rHt).GetValue() != 100;
                break;
            case RES_CHRATR_RELIEF:
                bMustWrite = static_cast< const SvxCharReliefItem& >(rHt).GetValue() != FontRelief::NONE;
                break;
            case RES_CHRATR_HIDDEN:
                bMustWrite = static_cast< const SvxCharHiddenItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_BOX:
                {
                    const SvxBoxItem& rBoxItem = static_cast< const SvxBoxItem& >(rHt);
                    bMustWrite = rBoxItem.GetTop() || rBoxItem.GetLeft() ||
                                 rBoxItem.GetBottom() || rBoxItem.GetRight() ||
                                 rBoxItem.GetSmallestDistance();
                }
                break;
            case RES_CHRATR_HIGHLIGHT:
                {
                    const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
                    bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
                                  rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
                                  rBrushItem.GetGraphic() != nullptr ||
                                  rBrushItem.GetGraphicObject() != nullptr);
                }
                break;

            case RES_PARATR_LINESPACING:
                bMustWrite = static_cast< const SvxLineSpacingItem& >(rHt).GetInterLineSpaceRule() != SvxInterLineSpaceRule::Off;
                break;
            case RES_PARATR_ADJUST:
                bMustWrite = static_cast< const SvxAdjustItem& >(rHt).GetAdjust() != SvxAdjust::Left;
                break;
            case RES_PARATR_SPLIT:
                bMustWrite = !static_cast< const SvxFormatSplitItem& >(rHt).GetValue();
                break;
            case RES_PARATR_WIDOWS:
                bMustWrite = static_cast< const SvxWidowsItem& >(rHt).GetValue();
                break;
            case RES_PARATR_TABSTOP:
                bMustWrite = static_cast< const SvxTabStopItem& >(rHt).Count() != 0;
                break;
            case RES_PARATR_HYPHENZONE:
                bMustWrite = static_cast< const SvxHyphenZoneItem& >(rHt).IsHyphen();
                break;
            case RES_PARATR_NUMRULE:
                bMustWrite = !static_cast< const SwNumRuleItem& >(rHt).GetValue().isEmpty();
                break;
            case RES_PARATR_SCRIPTSPACE:
                bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
                break;
            case RES_PARATR_HANGINGPUNCTUATION:
                bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
                break;
            case RES_PARATR_FORBIDDEN_RULES:
                bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
                break;
            case RES_PARATR_VERTALIGN:
                bMustWrite = static_cast< const SvxParaVertAlignItem& >(rHt).GetValue() != SvxParaVertAlignItem::Align::Automatic;
                break;
            case RES_PARATR_SNAPTOGRID:
                bMustWrite = !static_cast< const SvxParaGridItem& >(rHt).GetValue();
                break;
            case RES_CHRATR_GRABBAG:
                bMustWrite = true;
                break;

            default:
                SAL_INFO("sw.ww8", "Unhandled SfxPoolItem with id " << rHt.Which() );
                break;
        }

        if (bMustWrite)
            OutputItem(rHt);
    }

    void DocxAttributeOutput::DocDefaults( )
    {
        // Write the '<w:docDefaults>' section here
        m_pSerializer->startElementNS(XML_w, XML_docDefaults, FSEND);

        // Output the default run properties
        m_pSerializer->startElementNS(XML_w, XML_rPrDefault, FSEND);

        StartStyleProperties(false, 0);

        for (int i = int(RES_CHRATR_BEGIN); i < int(RES_CHRATR_END); ++i)
            OutputDefaultItem(m_rExport.m_pDoc->GetDefault(i));

        EndStyleProperties(false);

        m_pSerializer->endElementNS(XML_w, XML_rPrDefault);

        // Output the default paragraph properties
        m_pSerializer->startElementNS(XML_w, XML_pPrDefault, FSEND);

        StartStyleProperties(true, 0);

        for (int i = int(RES_PARATR_BEGIN); i < int(RES_PARATR_END); ++i)
            OutputDefaultItem(m_rExport.m_pDoc->GetDefault(i));

        EndStyleProperties(true);

        m_pSerializer->endElementNS(XML_w, XML_pPrDefault);

        m_pSerializer->endElementNS(XML_w, XML_docDefaults);
    }

    void DocxAttributeOutput::EndStyles( sal_uInt16 nNumberOfStyles )
    {
        // HACK
        // Ms Office seems to have an internal limitation of 4091 styles
        // and refuses to load .docx with more, even though the spec seems to allow that;
        // so simply if there are more styles, don't export those
        const sal_Int32 nCountStylesToWrite = MSWORD_MAX_STYLES_LIMIT - nNumberOfStyles;
        m_pTableStyleExport->TableStyles(nCountStylesToWrite);
        m_pSerializer->endElementNS( XML_w, XML_styles );
    }

    void DocxAttributeOutput::DefaultStyle()
    {
        // are these the values of enum ww::sti (see ../inc/wwstyles.hxx)?
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::DefaultStyle()");
    }

    /* Writes <a:srcRect> tag back to document.xml if a file contains a cropped image.
    *  NOTE : Tested on images of type JPEG,EMF/WMF,BMP, PNG and GIF.
    */
    void DocxAttributeOutput::WriteSrcRect(const SdrObject* pSdrObj, const SwFrameFormat* pFrameFormat )
    {
        uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
        uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );

        uno::Reference<graphic::XGraphic> xGraphic;
        xPropSet->getPropertyValue("Graphic") >>= xGraphic;
        const Graphic aGraphic(xGraphic);

        Size aOriginalSize(aGraphic.GetPrefSize());

        const MapMode aMap100mm( MapUnit::Map100thMM );
        const MapMode& rMapMode = aGraphic.GetPrefMapMode();
        if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
        {
            aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, aMap100mm);
        }

        css::text::GraphicCrop aGraphicCropStruct;
        xPropSet->getPropertyValue( "GraphicCrop" ) >>= aGraphicCropStruct;
        sal_Int32 nCropL = aGraphicCropStruct.Left;
        sal_Int32 nCropR = aGraphicCropStruct.Right;
        sal_Int32 nCropT = aGraphicCropStruct.Top;
        sal_Int32 nCropB = aGraphicCropStruct.Bottom;

        // simulate border padding as a negative crop.
        const SfxPoolItem* pItem;
        if (pFrameFormat && SfxItemState::SET == pFrameFormat->GetItemState(RES_BOX, false, &pItem))
        {
            const SvxBoxItem& rBox = *static_cast<const SvxBoxItem*>(pItem);
            nCropL -= rBox.GetDistance( SvxBoxItemLine::LEFT );
            nCropR -= rBox.GetDistance( SvxBoxItemLine::RIGHT );
            nCropT -= rBox.GetDistance( SvxBoxItemLine::TOP );
            nCropB -= rBox.GetDistance( SvxBoxItemLine::BOTTOM );
        }

        if ( (0 != nCropL) || (0 != nCropT) || (0 != nCropR) || (0 != nCropB) )
        {
            double  widthMultiplier  = 100000.0/aOriginalSize.Width();
            double  heightMultiplier = 100000.0/aOriginalSize.Height();

            double left   = nCropL * widthMultiplier;
            double right  = nCropR * widthMultiplier;
            double top    = nCropT * heightMultiplier;
            double bottom = nCropB * heightMultiplier;

            m_pSerializer->singleElementNS( XML_a, XML_srcRect,
                 XML_l, I32S(left),
                 XML_t, I32S(top),
                 XML_r, I32S(right),
                 XML_b, I32S(bottom),
                 FSEND );
        }
    }

    void DocxAttributeOutput::PopRelIdCache()
    {
        if (!m_aRelIdCache.empty())
            m_aRelIdCache.pop();
        if (!m_aSdrRelIdCache.empty())
            m_aSdrRelIdCache.pop();
    }

    void DocxAttributeOutput::PushRelIdCache()
    {
        m_aRelIdCache.push(std::map<const Graphic*, OString>());
        m_aSdrRelIdCache.push(std::map<BitmapChecksum, OUString>());
    }

    OUString DocxAttributeOutput::FindRelId(BitmapChecksum nChecksum)
    {
        OUString aRet;

        if (!m_aSdrRelIdCache.empty() && m_aSdrRelIdCache.top().find(nChecksum) != m_aSdrRelIdCache.top().end())
            aRet = m_aSdrRelIdCache.top()[nChecksum];

        return aRet;
    }

    void DocxAttributeOutput::CacheRelId(BitmapChecksum nChecksum, const OUString& rRelId)
    {
        if (!m_aSdrRelIdCache.empty())
            m_aSdrRelIdCache.top()[nChecksum] = rRelId;
    }

    void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj )
    {
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj  ) - some stuff still missing" );

        GetSdtEndBefore(pSdrObj);

        // detect mis-use of the API
        assert(pGrfNode || (pOLEFrameFormat && pOLENode));
        const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : pOLEFrameFormat;
        // create the relation ID
        OString aRelId;
        sal_Int32 nImageType;
        if ( pGrfNode && pGrfNode->IsLinkedFile() )
        {
            // linked image, just create the relation
            OUString aFileName;
            pGrfNode->GetFileFilterNms( &aFileName, nullptr );

            // TODO Convert the file name to relative for better interoperability

            aRelId = m_rExport.AddRelation(
                        oox::getRelationship(Relationship::IMAGE),
                        aFileName );

            nImageType = XML_link;
        }
        else
        {
            // inline, we also have to write the image itself
            const Graphic* pGraphic = nullptr;
            if (pGrfNode)
                pGraphic = &pGrfNode->GetGrf();
            else
                pGraphic = pOLENode->GetGraphic();

            if (!m_aRelIdCache.empty() && m_aRelIdCache.top().find(pGraphic) != m_aRelIdCache.top().end())
                // We already have a RelId for this Graphic.
                aRelId = m_aRelIdCache.top()[pGraphic];
            else
            {
                // Not in cache, then need to write it.
                m_rDrawingML.SetFS( m_pSerializer ); // to be sure that we write to the right stream

                OUString aImageId = m_rDrawingML.WriteImage( *pGraphic );

                aRelId = OUStringToOString( aImageId, RTL_TEXTENCODING_UTF8 );
                if (!m_aRelIdCache.empty())
                    m_aRelIdCache.top()[pGraphic] = aRelId;
            }

            nImageType = XML_embed;
        }

        // In case there are any grab-bag items on the graphic frame, emit them now.
        // These are always character grab-bags, as graphics are at-char or as-char in Word.
        const SfxPoolItem* pItem = nullptr;
        if (pFrameFormat->GetAttrSet().HasItem(RES_FRMATR_GRABBAG, &pItem))
        {
            const SfxGrabBagItem* pGrabBag = static_cast<const SfxGrabBagItem*>(pItem);
            CharGrabBag(*pGrabBag);
        }

        m_rExport.SdrExporter().startDMLAnchorInline(pFrameFormat, rSize);

        // picture description (used for pic:cNvPr later too)
        ::sax_fastparser::FastAttributeList* docPrattrList = FastSerializerHelper::createAttrList();
        docPrattrList->add( XML_id, OString::number( m_anchorId++).getStr());
        docPrattrList->add( XML_name, OUStringToOString( pFrameFormat->GetName(), RTL_TEXTENCODING_UTF8 ) );
        docPrattrList->add( XML_descr, OUStringToOString( pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription(), RTL_TEXTENCODING_UTF8 ).getStr());
        if( GetExport().GetFilter().getVersion( ) != oox::core::ECMA_DIALECT )
            docPrattrList->add( XML_title, OUStringToOString( pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle(), RTL_TEXTENCODING_UTF8 ).getStr());
        XFastAttributeListRef docPrAttrListRef( docPrattrList );
        m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrAttrListRef );
        // TODO hyperlink
        // m_pSerializer->singleElementNS( XML_a, XML_hlinkClick,
        //         FSNS( XML_xmlns, XML_a ), "http://schemas.openxmlformats.org/drawingml/2006/main",
        //         FSNS( XML_r, XML_id ), "rId4",
        //         FSEND );
        m_pSerializer->endElementNS( XML_wp, XML_docPr );

        m_pSerializer->startElementNS( XML_wp, XML_cNvGraphicFramePr,
                FSEND );
        // TODO change aspect?
        m_pSerializer->singleElementNS( XML_a, XML_graphicFrameLocks,
                FSNS( XML_xmlns, XML_a ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)), RTL_TEXTENCODING_UTF8).getStr(),
                XML_noChangeAspect, "1",
                FSEND );
        m_pSerializer->endElementNS( XML_wp, XML_cNvGraphicFramePr );

        m_pSerializer->startElementNS( XML_a, XML_graphic,
                FSNS( XML_xmlns, XML_a ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)), RTL_TEXTENCODING_UTF8).getStr(),
                FSEND );
        m_pSerializer->startElementNS( XML_a, XML_graphicData,
                XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/picture",
                FSEND );

        m_pSerializer->startElementNS( XML_pic, XML_pic,
                FSNS( XML_xmlns, XML_pic ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlPicture)), RTL_TEXTENCODING_UTF8).getStr(),
                FSEND );

        m_pSerializer->startElementNS( XML_pic, XML_nvPicPr,
                FSEND );
        // It seems pic:cNvpr and wp:docPr are pretty much the same thing with the same attributes
        m_pSerializer->startElementNS( XML_pic, XML_cNvPr, docPrAttrListRef );

        // TODO hyperlink
        // m_pSerializer->singleElementNS( XML_a, XML_hlinkClick,
        //     FSNS( XML_r, XML_id ), "rId4",
        //     FSEND );
        m_pSerializer->endElementNS( XML_pic, XML_cNvPr );

        m_pSerializer->startElementNS( XML_pic, XML_cNvPicPr,
                FSEND );
        // TODO change aspect?
        m_pSerializer->singleElementNS( XML_a, XML_picLocks,
                XML_noChangeAspect, "1", XML_noChangeArrowheads, "1",
                FSEND );
        m_pSerializer->endElementNS( XML_pic, XML_cNvPicPr );
        m_pSerializer->endElementNS( XML_pic, XML_nvPicPr );

        // the actual picture
        m_pSerializer->startElementNS( XML_pic, XML_blipFill,
                FSEND );

    /* At this point we are certain that, WriteImage returns empty RelId
       for unhandled graphic type. Therefore we write the picture description
       and not the relation( coz there ain't any), so that the user knows
       there is a image/graphic in the doc but it is broken instead of
       completely discarding it.
    */
        if ( aRelId.isEmpty() )
            m_pSerializer->startElementNS( XML_a, XML_blip,
                FSEND );
        else
            m_pSerializer->startElementNS( XML_a, XML_blip,
                FSNS( XML_r, nImageType ), aRelId.getStr(),
                FSEND );

        pItem = nullptr;
        GraphicDrawMode nMode = GraphicDrawMode::Standard;

        if ( pGrfNode && SfxItemState::SET == pGrfNode->GetSwAttrSet().GetItemState(RES_GRFATR_DRAWMODE, true, &pItem))
        {
            nMode = static_cast<GraphicDrawMode>(static_cast<const SfxEnumItemInterface*>(pItem)->GetEnumValue());
            if (nMode == GraphicDrawMode::Greys)
                m_pSerializer->singleElementNS (XML_a, XML_grayscl, FSEND);
            else if (nMode == GraphicDrawMode::Mono) //black/white has a 0,5 threshold in LibreOffice
                m_pSerializer->singleElementNS (XML_a, XML_biLevel, XML_thresh, OString::number(50000), FSEND);
            else if (nMode == GraphicDrawMode::Watermark) //watermark has a brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice
                m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, OString::number(70000), XML_contrast, OString::number(-70000), FSEND );
        }
        m_pSerializer->endElementNS( XML_a, XML_blip );

        if (pSdrObj){
            WriteSrcRect(pSdrObj, pFrameFormat);
        }

        m_pSerializer->startElementNS( XML_a, XML_stretch,
                FSEND );
        m_pSerializer->singleElementNS( XML_a, XML_fillRect,
                FSEND );
        m_pSerializer->endElementNS( XML_a, XML_stretch );
        m_pSerializer->endElementNS( XML_pic, XML_blipFill );

        // TODO setup the right values below
        m_pSerializer->startElementNS( XML_pic, XML_spPr,
                XML_bwMode, "auto",
                FSEND );

        rtl::Reference<sax_fastparser::FastAttributeList> xFrameAttributes(
            FastSerializerHelper::createAttrList());

        if (pGrfNode)
        {
            MirrorGraph eMirror = pGrfNode->GetSwAttrSet().Get(RES_GRFATR_MIRRORGRF).GetValue();
            if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both)
                // Mirror on the vertical axis is a horizontal flip.
                xFrameAttributes->add(XML_flipH, "1");
        }

        m_pSerializer->startElementNS(
            XML_a, XML_xfrm, uno::Reference<xml::sax::XFastAttributeList>(xFrameAttributes.get()));

        m_pSerializer->singleElementNS( XML_a, XML_off,
                XML_x, "0", XML_y, "0",
                FSEND );
        OString aWidth( OString::number( TwipsToEMU( rSize.Width() ) ) );
        OString aHeight( OString::number( TwipsToEMU( rSize.Height() ) ) );
        m_pSerializer->singleElementNS( XML_a, XML_ext,
                XML_cx, aWidth.getStr(),
                XML_cy, aHeight.getStr(),
                FSEND );
        m_pSerializer->endElementNS( XML_a, XML_xfrm );
        m_pSerializer->startElementNS( XML_a, XML_prstGeom,
                XML_prst, "rect",
                FSEND );
        m_pSerializer->singleElementNS( XML_a, XML_avLst,
                FSEND );
        m_pSerializer->endElementNS( XML_a, XML_prstGeom );

        const SvxBoxItem& rBoxItem = pFrameFormat->GetBox();
        const SvxBorderLine* pLeft = rBoxItem.GetLine(SvxBoxItemLine::LEFT);
        const SvxBorderLine* pRight = rBoxItem.GetLine(SvxBoxItemLine::RIGHT);
        const SvxBorderLine* pTop = rBoxItem.GetLine(SvxBoxItemLine::TOP);
        const SvxBorderLine* pBottom = rBoxItem.GetLine(SvxBoxItemLine::BOTTOM);
        if (pLeft || pRight || pTop || pBottom)
            m_rExport.SdrExporter().writeBoxItemLine(rBoxItem);

        m_rExport.SdrExporter().writeDMLEffectLst(*pFrameFormat);

        m_pSerializer->endElementNS( XML_pic, XML_spPr );

        m_pSerializer->endElementNS( XML_pic, XML_pic );

        m_pSerializer->endElementNS( XML_a, XML_graphicData );
        m_pSerializer->endElementNS( XML_a, XML_graphic );
        m_rExport.SdrExporter().endDMLAnchorInline(pFrameFormat);
    }

    void DocxAttributeOutput::WriteOLE2Obj( const SdrObject* pSdrObj, SwOLENode& rOLENode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
    {
        if( WriteOLEChart( pSdrObj, rSize ))
            return;
        if( WriteOLEMath( rOLENode ))
            return;
        PostponeOLE( rOLENode, rSize, pFlyFrameFormat );
    }

    bool DocxAttributeOutput::WriteOLEChart( const SdrObject* pSdrObj, const Size& rSize )
    {
        uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
        if (!xShape.is())
            return false;

        uno::Reference<beans::XPropertySet> const xPropSet(xShape, uno::UNO_QUERY);
        if (!xPropSet.is())
            return false;

        OUString clsid; // why is the property of type string, not sequence<byte>?
        xPropSet->getPropertyValue("CLSID") >>= clsid;
        assert(!clsid.isEmpty());
        SvGlobalName aClassID;
        bool const isValid(aClassID.MakeId(clsid));
        assert(isValid); (void)isValid;

        if (!SotExchange::IsChart(aClassID))
            return false;

        m_postponedChart = pSdrObj;
        m_postponedChartSize = rSize;
        return true;
    }

    /*
     * Write chart hierarchy in w:drawing after end element of w:rPr tag.
     */
    void DocxAttributeOutput::WritePostponedChart()
    {
        if(m_postponedChart == nullptr)
            return;
        uno::Reference< chart2::XChartDocument > xChartDoc;
        uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(m_postponedChart)->getUnoShape(), uno::UNO_QUERY );
        if( xShape.is() )
        {
            uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
            if( xPropSet.is() )
                xChartDoc.set( xPropSet->getPropertyValue( "Model" ), uno::UNO_QUERY );
        }

        if( xChartDoc.is() )
        {
            SAL_INFO("sw.ww8", "DocxAttributeOutput::WriteOLE2Obj: export chart ");
            m_pSerializer->startElementNS( XML_w, XML_drawing,
                FSEND );
            m_pSerializer->startElementNS( XML_wp, XML_inline,
                XML_distT, "0", XML_distB, "0", XML_distL, "0", XML_distR, "0",
                FSEND );

            OString aWidth( OString::number( TwipsToEMU( m_postponedChartSize.Width() ) ) );
            OString aHeight( OString::number( TwipsToEMU( m_postponedChartSize.Height() ) ) );
            m_pSerializer->singleElementNS( XML_wp, XML_extent,
                XML_cx, aWidth.getStr(),
                XML_cy, aHeight.getStr(),
                FSEND );
            // TODO - the right effectExtent, extent including the effect
            m_pSerializer->singleElementNS( XML_wp, XML_effectExtent,
                XML_l, "0", XML_t, "0", XML_r, "0", XML_b, "0",
                FSEND );

            OUString sName("Object 1");
            uno::Reference< container::XNamed > xNamed( xShape, uno::UNO_QUERY );
            if( xNamed.is() )
                sName = xNamed->getName();

            /* If there is a scenario where a chart is followed by a shape
               which is being exported as an alternate content then, the
               docPr Id is being repeated, ECMA 20.4.2.5 says that the
               docPr Id should be unique, ensuring the same here.
            */
            m_pSerializer->singleElementNS( XML_wp, XML_docPr,
                XML_id, I32S( m_anchorId++ ),
                XML_name, USS( sName ),
                FSEND );

            m_pSerializer->singleElementNS( XML_wp, XML_cNvGraphicFramePr,
                FSEND );

            m_pSerializer->startElementNS( XML_a, XML_graphic,
                FSNS( XML_xmlns, XML_a ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)), RTL_TEXTENCODING_UTF8).getStr(),
                FSEND );

            m_pSerializer->startElementNS( XML_a, XML_graphicData,
                XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart",
                FSEND );

            OString aRelId;
            m_nChartCount++;
            uno::Reference< frame::XModel > xModel( xChartDoc, uno::UNO_QUERY );
            aRelId = m_rExport.OutputChart( xModel, m_nChartCount, m_pSerializer );

            m_pSerializer->singleElementNS( XML_c, XML_chart,
                FSNS( XML_xmlns, XML_c ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlChart)), RTL_TEXTENCODING_UTF8).getStr(),
                FSNS( XML_xmlns, XML_r ), OUStringToOString(GetExport().GetFilter().getNamespaceURL(OOX_NS(officeRel)), RTL_TEXTENCODING_UTF8).getStr(),
                FSNS( XML_r, XML_id ), aRelId.getStr(),
                FSEND );

            m_pSerializer->endElementNS( XML_a, XML_graphicData );
            m_pSerializer->endElementNS( XML_a, XML_graphic );
            m_pSerializer->endElementNS( XML_wp, XML_inline );
            m_pSerializer->endElementNS( XML_w, XML_drawing );

        }
        m_postponedChart = nullptr;
    }

    bool DocxAttributeOutput::WriteOLEMath( const SwOLENode& rOLENode )
    {
        uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode&>(rOLENode).GetOLEObj().GetOleRef());
        SvGlobalName aObjName(xObj->getClassID());

        if( !SotExchange::IsMath(aObjName) )
            return false;
        m_aPostponedMaths.push_back(&rOLENode);
        return true;
    }

    void DocxAttributeOutput::WritePostponedMath(const SwOLENode* pPostponedMath)
    {
        uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode*>(pPostponedMath)->GetOLEObj().GetOleRef());
        if (embed::EmbedStates::LOADED == xObj->getCurrentState())
        {
            // must be running so there is a Component
            try
            {
                xObj->changeState(embed::EmbedStates::RUNNING);
            }
            catch (const uno::Exception&)
            {
            }
        }
        uno::Reference< uno::XInterface > xInterface( xObj->getComponent(), uno::UNO_QUERY );
        if (!xInterface.is())
        {
            SAL_WARN("sw.ww8", "Broken math object");
            return;
        }
    // gcc4.4 (and 4.3 and possibly older) have a problem with dynamic_cast directly to the target class,
    // so help it with an intermediate cast. I'm not sure what exactly the problem is, seems to be unrelated
    // to RTLD_GLOBAL, so most probably a gcc bug.
        oox::FormulaExportBase* formulaexport = dynamic_cast<oox::FormulaExportBase*>(dynamic_cast<SfxBaseModel*>(xInterface.get()));
        assert( formulaexport != nullptr );
        if (formulaexport)
            formulaexport->writeFormulaOoxml( m_pSerializer, GetExport().GetFilter().getVersion(),
                    oox::drawingml::DOCUMENT_DOCX);
    }

    void DocxAttributeOutput::WritePostponedFormControl(const SdrObject* pObject)
    {
        if (!pObject || pObject->GetObjInventor() != SdrInventor::FmForm)
            return;

        SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
        if (!pFormObj)
            return;

        uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
        uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
        if (!xInfo.is())
            return;

        if (xInfo->supportsService("com.sun.star.form.component.DateField"))
        {
            // gather component properties

            Date aOriginalDate(Date::EMPTY);
            OUString sOriginalContent, sDateFormat, sAlias;
            OUString sLocale("en-US");
            uno::Sequence<beans::PropertyValue> aGrabBag;
            uno::Reference<beans::XPropertySet> xShapePropertySet(pFormObj->getUnoShape(), uno::UNO_QUERY);
            uno::Sequence<beans::PropertyValue> aCharFormat;
            if (xShapePropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBag)
            {
                for (sal_Int32 i=0; i < aGrabBag.getLength(); ++i)
                {
                    if (aGrabBag[i].Name == "DateFormat")
                        aGrabBag[i].Value >>= sDateFormat;
                    else if (aGrabBag[i].Name == "Locale")
                        aGrabBag[i].Value >>= sLocale;
                    else if (aGrabBag[i].Name == "OriginalContent")
                        aGrabBag[i].Value >>= sOriginalContent;
                    else if (aGrabBag[i].Name == "OriginalDate")
                    {
                        css::util::Date aUNODate;
                        aGrabBag[i].Value >>= aUNODate;
                        aOriginalDate = aUNODate;
                    }
                    else if (aGrabBag[i].Name == "CharFormat")
                        aGrabBag[i].Value >>= aCharFormat;
                    else if (aGrabBag[i].Name == "ooxml:CT_SdtPr_alias")
                        aGrabBag[i].Value >>= sAlias;
                }
            }
            uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);

            OString sDate;
            OUString aContentText;
            bool bHasDate = false;
            css::util::Date aUNODate;
            if (xPropertySet->getPropertyValue("Date") >>= aUNODate)
            {
                bHasDate = true;
                Date aDate(aUNODate.Day, aUNODate.Month, aUNODate.Year);
                sDate = DateToOString(aDate);

                if (aOriginalDate == aDate)
                {
                    aContentText = sOriginalContent;
                    // sDateFormat was extracted from the grab bag
                }
                else
                {
                    aContentText = OUString::createFromAscii(DateToDDMMYYYYOString(aDate).getStr());
                    sDateFormat = "dd/MM/yyyy";
                }
            }
            else
                aContentText = xPropertySet->getPropertyValue("HelpText").get<OUString>();

            // output component

            m_pSerializer->startElementNS(XML_w, XML_sdt, FSEND);
            m_pSerializer->startElementNS(XML_w, XML_sdtPr, FSEND);

            if (!sAlias.isEmpty())
                m_pSerializer->singleElementNS(XML_w, XML_alias,
                                               FSNS(XML_w, XML_val), OUStringToOString(sAlias, RTL_TEXTENCODING_UTF8),
                                               FSEND);

            if (bHasDate)
                m_pSerializer->startElementNS(XML_w, XML_date,
                                              FSNS( XML_w, XML_fullDate ), sDate.getStr(),
                                              FSEND);
            else
                m_pSerializer->startElementNS(XML_w, XML_date, FSEND);

            m_pSerializer->singleElementNS(XML_w, XML_dateFormat,
                                           FSNS(XML_w, XML_val),
                                           OUStringToOString( sDateFormat, RTL_TEXTENCODING_UTF8 ).getStr(),
                                           FSEND);
            m_pSerializer->singleElementNS(XML_w, XML_lid,
                                           FSNS(XML_w, XML_val),
                                           OUStringToOString( sLocale, RTL_TEXTENCODING_UTF8 ).getStr(),
                                           FSEND);
            m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
                                           FSNS(XML_w, XML_val), "dateTime",
                                           FSEND);
            m_pSerializer->singleElementNS(XML_w, XML_calendar,
                                           FSNS(XML_w, XML_val), "gregorian",
                                           FSEND);

            m_pSerializer->endElementNS(XML_w, XML_date);
            m_pSerializer->endElementNS(XML_w, XML_sdtPr);

            m_pSerializer->startElementNS(XML_w, XML_sdtContent, FSEND);
            m_pSerializer->startElementNS(XML_w, XML_r, FSEND);

            if (aCharFormat.hasElements())
            {
                m_pTableStyleExport->SetSerializer(m_pSerializer);
                m_pTableStyleExport->CharFormat(aCharFormat);
            }

            RunText(aContentText);
            m_pSerializer->endElementNS(XML_w, XML_r);
            m_pSerializer->endElementNS(XML_w, XML_sdtContent);

            m_pSerializer->endElementNS(XML_w, XML_sdt);
        }
        else if (xInfo->supportsService("com.sun.star.form.component.ComboBox"))
        {
            // gather component properties

            uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
            OUString sText = xPropertySet->getPropertyValue("Text").get<OUString>();
            uno::Sequence<OUString> aItems = xPropertySet->getPropertyValue("StringItemList").get< uno::Sequence<OUString> >();

            // output component

            m_pSerializer->startElementNS(XML_w, XML_sdt, FSEND);
            m_pSerializer->startElementNS(XML_w, XML_sdtPr, FSEND);

            m_pSerializer->startElementNS(XML_w, XML_dropDownList, FSEND);

            for (sal_Int32 i=0; i < aItems.getLength(); ++i)
            {
                m_pSerializer->singleElementNS(XML_w, XML_listItem,
                                               FSNS(XML_w, XML_displayText),
                                               OUStringToOString( aItems[i], RTL_TEXTENCODING_UTF8 ).getStr(),
                                               FSNS(XML_w, XML_value),
                                               OUStringToOString( aItems[i], RTL_TEXTENCODING_UTF8 ).getStr(),
                                               FSEND);
            }

            m_pSerializer->endElementNS(XML_w, XML_dropDownList);
            m_pSerializer->endElementNS(XML_w, XML_sdtPr);

            m_pSerializer->startElementNS(XML_w, XML_sdtContent, FSEND);
            m_pSerializer->startElementNS(XML_w, XML_r, FSEND);
            RunText(sText);
            m_pSerializer->endElementNS(XML_w, XML_r);
            m_pSerializer->endElementNS(XML_w, XML_sdtContent);

            m_pSerializer->endElementNS(XML_w, XML_sdt);
        }
    }

    void DocxAttributeOutput::WritePostponedActiveXControl(bool bInsideRun)
    {
        for( const auto & rPostponedDrawing : m_aPostponedActiveXControls )
        {
            WriteActiveXControl(rPostponedDrawing.object, *rPostponedDrawing.frame, bInsideRun);
        }
        m_aPostponedActiveXControls.clear();
    }

    void DocxAttributeOutput::WriteActiveXControl(const SdrObject* pObject, const SwFrameFormat& rFrameFormat, bool bInsideRun)
    {
        SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
        if (!pFormObj)
            return;

        uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
        if (!xControlModel.is())
            return;

        const bool bAnchoredInline = rFrameFormat.GetAnchor().GetAnchorId() == static_cast<RndStdIds>(css::text::TextContentAnchorType_AS_CHARACTER);

        if(!bInsideRun)
        {
            m_pSerializer->startElementNS(XML_w, XML_r, FSEND);
        }

        // w:pict for floating embedded control and w:object for inline embedded control
        if(bAnchoredInline)
            m_pSerializer->startElementNS(XML_w, XML_object, FSEND);
        else
            m_pSerializer->startElementNS(XML_w, XML_pict, FSEND);

        // write ActiveX fragment and ActiveX binary
        uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pObject)->getUnoShape(), uno::UNO_QUERY);
        std::pair<OString,OString> sRelIdAndName = m_rExport.WriteActiveXObject(xShape, xControlModel);

        // VML shape definition
        m_rExport.VMLExporter().SetSkipwzName(true);
        m_rExport.VMLExporter().SetHashMarkForType(true);
        m_rExport.VMLExporter().OverrideShapeIDGen(true, "control_shape_");
        OString sShapeId;
        if(bAnchoredInline)
        {
            sShapeId = m_rExport.VMLExporter().AddInlineSdrObject(*pObject, true);
        }
        else
        {
            const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient();
            const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient();
            sShapeId = m_rExport.VMLExporter().AddSdrObject(*pObject,
                rHoriOri.GetHoriOrient(), rVertOri.GetVertOrient(),
                rHoriOri.GetRelationOrient(),
                rVertOri.GetRelationOrient(), true);
        }
        // Restore default values
        m_rExport.VMLExporter().SetSkipwzName(false);
        m_rExport.VMLExporter().SetHashMarkForType(false);
        m_rExport.VMLExporter().OverrideShapeIDGen(false);

        // control
        m_pSerializer->singleElementNS(XML_w, XML_control,
                                        FSNS(XML_r, XML_id), sRelIdAndName.first.getStr(),
                                        FSNS(XML_w, XML_name), sRelIdAndName.second.getStr(),
                                        FSNS(XML_w, XML_shapeid), sShapeId.getStr(),
                                        FSEND);

        if(bAnchoredInline)
            m_pSerializer->endElementNS(XML_w, XML_object);
        else
            m_pSerializer->endElementNS(XML_w, XML_pict);

        if(!bInsideRun)
        {
            m_pSerializer->endElementNS(XML_w, XML_r);
        }
    }

    bool DocxAttributeOutput::ExportAsActiveXControl(const SdrObject* pObject) const
    {
        SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
        if (!pFormObj)
            return false;

        uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
        if (!xControlModel.is())
            return false;

        uno::Reference< css::frame::XModel > xModel( m_rExport.m_pDoc->GetDocShell() ? m_rExport.m_pDoc->GetDocShell()->GetModel() : nullptr );
        if (!xModel.is())
            return false;

        uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
        if (!xInfo.is())
            return false;

        // See WritePostponedFormControl
        // By now date field and combobox is handled on a different way, so let's not interfere with the other method.
        if(xInfo->supportsService("com.sun.star.form.component.DateField") ||
           xInfo->supportsService("com.sun.star.form.component.ComboBox"))
            return false;

        oox::ole::OleFormCtrlExportHelper exportHelper(comphelper::getProcessComponentContext(), xModel, xControlModel);
        if(!exportHelper.isValid())
            return false;

        return true;
    }

    void DocxAttributeOutput::PostponeOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
    {
        if( !m_pPostponedOLEs )
            //cannot be postponed, try to write now
            WriteOLE( rNode, rSize, pFlyFrameFormat );
        else
            m_pPostponedOLEs->push_back( PostponedOLE( &rNode, rSize, pFlyFrameFormat ) );
    }

    /*
     * Write w:object hierarchy for embedded objects after end element of w:rPr tag.
     */
    void DocxAttributeOutput::WritePostponedOLE()
    {
        if( !m_pPostponedOLEs )
            return;

        for( const auto & rPostponedOLE : *m_pPostponedOLEs )
        {
            WriteOLE( *rPostponedOLE.object, rPostponedOLE.size, rPostponedOLE.frame );
        }

        // clear list of postponed objects
        m_pPostponedOLEs.reset(nullptr);
    }

    void DocxAttributeOutput::WriteOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* rFlyFrameFormat )
    {
        // get interoperability information about embedded objects
        uno::Reference< beans::XPropertySet > xPropSet( m_rExport.m_pDoc->GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
        uno::Sequence< beans::PropertyValue > aGrabBag, aObjectsInteropList,aObjectInteropAttributes;
        xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= aGrabBag;
        for( sal_Int32 i=0; i < aGrabBag.getLength(); ++i )
            if ( aGrabBag[i].Name == "EmbeddedObjects" )
            {
                aGrabBag[i].Value >>= aObjectsInteropList;
                break;
            }

        SwOLEObj& aObject = rNode.GetOLEObj();
        uno::Reference < embed::XEmbeddedObject > xObj( aObject.GetOleRef() );
        comphelper::EmbeddedObjectContainer* aContainer = aObject.GetObject().GetContainer();
        OUString sObjectName = aContainer->GetEmbeddedObjectName( xObj );

        // set some attributes according to the type of the embedded object
        OUString sProgID, sDrawAspect = "Content";
        for( sal_Int32 i=0; i < aObjectsInteropList.getLength(); ++i )
            if ( aObjectsInteropList[i].Name == sObjectName )
            {
                aObjectsInteropList[i].Value >>= aObjectInteropAttributes;
                break;
            }

        for( sal_Int32 i=0; i < aObjectInteropAttributes.getLength(); ++i )
        {
                if ( aObjectInteropAttributes[i].Name == "ProgID" )
                {
                    aObjectInteropAttributes[i].Value >>= sProgID;
                }
                else if ( aObjectInteropAttributes[i].Name == "DrawAspect" )
                {
                    aObjectInteropAttributes[i].Value >>= sDrawAspect;
                }
        }

        // write embedded file
        OString sId = m_rExport.WriteOLEObject(aObject, sProgID);

        if( sId.isEmpty() )
        {
            // the embedded file could not be saved
            // fallback: save as an image
            FlyFrameGraphic( nullptr, rSize, rFlyFrameFormat, &rNode );
            return;
        }

        // write preview image
        const Graphic* pGraphic = rNode.GetGraphic();
        m_rDrawingML.SetFS(m_pSerializer);
        OUString sImageId = m_rDrawingML.WriteImage( *pGraphic );

        m_pSerializer->startElementNS( XML_w, XML_object, FSEND );

        OStringBuffer sShapeStyle, sShapeId;
        sShapeStyle.append( "width:" ).append( double( rSize.Width() ) / 20 )
                            .append( "pt;height:" ).append( double( rSize.Height() ) / 20 )
                            .append( "pt" ); //from VMLExport::AddRectangleDimensions(), it does: value/20
        sShapeId.append( "ole_" ).append( sId );

        // shape definition
        m_pSerializer->startElementNS( XML_v, XML_shape,
                                       XML_id, sShapeId.getStr(),
                                       XML_style, sShapeStyle.getStr(),
                                       FSNS( XML_o, XML_ole ), "", //compulsory, even if it's empty
                                       FSEND );

        // shape filled with the preview image
        m_pSerializer->singleElementNS( XML_v, XML_imagedata,
                                        FSNS( XML_r, XML_id ), OUStringToOString( sImageId, RTL_TEXTENCODING_UTF8 ).getStr(),
                                        FSNS( XML_o, XML_title ), "",
                                        FSEND );

        m_pSerializer->endElementNS( XML_v, XML_shape );

        // OLE object definition
        m_pSerializer->singleElementNS( XML_o, XML_OLEObject,
                                        XML_Type, "Embed",
                                        XML_ProgID, OUStringToOString( sProgID, RTL_TEXTENCODING_UTF8 ).getStr(),
                                        XML_ShapeID, sShapeId.getStr(),
                                        XML_DrawAspect, OUStringToOString( sDrawAspect, RTL_TEXTENCODING_UTF8 ).getStr(),
                                        XML_ObjectID, "_" + OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())),
                                        FSNS( XML_r, XML_id ), sId.getStr(),
                                        FSEND );

        m_pSerializer->endElementNS( XML_w, XML_object );
    }

    void DocxAttributeOutput::WritePostponedCustomShape()
    {
        if (!m_pPostponedCustomShape)
            return;

        for( const auto & rPostponedDrawing : *m_pPostponedCustomShape)
        {
            if ( IsAlternateContentChoiceOpen() )
                m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
            else
                m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
        }
        m_pPostponedCustomShape.reset(nullptr);
    }

    void DocxAttributeOutput::WritePostponedDMLDrawing()
    {
        if (!m_pPostponedDMLDrawings)
            return;

        // Clear the list early, this method may be called recursively.
        std::unique_ptr< std::vector<PostponedDrawing> > pPostponedDMLDrawings(m_pPostponedDMLDrawings.release());
        std::unique_ptr< std::vector<PostponedOLE> > pPostponedOLEs(m_pPostponedOLEs.release());

        for( const auto & rPostponedDrawing : *pPostponedDMLDrawings )
        {
            // Avoid w:drawing within another w:drawing.
            if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) )
               m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
            else
                m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
        }

        m_pPostponedOLEs = std::move(pPostponedOLEs);
    }

    void DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame &rFrame, const Point& /*rNdTopLeft*/ )
    {
        m_pSerializer->mark(Tag_OutputFlyFrame);

        switch ( rFrame.GetWriterType() )
        {
            case ww8::Frame::eGraphic:
                {
                    const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
                    const SwNode *pNode = rFrame.GetContent();
                    const SwGrfNode *pGrfNode = pNode ? pNode->GetGrfNode() : nullptr;
                    if ( pGrfNode )
                    {
                        if (!m_pPostponedGraphic)
                        {
                            m_bPostponedProcessingFly = false ;
                            FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), nullptr, nullptr, pSdrObj);
                        }
                        else // we are writing out attributes, but w:drawing should not be inside w:rPr,
                        {    // so write it out later
                            m_bPostponedProcessingFly = true ;
                            m_pPostponedGraphic->push_back(PostponedGraphic(pGrfNode, rFrame.GetLayoutSize(), pSdrObj));
                        }
                    }
                }
                break;
            case ww8::Frame::eDrawing:
                {
                    const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
                    if ( pSdrObj )
                    {
                        if ( IsDiagram( pSdrObj ) )
                        {
                            if ( !m_pPostponedDiagrams )
                            {
                                m_bPostponedProcessingFly = false ;
                                m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
                            }
                            else // we are writing out attributes, but w:drawing should not be inside w:rPr,
                            {    // so write it out later
                                m_bPostponedProcessingFly = true ;
                                m_pPostponedDiagrams->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrameFormat()) ));
                            }
                        }
                        else
                        {
                            if (!m_pPostponedDMLDrawings)
                            {
                                if ( IsAlternateContentChoiceOpen() )
                                {
                                    // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing.
                                    if( m_rExport.SdrExporter().IsDrawingOpen() )
                                        m_pPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
                                    else
                                        m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++);
                                }
                                else
                                    m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);

                                m_bPostponedProcessingFly = false ;
                            }
                            // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added
                            // m_bParagraphFrameOpen: check if the frame is open.
                            else if (IsAlternateContentChoiceOpen() && m_bParagraphFrameOpen)
                                m_pPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
                            else
                            {
                                // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later
                                m_bPostponedProcessingFly = true ;
                                m_pPostponedDMLDrawings->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
                            }
                        }
                    }
                }
                break;
            case ww8::Frame::eTextBox:
                {
                    // If this is a TextBox of a shape, then ignore: it's handled in WriteTextBox().
                    if (DocxSdrExport::isTextBox(rFrame.GetFrameFormat()))
                        break;

                    // The frame output is postponed to the end of the anchor paragraph
                    bool bDuplicate = false;
                    const OUString& rName = rFrame.GetFrameFormat().GetName();
                    unsigned nSize = m_aFramesOfParagraph.size();
                    for( unsigned nIndex = 0; nIndex < nSize; ++nIndex )
                    {
                        const OUString& rNameExisting = m_aFramesOfParagraph[nIndex].GetFrameFormat().GetName();

                        if (!rName.isEmpty() && !rNameExisting.isEmpty())
                        {
                            if (rName == rNameExisting)
                                bDuplicate = true;
                        }
                    }

                    if( !bDuplicate )
                    {
                        m_bPostponedProcessingFly = true ;
                        m_aFramesOfParagraph.emplace_back(rFrame);
                    }
                }
                break;
            case ww8::Frame::eOle:
                {
                    const SwFrameFormat &rFrameFormat = rFrame.GetFrameFormat();
                    const SdrObject *pSdrObj = rFrameFormat.FindRealSdrObject();
                    if ( pSdrObj )
                    {
                        SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1);
                        SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode();
                        WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrameFormat*>( &rFrameFormat ));
                        m_bPostponedProcessingFly = false ;
                    }
                }
                break;
            case ww8::Frame::eFormControl:
                {
                    const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject();
                    if(ExportAsActiveXControl(pObject))
                        m_aPostponedActiveXControls.emplace_back(pObject, &(rFrame.GetFrameFormat()));
                    else
                        m_aPostponedFormControls.push_back(pObject);
                    m_bPostponedProcessingFly = true ;
                }
                break;
            default:
                SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFrame ) - frame type " <<
                        ( rFrame.GetWriterType() == ww8::Frame::eTextBox ? "eTextBox":
                          ( rFrame.GetWriterType() == ww8::Frame::eOle ? "eOle": "???" ) ) );
                break;
        }

        m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame, sax_fastparser::MergeMarks::POSTPONE);
    }

    bool DocxAttributeOutput::IsDiagram( const SdrObject* sdrObject )
    {
        uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(sdrObject)->getUnoShape(), uno::UNO_QUERY );
        if ( !xShape.is() )
            return false;

        uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
        if ( !xPropSet.is() )
            return false;

        // if the shape doesn't have the InteropGrabBag property, it's not a diagram
        uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
        OUString aName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG;
        if ( !xPropSetInfo->hasPropertyByName( aName ) )
            return false;

        uno::Sequence< beans::PropertyValue > propList;
        xPropSet->getPropertyValue( aName ) >>= propList;
        for ( sal_Int32 nProp=0; nProp < propList.getLength(); ++nProp )
        {
            // if we find any of the diagram components, it's a diagram
            OUString propName = propList[nProp].Name;
            if ( propName == "OOXData" || propName == "OOXLayout" || propName == "OOXStyle" ||
                 propName == "OOXColor" || propName == "OOXDrawing")
                return true;
        }
        return false;
    }

    void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
    {
        const EditTextObject& rEditObj = rParaObj.GetTextObject();
        MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX );

        sal_Int32 nPara = rEditObj.GetParagraphCount();

        m_pSerializer->startElementNS( XML_w, XML_txbxContent, FSEND );
        for (sal_Int32 n = 0; n < nPara; ++n)
        {
            if( n )
                aAttrIter.NextPara( n );

            OUString aStr( rEditObj.GetText( n ));
            sal_Int32 nCurrentPos = 0;
            sal_Int32 nEnd = aStr.getLength();

            StartParagraph(ww8::WW8TableNodeInfo::Pointer_t());

            // Write paragraph properties.
            StartParagraphProperties();
            aAttrIter.OutParaAttr(false);
            SfxItemSet aParagraphMarkerProperties(m_rExport.m_pDoc->GetAttrPool());
            EndParagraphProperties(aParagraphMarkerProperties, nullptr, nullptr, nullptr);

            do {
                const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd);

                m_pSerializer->startElementNS( XML_w, XML_r, FSEND );

                // Write run properties.
                m_pSerializer->startElementNS(XML_w, XML_rPr, FSEND);
                aAttrIter.OutAttr(nCurrentPos);
                WriteCollectedRunProperties();
                m_pSerializer->endElementNS(XML_w, XML_rPr);

                bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
                if( !bTextAtr )
                {
                    OUString aOut( aStr.copy( nCurrentPos, nNextAttr - nCurrentPos ) );
                    RunText(aOut);
                }

                if ( !m_sRawText.isEmpty() )
                {
                    RunText( m_sRawText );
                    m_sRawText.clear();
                }

                m_pSerializer->endElementNS( XML_w, XML_r );

                nCurrentPos = nNextAttr;
                aAttrIter.NextPos();
            }
            while( nCurrentPos < nEnd );
            // Word can't handle nested text boxes, so write them on the same level.
            ++m_nTextFrameLevel;
            EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t());
            --m_nTextFrameLevel;
        }
        m_pSerializer->endElementNS( XML_w, XML_txbxContent );
    }

    void DocxAttributeOutput::pushToTableExportContext(DocxTableExportContext& rContext)
    {
        rContext.m_pTableInfo = m_rExport.m_pTableInfo;
        m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();

        rContext.m_bTableCellOpen = m_tableReference->m_bTableCellOpen;
        m_tableReference->m_bTableCellOpen = false;

        rContext.m_nTableDepth = m_tableReference->m_nTableDepth;
        m_tableReference->m_nTableDepth = 0;

        rContext.m_bStartedParaSdt = m_bStartedParaSdt;
        m_bStartedParaSdt = false;
    }

    void DocxAttributeOutput::popFromTableExportContext(DocxTableExportContext const & rContext)
    {
        m_rExport.m_pTableInfo = rContext.m_pTableInfo;
        m_tableReference->m_bTableCellOpen = rContext.m_bTableCellOpen;
        m_tableReference->m_nTableDepth = rContext.m_nTableDepth;
        m_bStartedParaSdt = rContext.m_bStartedParaSdt;
    }

    void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape)
    {
        DocxTableExportContext aTableExportContext(*this);

        SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
        const SwPosition* pAnchor = pTextBox->GetAnchor().GetContentAnchor();
        ww8::Frame aFrame(*pTextBox, *pAnchor);
        m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true);
    }

    void DocxAttributeOutput::WriteVMLTextBox(uno::Reference<drawing::XShape> xShape)
    {
        DocxTableExportContext aTableExportContext(*this);

        SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
        const SwPosition* pAnchor = pTextBox->GetAnchor().GetContentAnchor();
        ww8::Frame aFrame(*pTextBox, *pAnchor);
        m_rExport.SdrExporter().writeVMLTextFrame(&aFrame, /*bTextBoxOnly=*/true);
    }

    oox::drawingml::DrawingML& DocxAttributeOutput::GetDrawingML()
    {
        return m_rDrawingML;
    }

    /// Functor to do case-insensitive ordering of OUString instances.
    struct OUStringIgnoreCase
    {
        bool operator() (const OUString& lhs, const OUString& rhs) const
        {
            return lhs.compareToIgnoreAsciiCase(rhs) < 0;
        }
    };

    /// Guesses if a style created in Writer (no grab-bag) should be qFormat or not.
    static bool lcl_guessQFormat(const OUString& rName, sal_uInt16 nWwId)
    {
        // If the style has no dedicated STI number, then it's probably a custom style -> qFormat.
        if (nWwId == ww::stiUser)
            return true;

        // Allow exported built-in styles UI language neutral
        if ( nWwId == ww::stiNormal ||
            ( nWwId>= ww::stiLev1 && nWwId <= ww::stiLev9 ) ||
                nWwId == ww::stiCaption || nWwId == ww::stiTitle ||
                nWwId == ww::stiSubtitle || nWwId == ww::stiStrong ||
                nWwId == ww::stiEmphasis )
            return true;

        static std::set<OUString, OUStringIgnoreCase> aWhitelist;
        if (aWhitelist.empty())
        {
            aWhitelist.insert("No Spacing");
            aWhitelist.insert("List Paragraph");
            aWhitelist.insert("Quote");
            aWhitelist.insert("Intense Quote");
            aWhitelist.insert("Subtle Emphasis,");
            aWhitelist.insert("Intense Emphasis");
            aWhitelist.insert("Subtle Reference");
            aWhitelist.insert("Intense Reference");
            aWhitelist.insert("Book Title");
            aWhitelist.insert("TOC Heading");
        }
        // Not custom style? Then we have a list of standard styles which should be qFormat.
        return aWhitelist.find(rName) != aWhitelist.end();
    }

    void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
            sal_uInt16 nBase, sal_uInt16 nNext, sal_uInt16 nWwId, sal_uInt16 nId, bool bAutoUpdate )
    {
        bool bQFormat = false, bUnhideWhenUsed = false, bSemiHidden = false, bLocked = false, bDefault = false, bCustomStyle = false;
        OUString aLink, aRsid, aUiPriority;
        FastAttributeList* pStyleAttributeList = FastSerializerHelper::createAttrList();
        uno::Any aAny;
        if (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR)
        {
            const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nId);
            pFormat->GetGrabBagItem(aAny);
        }
        else
        {
            const SwNumRule* pRule = m_rExport.m_pStyles->GetSwNumRule(nId);
            pRule->GetGrabBagItem(aAny);
        }
        const uno::Sequence<beans::PropertyValue>& rGrabBag = aAny.get< uno::Sequence<beans::PropertyValue> >();

        for (sal_Int32 i = 0; i < rGrabBag.getLength(); ++i)
        {
            if (rGrabBag[i].Name == "uiPriority")
                aUiPriority = rGrabBag[i].Value.get<OUString>();
            else if (rGrabBag[i].Name == "qFormat")
                bQFormat = true;
            else if (rGrabBag[i].Name == "link")
                aLink = rGrabBag[i].Value.get<OUString>();
            else if (rGrabBag[i].Name == "rsid")
                aRsid = rGrabBag[i].Value.get<OUString>();
            else if (rGrabBag[i].Name == "unhideWhenUsed")
                bUnhideWhenUsed = true;
            else if (rGrabBag[i].Name == "semiHidden")
                bSemiHidden = true;
            else if (rGrabBag[i].Name == "locked")
                bLocked = true;
            else if (rGrabBag[i].Name == "default")
                bDefault = rGrabBag[i].Value.get<bool>();
            else if (rGrabBag[i].Name == "customStyle")
                bCustomStyle = rGrabBag[i].Value.get<bool>();
            else
                SAL_WARN("sw.ww8", "Unhandled style property: " << rGrabBag[i].Name);
        }

        // MSO exports English names and writerfilter only recognize them.
        const sal_Char *pEnglishName = nullptr;
        const char* pType = nullptr;
        switch (eType)
        {
            case STYLE_TYPE_PARA:
                pType = "paragraph";
                if ( nWwId < ww::stiMax)
                    pEnglishName = ww::GetEnglishNameFromSti( static_cast<ww::sti>(nWwId ) );
                break;
            case STYLE_TYPE_CHAR: pType = "character"; break;
            case STYLE_TYPE_LIST: pType = "numbering"; break;
        }
        pStyleAttributeList->add(FSNS( XML_w, XML_type ), pType);
        pStyleAttributeList->add(FSNS( XML_w, XML_styleId ), m_rExport.m_pStyles->GetStyleId(nId).getStr());
        if (bDefault)
            pStyleAttributeList->add(FSNS(XML_w, XML_default), "1");
        if (bCustomStyle)
            pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
        XFastAttributeListRef xStyleAttributeList(pStyleAttributeList);
        m_pSerializer->startElementNS( XML_w, XML_style, xStyleAttributeList);
        m_pSerializer->singleElementNS( XML_w, XML_name,
                FSNS( XML_w, XML_val ), pEnglishName ? pEnglishName : OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ).getStr(),
                FSEND );

        if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST)
        {
            m_pSerializer->singleElementNS( XML_w, XML_basedOn,
                    FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nBase).getStr(),
                    FSEND );
        }

        if ( nNext != nId && eType != STYLE_TYPE_LIST)
        {
            m_pSerializer->singleElementNS( XML_w, XML_next,
                    FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nNext).getStr(),
                    FSEND );
        }

        if (!aLink.isEmpty())
            m_pSerializer->singleElementNS(XML_w, XML_link,
                    FSNS(XML_w, XML_val), OUStringToOString(aLink, RTL_TEXTENCODING_UTF8).getStr(),
                    FSEND);

        if ( bAutoUpdate )
            m_pSerializer->singleElementNS( XML_w, XML_autoRedefine, FSEND );

        if (!aUiPriority.isEmpty())
            m_pSerializer->singleElementNS(XML_w, XML_uiPriority,
                    FSNS(XML_w, XML_val), OUStringToOString(aUiPriority, RTL_TEXTENCODING_UTF8).getStr(),
                    FSEND);
        if (bSemiHidden)
            m_pSerializer->singleElementNS(XML_w, XML_semiHidden, FSEND);
        if (bUnhideWhenUsed)
            m_pSerializer->singleElementNS(XML_w, XML_unhideWhenUsed, FSEND);

        if (bQFormat || lcl_guessQFormat(rName, nWwId))
            m_pSerializer->singleElementNS(XML_w, XML_qFormat, FSEND);
        if (bLocked)
            m_pSerializer->singleElementNS(XML_w, XML_locked, FSEND);
        if (!aRsid.isEmpty())
            m_pSerializer->singleElementNS(XML_w, XML_rsid,
                    FSNS(XML_w, XML_val), OUStringToOString(aRsid, RTL_TEXTENCODING_UTF8).getStr(),
                    FSEND);
    }

    void DocxAttributeOutput::EndStyle()
    {
        m_pSerializer->endElementNS( XML_w, XML_style );
    }

    void DocxAttributeOutput::StartStyleProperties( bool bParProp, sal_uInt16 /*nStyle*/ )
    {
        if ( bParProp )
        {
            m_pSerializer->startElementNS( XML_w, XML_pPr, FSEND );
            InitCollectedParagraphProperties();
        }
        else
        {
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );
            InitCollectedRunProperties();
        }
    }

    void DocxAttributeOutput::EndStyleProperties( bool bParProp )
    {
        if ( bParProp )
        {
            WriteCollectedParagraphProperties();

            // Merge the marks for the ordered elements
            m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);

            m_pSerializer->endElementNS( XML_w, XML_pPr );
        }
        else
        {
            WriteCollectedRunProperties();

            // Merge the marks for the ordered elements
            m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);

            m_pSerializer->endElementNS( XML_w, XML_rPr );
        }
    }

    namespace
    {

    void lcl_OutlineLevel(sax_fastparser::FSHelperPtr const & pSerializer, sal_uInt16 nLevel)
    {
        if (nLevel >= WW8ListManager::nMaxLevel)
            nLevel = WW8ListManager::nMaxLevel - 1;

        pSerializer->singleElementNS(XML_w, XML_outlineLvl,
                FSNS(XML_w, XML_val), OString::number(nLevel).getStr(),
                FSEND);
    }

    }

    void DocxAttributeOutput::OutlineNumbering(sal_uInt8 const /*nLvl*/)
    {
        // Handled by ParaOutlineLevel() instead.
    }

    void DocxAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& rItem)
    {
        if (rItem.GetValue() > 0)
            lcl_OutlineLevel(m_pSerializer, rItem.GetValue() - 1);
    }

    void DocxAttributeOutput::PageBreakBefore( bool bBreak )
    {
        if ( bBreak )
            m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore,
                    FSNS( XML_w, XML_val ), "false",
                    FSEND );
    }

    void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, const WW8_SepInfo* pSectionInfo )
    {
        switch ( nC )
        {
            case msword::ColumnBreak:
                // The column break should be output in the next paragraph...
                m_nColBreakStatus = COLBRK_POSTPONE;
                break;
            case msword::PageBreak:
                if ( pSectionInfo )
                {
                    // Detect when the current node is the last node in the
                    // document: the last section is written explicitly in
                    // DocxExport::WriteMainText(), don't duplicate that here.
                    SwNodeIndex aCurrentNode(m_rExport.m_pCurPam->GetNode());
                    SwNodeIndex aLastNode(m_rExport.m_pDoc->GetNodes().GetEndOfContent(), -1);
                    bool bEmit = aCurrentNode != aLastNode;

                    if (!bEmit)
                    {
                        // Need to still emit an empty section at the end of the
                        // document in case balanced columns are wanted, since the last
                        // section in Word is always balanced.
                        sal_uInt16 nColumns = 1;
                        bool bBalance = false;
                        if (const SwSectionFormat* pFormat = pSectionInfo->pSectionFormat)
                        {
                            if (pFormat != reinterpret_cast<SwSectionFormat*>(sal_IntPtr(-1)))
                            {
                                nColumns = pFormat->GetCol().GetNumCols();
                                const SwFormatNoBalancedColumns& rNoBalanced = pFormat->GetBalancedColumns();
                                bBalance = !rNoBalanced.GetValue();
                            }
                        }
                        bEmit = (nColumns > 1 && bBalance);
                    }

                    if (!bEmit)
                    {
                        // Also need to emit if the page desc contains a header or
                        // footer, otherwise we go with the properties of the
                        // section (and not the page style), which never has
                        // headers/footers.
                        if (const SwPageDesc* pPageDesc = pSectionInfo->pPageDesc)
                        {
                            const auto& rMaster = pPageDesc->GetMaster();
                            bEmit = rMaster.GetHeader().IsActive() || rMaster.GetFooter().IsActive();
                        }
                    }

                    // don't add section properties if this will be the first
                    // paragraph in the document
                    if ( !m_bParagraphOpened && !m_bIsFirstParagraph && bEmit )
                    {
                        // Create a dummy paragraph if needed
                        m_pSerializer->startElementNS( XML_w, XML_p, FSEND );
                        m_pSerializer->startElementNS( XML_w, XML_pPr, FSEND );

                        m_rExport.SectionProperties( *pSectionInfo );

                        m_pSerializer->endElementNS( XML_w, XML_pPr );
                        m_pSerializer->endElementNS( XML_w, XML_p );
                    }
                    else
                    {
                        // postpone the output of this; it has to be done inside the
                        // paragraph properties, so remember it until then
                        m_pSectionInfo.reset( new WW8_SepInfo( *pSectionInfo ));
                    }
                }
                else if ( m_bParagraphOpened )
                {
                    m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
                    m_pSerializer->singleElementNS( XML_w, XML_br,
                            FSNS( XML_w, XML_type ), "page", FSEND );
                    m_pSerializer->endElementNS( XML_w, XML_r );
                }
                else
                    m_bPostponedPageBreak = true;

                break;
            default:
                SAL_INFO("sw.ww8", "Unknown section break to write: " << nC );
                break;
        }
    }

    void DocxAttributeOutput::EndParaSdtBlock()
    {
        if (m_bStartedParaSdt)
        {
            // Paragraph-level SDT still open? Close it now.
            EndSdtBlock();
            m_bStartedParaSdt = false;
        }
    }

    void DocxAttributeOutput::StartSection()
    {
        m_pSerializer->startElementNS( XML_w, XML_sectPr, FSEND );
        m_bOpenedSectPr = true;

        // Write the elements in the spec order
        static const sal_Int32 aOrder[] =
        {
            FSNS( XML_w, XML_headerReference ),
            FSNS( XML_w, XML_footerReference ),
            FSNS( XML_w, XML_footnotePr ),
            FSNS( XML_w, XML_endnotePr ),
            FSNS( XML_w, XML_type ),
            FSNS( XML_w, XML_pgSz ),
            FSNS( XML_w, XML_pgMar ),
            FSNS( XML_w, XML_paperSrc ),
            FSNS( XML_w, XML_pgBorders ),
            FSNS( XML_w, XML_lnNumType ),
            FSNS( XML_w, XML_pgNumType ),
            FSNS( XML_w, XML_cols ),
            FSNS( XML_w, XML_formProt ),
            FSNS( XML_w, XML_vAlign ),
            FSNS( XML_w, XML_noEndnote ),
            FSNS( XML_w, XML_titlePg ),
            FSNS( XML_w, XML_textDirection ),
            FSNS( XML_w, XML_bidi ),
            FSNS( XML_w, XML_rtlGutter ),
            FSNS( XML_w, XML_docGrid ),
            FSNS( XML_w, XML_printerSettings ),
            FSNS( XML_w, XML_sectPrChange )
        };

        // postpone the output so that we can later [in EndParagraphProperties()]
        // prepend the properties before the run
        m_pSerializer->mark(Tag_StartSection, comphelper::containerToSequence(aOrder));
        m_bHadSectPr = true;
    }

    void DocxAttributeOutput::EndSection()
    {
        // Write the section properties
        if ( m_pSectionSpacingAttrList.is() )
        {
            XFastAttributeListRef xAttrList( m_pSectionSpacingAttrList.get() );
            m_pSectionSpacingAttrList.clear();

            m_pSerializer->singleElementNS( XML_w, XML_pgMar, xAttrList );
        }

        // Order the elements
        m_pSerializer->mergeTopMarks(Tag_StartSection);

        m_pSerializer->endElementNS( XML_w, XML_sectPr );
        m_bOpenedSectPr = false;
    }

    void DocxAttributeOutput::SectionFormProtection( bool bProtected )
    {
        if ( bProtected )
            m_pSerializer->singleElementNS( XML_w, XML_formProt,
                    FSNS( XML_w, XML_val ), "true", FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_formProt,
                    FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::SectionLineNumbering( sal_uLong nRestartNo, const SwLineNumberInfo& rLnNumInfo )
    {
        FastAttributeList* pAttr = FastSerializerHelper::createAttrList();
        pAttr->add( FSNS( XML_w, XML_countBy ), OString::number(rLnNumInfo.GetCountBy()).getStr());
        pAttr->add( FSNS( XML_w, XML_restart ), rLnNumInfo.IsRestartEachPage() ? "newPage" : "continuous" );
        if( rLnNumInfo.GetPosFromLeft())
            pAttr->add( FSNS( XML_w, XML_distance ), OString::number(rLnNumInfo.GetPosFromLeft()).getStr());
        if( nRestartNo )
            pAttr->add( FSNS( XML_w, XML_start ), OString::number( nRestartNo).getStr());
        XFastAttributeListRef xAttrs( pAttr );
        m_pSerializer->singleElementNS( XML_w, XML_lnNumType, xAttrs );
    }

    void DocxAttributeOutput::SectionTitlePage()
    {
        m_pSerializer->singleElementNS( XML_w, XML_titlePg, FSEND );
    }

    void DocxAttributeOutput::SectionPageBorders( const SwFrameFormat* pFormat, const SwFrameFormat* /*pFirstPageFormat*/ )
    {
        // Output the margins

        const SvxBoxItem& rBox = pFormat->GetBox( );

        const SvxBorderLine* pLeft = rBox.GetLeft( );
        const SvxBorderLine* pTop = rBox.GetTop( );
        const SvxBorderLine* pRight = rBox.GetRight( );
        const SvxBorderLine* pBottom = rBox.GetBottom( );

        if ( !(pBottom || pTop || pLeft || pRight) )
            return;

        OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();

        // Check if there is a shadow item
        const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
        if ( pItem )
        {
            const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
            aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
        }

        // By top margin, impl_borders() means the distance between the top of the page and the header frame.
        editeng::WordPageMargins aMargins = m_pageMargins;
        HdFtDistanceGlue aGlue(pFormat->GetAttrSet());
        if (aGlue.HasHeader())
            aMargins.nTop = aGlue.dyaHdrTop;
        // Ditto for bottom margin.
        if (aGlue.HasFooter())
            aMargins.nBottom = aGlue.dyaHdrBottom;

        aOutputBorderOptions.pDistances = std::make_shared<editeng::WordBorderDistances>();
        editeng::BorderDistancesToWord(rBox, aMargins, *aOutputBorderOptions.pDistances);

        // All distances are relative to the text margins
        m_pSerializer->startElementNS(XML_w, XML_pgBorders,
            FSNS(XML_w, XML_display), "allPages",
            FSNS(XML_w, XML_offsetFrom), aOutputBorderOptions.pDistances->bFromEdge ? "page" : "text",
            FSEND);

        std::map<SvxBoxItemLine, css::table::BorderLine2> aEmptyMap; // empty styles map
        impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aEmptyMap );

        m_pSerializer->endElementNS( XML_w, XML_pgBorders );

    }

    void DocxAttributeOutput::SectionBiDi( bool bBiDi )
    {
        if ( bBiDi )
            m_pSerializer->singleElementNS( XML_w, XML_bidi, FSEND );
    }

    static OString impl_NumberingType( sal_uInt16 nNumberingType )
    {
        OString aType;

        switch ( nNumberingType )
        {
            case SVX_NUM_CHARS_UPPER_LETTER:
            case SVX_NUM_CHARS_UPPER_LETTER_N:  aType = "upperLetter"; break;
            case SVX_NUM_CHARS_LOWER_LETTER:
            case SVX_NUM_CHARS_LOWER_LETTER_N:  aType = "lowerLetter"; break;
            case SVX_NUM_ROMAN_UPPER:           aType = "upperRoman";  break;
            case SVX_NUM_ROMAN_LOWER:           aType = "lowerRoman";  break;

            case SVX_NUM_ARABIC:                aType = "decimal";     break;

            case SVX_NUM_BITMAP:
            case SVX_NUM_CHAR_SPECIAL:          aType = "bullet";      break;

            case style::NumberingType::CHARS_HEBREW: aType = "hebrew2"; break;
            case style::NumberingType::NUMBER_HEBREW: aType = "hebrew1"; break;

            default:                            aType = "none";        break;
        }

        return aType;
    }

    // Converting Level Numbering Format Code to string
    static OString impl_LevelNFC( sal_uInt16 nNumberingType , const SfxItemSet *pOutSet)
    {
        OString aType;

        switch ( nNumberingType )
        {
            case style::NumberingType::CHARS_UPPER_LETTER:
            case style::NumberingType::CHARS_UPPER_LETTER_N:
            case style::NumberingType::CHARS_LOWER_LETTER:
            case style::NumberingType::CHARS_LOWER_LETTER_N:
            case style::NumberingType::ROMAN_UPPER:
            case style::NumberingType::ROMAN_LOWER:
            case style::NumberingType::ARABIC:
            case style::NumberingType::BITMAP:
            case style::NumberingType::CHAR_SPECIAL:
            case style::NumberingType::CHARS_HEBREW:
            case style::NumberingType::NUMBER_HEBREW:
            case style::NumberingType::NUMBER_NONE:
                return impl_NumberingType( nNumberingType );
            case style::NumberingType::FULLWIDTH_ARABIC: aType="decimalFullWidth"; break;
            case style::NumberingType::TIAN_GAN_ZH: aType="ideographTraditional"; break;
            case style::NumberingType::DI_ZI_ZH: aType="ideographZodiac"; break;
            case style::NumberingType::NUMBER_LOWER_ZH:
                aType="taiwaneseCountingThousand";
                if (pOutSet) {
                    const SvxLanguageItem rLang = pOutSet->Get( RES_CHRATR_CJK_LANGUAGE);
                    const LanguageType eLang = rLang.GetLanguage();

                    if (LANGUAGE_CHINESE_SIMPLIFIED == eLang) {
                        aType="chineseCountingThousand";
                    }
                }
            break;
            case style::NumberingType::NUMBER_UPPER_ZH_TW: aType="ideographLegalTraditional";break;
            case style::NumberingType::NUMBER_UPPER_ZH: aType="chineseLegalSimplified"; break;
            case style::NumberingType::NUMBER_TRADITIONAL_JA: aType="japaneseLegal";break;
            case style::NumberingType::AIU_FULLWIDTH_JA: aType="aiueoFullWidth";break;
            case style::NumberingType::AIU_HALFWIDTH_JA: aType="aiueo";break;
            case style::NumberingType::IROHA_FULLWIDTH_JA: aType="iroha";break;
            case style::NumberingType::IROHA_HALFWIDTH_JA: aType="irohaFullWidth";break;
            case style::NumberingType::HANGUL_SYLLABLE_KO: aType="ganada";break;
            case style::NumberingType::HANGUL_JAMO_KO: aType="chosung";break;
            case style::NumberingType::NUMBER_HANGUL_KO: aType="koreanDigital";break;
            case style::NumberingType::NUMBER_UPPER_KO: aType="koreanLegal"; break;
            case style::NumberingType::CIRCLE_NUMBER: aType="decimalEnclosedCircle"; break;
            case style::NumberingType::CHARS_ARABIC: aType="arabicAlpha"; break;
            case style::NumberingType::CHARS_THAI: aType="thaiLetters"; break;
            case style::NumberingType::CHARS_PERSIAN: aType="hindiVowels"; break;
            case style::NumberingType::TEXT_NUMBER: aType="ordinal"; break;
            case style::NumberingType::TEXT_CARDINAL: aType="cardinalText"; break;
            case style::NumberingType::TEXT_ORDINAL: aType="ordinalText"; break;
    /*
            Fallback the rest to decimal.
            case style::NumberingType::NATIVE_NUMBERING:
            case style::NumberingType::HANGUL_CIRCLED_JAMO_KO:
            case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO:
            case style::NumberingType::CHARS_GREEK_UPPER_LETTER:
            case style::NumberingType::CHARS_GREEK_LOWER_LETTER:
            case style::NumberingType::PAGE_DESCRIPTOR:
            case style::NumberingType::TRANSLITERATION:
            case style::NumberingType::CHARS_NEPALI:
            case style::NumberingType::CHARS_KHMER:
            case style::NumberingType::CHARS_LAO:
            case style::NumberingType::CHARS_TIBETAN:
            case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_BG:
            case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_BG:
            case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_BG:
            case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_BG:
            case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU:
            case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU:
            case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_RU:
            case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_RU:
            case style::NumberingType::CHARS_MYANMAR:
            case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_SR:
            case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_SR:
            case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_SR:
            case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_SR:
    */
            default:
                aType = "decimal";        break;
        }
        return aType;
    }

    void DocxAttributeOutput::SectionPageNumbering( sal_uInt16 nNumType, const ::boost::optional<sal_uInt16>& oPageRestartNumber )
    {
        // FIXME Not called properly with page styles like "First Page"

        FastAttributeList* pAttr = FastSerializerHelper::createAttrList();

        // boost::none means no restart: then don't output that attribute if it is negative
        if ( oPageRestartNumber )
           pAttr->add( FSNS( XML_w, XML_start ), OString::number( oPageRestartNumber.get() ) );

        // nNumType corresponds to w:fmt. See WW8Export::GetNumId() for more precisions
        OString aFormat( impl_NumberingType( nNumType ) );
        if ( !aFormat.isEmpty() )
            pAttr->add( FSNS( XML_w, XML_fmt ), aFormat.getStr() );

        XFastAttributeListRef xAttrs( pAttr );
        m_pSerializer->singleElementNS( XML_w, XML_pgNumType, xAttrs );

        // see 2.6.12 pgNumType (Page Numbering Settings)
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::SectionPageNumbering()" );
    }

    void DocxAttributeOutput::SectionType( sal_uInt8 nBreakCode )
    {
        /*  break code:   0 No break, 1 New column
            2 New page, 3 Even page, 4 Odd page
            */
        const char* pType = nullptr;
        switch ( nBreakCode )
        {
            case 1:  pType = "nextColumn"; break;
            case 2:  pType = "nextPage";   break;
            case 3:  pType = "evenPage";   break;
            case 4:  pType = "oddPage";    break;
            default: pType = "continuous"; break;
        }

        if ( pType )
            m_pSerializer->singleElementNS( XML_w, XML_type,
                    FSNS( XML_w, XML_val ), pType,
                    FSEND );
    }

    void DocxAttributeOutput::TextVerticalAdjustment( const drawing::TextVerticalAdjust nVA )
    {
        switch( nVA )
        {
            case drawing::TextVerticalAdjust_CENTER:
                m_pSerializer->singleElementNS( XML_w, XML_vAlign,
                    FSNS( XML_w, XML_val ), "center", FSEND );
                break;
            case drawing::TextVerticalAdjust_BOTTOM:
                m_pSerializer->singleElementNS( XML_w, XML_vAlign,
                    FSNS( XML_w, XML_val ), "bottom", FSEND );
                break;
            case drawing::TextVerticalAdjust_BLOCK:  //justify
                m_pSerializer->singleElementNS( XML_w, XML_vAlign,
                    FSNS( XML_w, XML_val ), "both", FSEND );
                break;
            default:
                break;
        }
    }

    void DocxAttributeOutput::StartFont( const OUString& rFamilyName ) const
    {
        m_pSerializer->startElementNS( XML_w, XML_font,
                FSNS( XML_w, XML_name ), OUStringToOString( rFamilyName, RTL_TEXTENCODING_UTF8 ).getStr(),
                FSEND );
    }

    void DocxAttributeOutput::EndFont() const
    {
        m_pSerializer->endElementNS( XML_w, XML_font );
    }

    void DocxAttributeOutput::FontAlternateName( const OUString& rName ) const
    {
        m_pSerializer->singleElementNS( XML_w, XML_altName,
                FSNS( XML_w, XML_val ), OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ).getStr(),
                FSEND );
    }

    void DocxAttributeOutput::FontCharset( sal_uInt8 nCharSet, rtl_TextEncoding nEncoding ) const
    {
        FastAttributeList* pAttr = FastSerializerHelper::createAttrList();

        OString aCharSet( OString::number( nCharSet, 16 ) );
        if ( aCharSet.getLength() == 1 )
            aCharSet = OString( "0" ) + aCharSet;
        pAttr->add( FSNS( XML_w, XML_val ), aCharSet.getStr());

        if( GetExport().GetFilter().getVersion( ) != oox::core::ECMA_DIALECT )
        {
            if( const char* charset = rtl_getMimeCharsetFromTextEncoding( nEncoding ))
                pAttr->add( FSNS( XML_w, XML_characterSet ), charset );
        }

        m_pSerializer->singleElementNS( XML_w, XML_charset, XFastAttributeListRef( pAttr ));
    }

    void DocxAttributeOutput::FontFamilyType( FontFamily eFamily ) const
    {
        const char *pFamily = nullptr;
        switch ( eFamily )
        {
            case FAMILY_ROMAN:      pFamily = "roman"; break;
            case FAMILY_SWISS:      pFamily = "swiss"; break;
            case FAMILY_MODERN:     pFamily = "modern"; break;
            case FAMILY_SCRIPT:     pFamily = "script"; break;
            case FAMILY_DECORATIVE: pFamily = "decorative"; break;
            default:                pFamily = "auto"; break; // no font family
        }

        if ( pFamily )
            m_pSerializer->singleElementNS( XML_w, XML_family,
                    FSNS( XML_w, XML_val ), pFamily,
                    FSEND );
    }

    void DocxAttributeOutput::FontPitchType( FontPitch ePitch ) const
    {
        const char *pPitch = nullptr;
        switch ( ePitch )
        {
            case PITCH_VARIABLE: pPitch = "variable"; break;
            case PITCH_FIXED:    pPitch = "fixed"; break;
            default:             pPitch = "default"; break; // no info about the pitch
        }

        if ( pPitch )
            m_pSerializer->singleElementNS( XML_w, XML_pitch,
                    FSNS( XML_w, XML_val ), pPitch,
                    FSEND );
    }

    void DocxAttributeOutput::EmbedFont( const OUString& name, FontFamily family, FontPitch pitch )
    {
        if( !m_rExport.m_pDoc->getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS ))
            return; // no font embedding with this document
        EmbedFontStyle( name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_NORMAL, pitch );
        EmbedFontStyle( name, XML_embedBold, family, ITALIC_NONE, WEIGHT_BOLD, pitch );
        EmbedFontStyle( name, XML_embedItalic, family, ITALIC_NORMAL, WEIGHT_NORMAL, pitch );
        EmbedFontStyle( name, XML_embedBoldItalic, family, ITALIC_NORMAL, WEIGHT_BOLD, pitch );
    }

    static inline char toHexChar( int value )
    {
        return value >= 10 ? value + 'A' - 10 : value + '0';
    }

    void DocxAttributeOutput::EmbedFontStyle( const OUString& name, int tag, FontFamily family, FontItalic italic,
        FontWeight weight, FontPitch pitch )
    {
        // Embed font if at least viewing is allowed (in which case the opening app must check
        // the font license rights too and open either read-only or not use the font for editing).
        OUString fontUrl = EmbeddedFontsHelper::fontFileUrl( name, family, italic, weight, pitch,
            EmbeddedFontsHelper::FontRights::ViewingAllowed );
        if( fontUrl.isEmpty())
            return;
        // TODO IDocumentSettingAccess::EMBED_SYSTEM_FONTS
        if( !fontFilesMap.count( fontUrl ))
        {
            osl::File file( fontUrl );
            if( file.open( osl_File_OpenFlag_Read ) != osl::File::E_None )
                return;
            uno::Reference< css::io::XOutputStream > xOutStream = m_rExport.GetFilter().openFragmentStream(
                "word/fonts/font" + OUString::number(m_nextFontId) + ".odttf",
                "application/vnd.openxmlformats-officedocument.obfuscatedFont" );
            // Not much point in trying hard with the obfuscation key, whoever reads the spec can read the font anyway,
            // so just alter the first and last part of the key.
            char fontKeyStr[] = "{00014A78-CABC-4EF0-12AC-5CD89AEFDE00}";
            sal_uInt8 fontKey[ 16 ] = { 0, 0xDE, 0xEF, 0x9A, 0xD8, 0x5C, 0xAC, 0x12, 0xF0, 0x4E,
                0xBC, 0xCA, 0x78, 0x4A, 0x01, 0 };
            fontKey[ 0 ] = fontKey[ 15 ] = m_nextFontId % 256;
            fontKeyStr[ 1 ] = fontKeyStr[ 35 ] = toHexChar(( m_nextFontId % 256 ) / 16 );
            fontKeyStr[ 2 ] = fontKeyStr[ 36 ] = toHexChar(( m_nextFontId % 256 ) % 16 );
            char buffer[ 4096 ];
            sal_uInt64 readSize;
            file.read( buffer, 32, readSize );
            if( readSize < 32 )
            {
                SAL_WARN( "sw.ww8", "Font file size too small (" << fontUrl << ")" );
                xOutStream->closeOutput();
                return;
            }
            for( int i = 0;
                 i < 16;
                 ++i )
            {
                buffer[ i ] ^= fontKey[ i ];
                buffer[ i + 16 ] ^= fontKey[ i ];
            }
            xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), 32 ));
            for(;;)
            {
                sal_Bool eof;
                if( file.isEndOfFile( &eof ) != osl::File::E_None )
                {
                    SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
                    xOutStream->closeOutput();
                    return;
                }
                if( eof )
                    break;
                if( file.read( buffer, 4096, readSize ) != osl::File::E_None )
                {
                    SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
                    xOutStream->closeOutput();
                    return;
                }
                if( readSize == 0 )
                    break;
                xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), readSize ));
            }
            xOutStream->closeOutput();
            OString relId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
                oox::getRelationship(Relationship::FONT),
                "fonts/font" + OUString::number( m_nextFontId ) + ".odttf" ), RTL_TEXTENCODING_UTF8 );
            EmbeddedFontRef ref;
            ref.relId = relId;
            ref.fontKey = fontKeyStr;
            fontFilesMap[ fontUrl ] = ref;
            ++m_nextFontId;
        }
        m_pSerializer->singleElementNS( XML_w, tag,
            FSNS( XML_r, XML_id ), fontFilesMap[ fontUrl ].relId,
            FSNS( XML_w, XML_fontKey ), fontFilesMap[ fontUrl ].fontKey,
            FSEND );
    }

    OString DocxAttributeOutput::TransHighlightColor( sal_uInt8 nIco )
    {
        switch (nIco)
        {
            case 1: return OString("black"); break;
            case 2: return OString("blue"); break;
            case 3: return OString("cyan"); break;
            case 4: return OString("green"); break;
            case 5: return OString("magenta"); break;
            case 6: return OString("red"); break;
            case 7: return OString("yellow"); break;
            case 8: return OString("white"); break;
            case 9: return OString("darkBlue"); break;
            case 10: return OString("darkCyan"); break;
            case 11: return OString("darkGreen"); break;
            case 12: return OString("darkMagenta"); break;
            case 13: return OString("darkRed"); break;
            case 14: return OString("darkYellow"); break;
            case 15: return OString("darkGray"); break;
            case 16: return OString("lightGray"); break;
            default: return OString(); break;
        }
    }

    void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule &rRule )
    {
        // nId is the same both for abstract numbering definition as well as the
        // numbering definition itself
        // TODO check that this is actually true & fix if not ;-)
        OString aId( OString::number( nId ) );

        m_pSerializer->startElementNS( XML_w, XML_num,
                FSNS( XML_w, XML_numId ), aId.getStr(),
                FSEND );

        m_pSerializer->singleElementNS( XML_w, XML_abstractNumId,
                FSNS( XML_w, XML_val ), aId.getStr(),
                FSEND );

    #if OSL_DEBUG_LEVEL > 1
        // TODO ww8 version writes this, anything to do about it here?
        if ( rRule.IsContinusNum() )
            SAL_INFO("sw", "TODO DocxAttributeOutput::NumberingDefinition()" );
    #else
        (void) rRule; // to quiet the warning...
    #endif

        m_pSerializer->endElementNS( XML_w, XML_num );
    }

    void DocxAttributeOutput::StartAbstractNumbering( sal_uInt16 nId )
    {
        const SwNumRule* pRule = (*m_rExport.m_pUsedNumTable)[nId - 1];
        m_bExportingOutline = pRule && pRule->IsOutlineRule();
        m_pSerializer->startElementNS( XML_w, XML_abstractNum,
                FSNS( XML_w, XML_abstractNumId ), OString::number( nId ).getStr(),
                FSEND );
    }

    void DocxAttributeOutput::EndAbstractNumbering()
    {
        m_pSerializer->endElementNS( XML_w, XML_abstractNum );
    }

    void DocxAttributeOutput::NumberingLevel( sal_uInt8 nLevel,
            sal_uInt16 nStart,
            sal_uInt16 nNumberingType,
            SvxAdjust eAdjust,
            const sal_uInt8 * /*pNumLvlPos*/,
            sal_uInt8 nFollow,
            const wwFont *pFont,
            const SfxItemSet *pOutSet,
            sal_Int16 nIndentAt,
            sal_Int16 nFirstLineIndex,
            sal_Int16 nListTabPos,
            const OUString &rNumberingString,
            const SvxBrushItem* pBrush)
    {
        m_pSerializer->startElementNS( XML_w, XML_lvl,
                FSNS( XML_w, XML_ilvl ), OString::number( nLevel ).getStr(),
                FSEND );

        // start with the nStart value. Do not write w:start if Numbered Lists
        // starts from zero.As it's an optional parameter.
        // refer ECMA 376 Second edition Part-1
        if(!(0 == nLevel && 0 == nStart))
        {
            m_pSerializer->singleElementNS( XML_w, XML_start,
                    FSNS( XML_w, XML_val ), OString::number( nStart ).getStr(),
                    FSEND );
        }

        if (m_bExportingOutline)
        {
            sal_uInt16 nId = m_rExport.m_pStyles->GetHeadingParagraphStyleId( nLevel );
            if ( nId != SAL_MAX_UINT16 )
                m_pSerializer->singleElementNS( XML_w, XML_pStyle ,
                    FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nId).getStr(),
                    FSEND );
        }
        // format
        OString aFormat( impl_LevelNFC( nNumberingType ,pOutSet) );

        if ( !aFormat.isEmpty() )
            m_pSerializer->singleElementNS( XML_w, XML_numFmt,
                    FSNS( XML_w, XML_val ), aFormat.getStr(),
                    FSEND );

        // suffix
        const char *pSuffix = nullptr;
        switch ( nFollow )
        {
            case 1:  pSuffix = "space";   break;
            case 2:  pSuffix = "nothing"; break;
            default: /*pSuffix = "tab";*/ break;
        }
        if ( pSuffix )
            m_pSerializer->singleElementNS( XML_w, XML_suff,
                    FSNS( XML_w, XML_val ), pSuffix,
                    FSEND );

        // text
        OUStringBuffer aBuffer( rNumberingString.getLength() + WW8ListManager::nMaxLevel );

        const sal_Unicode *pPrev = rNumberingString.getStr();
        const sal_Unicode *pIt = rNumberingString.getStr();
        while ( pIt < rNumberingString.getStr() + rNumberingString.getLength() )
        {
            // convert the level values to %NUMBER form
            // (we don't use pNumLvlPos at all)
            // FIXME so far we support the ww8 limit of levels only
            if ( *pIt < sal_Unicode( WW8ListManager::nMaxLevel ) )
            {
                aBuffer.append( pPrev, pIt - pPrev );
                aBuffer.append( '%' );
                aBuffer.append( OUString::number( sal_Int32( *pIt ) + 1 ) );

                pPrev = pIt + 1;
            }
            ++pIt;
        }
        if ( pPrev < pIt )
            aBuffer.append( pPrev, pIt - pPrev );

        // If bullet char is empty, set lvlText as empty
        if ( rNumberingString == OUStringLiteral1(0) && nNumberingType == SVX_NUM_CHAR_SPECIAL )
        {
            m_pSerializer->singleElementNS( XML_w, XML_lvlText, FSNS( XML_w, XML_val ), "", FSEND );
        }
        else
        {
            // Writer's "zero width space" suffix is necessary, so that LabelFollowedBy shows up, but Word doesn't require that.
            OUString aLevelText = aBuffer.makeStringAndClear();
            static OUString aZeroWidthSpace(u'\x200B');
            if (aLevelText == aZeroWidthSpace)
                aLevelText.clear();
            m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), aLevelText.toUtf8(), FSEND);
        }

        // bullet
        if (nNumberingType == SVX_NUM_BITMAP && pBrush)
        {
            int nIndex = m_rExport.GetGrfIndex(*pBrush);
            if (nIndex != -1)
            {
                m_pSerializer->singleElementNS(XML_w, XML_lvlPicBulletId,
                        FSNS(XML_w, XML_val), OString::number(nIndex).getStr(),
                        FSEND);
            }
        }

        // justification
        const char *pJc;
        bool ecmaDialect = ( m_rExport.GetFilter().getVersion() == oox::core::ECMA_DIALECT );
        switch ( eAdjust )
        {
            case SvxAdjust::Center: pJc = "center"; break;
            case SvxAdjust::Right:  pJc = !ecmaDialect ? "end" : "right";  break;
            default:                pJc = !ecmaDialect ? "start" : "left";   break;
        }
        m_pSerializer->singleElementNS( XML_w, XML_lvlJc,
                FSNS( XML_w, XML_val ), pJc,
                FSEND );

        // indentation
        m_pSerializer->startElementNS( XML_w, XML_pPr, FSEND );
        if( nListTabPos != 0 )
        {
            m_pSerializer->startElementNS( XML_w, XML_tabs, FSEND );
            m_pSerializer->singleElementNS( XML_w, XML_tab,
                    FSNS( XML_w, XML_val ), "num",
                    FSNS( XML_w, XML_pos ), OString::number( nListTabPos ).getStr(),
                    FSEND );
            m_pSerializer->endElementNS( XML_w, XML_tabs );
        }

        sal_Int32 nToken = ecmaDialect ? XML_left : XML_start;
        sal_Int32 nIndentToken = nFirstLineIndex > 0 ? XML_firstLine : XML_hanging;
        m_pSerializer->singleElementNS( XML_w, XML_ind,
                FSNS( XML_w, nToken ), OString::number( nIndentAt ).getStr(),
                FSNS( XML_w, nIndentToken ), OString::number( abs(nFirstLineIndex) ).getStr(),
                FSEND );
        m_pSerializer->endElementNS( XML_w, XML_pPr );

        // font
        if ( pOutSet )
        {
            m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND );

            if ( pFont )
            {
                GetExport().GetId( *pFont ); // ensure font info is written to fontTable.xml
                OString aFamilyName( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
                m_pSerializer->singleElementNS( XML_w, XML_rFonts,
                        FSNS( XML_w, XML_ascii ), aFamilyName.getStr(),
                        FSNS( XML_w, XML_hAnsi ), aFamilyName.getStr(),
                        FSNS( XML_w, XML_cs ), aFamilyName.getStr(),
                        FSNS( XML_w, XML_hint ), "default",
                        FSEND );
            }
            m_rExport.OutputItemSet( *pOutSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );

            WriteCollectedRunProperties();

            m_pSerializer->endElementNS( XML_w, XML_rPr );
        }

        // TODO anything to do about nListTabPos?

        m_pSerializer->endElementNS( XML_w, XML_lvl );
    }

    void DocxAttributeOutput::CharCaseMap( const SvxCaseMapItem& rCaseMap )
    {
        switch ( rCaseMap.GetValue() )
        {
            case SvxCaseMap::SmallCaps:
                m_pSerializer->singleElementNS( XML_w, XML_smallCaps, FSEND );
                break;
            case SvxCaseMap::Uppercase:
                m_pSerializer->singleElementNS( XML_w, XML_caps, FSEND );
                break;
            default: // Something that ooxml does not support
                m_pSerializer->singleElementNS( XML_w, XML_smallCaps, FSNS( XML_w, XML_val ), "false", FSEND );
                m_pSerializer->singleElementNS( XML_w, XML_caps, FSNS( XML_w, XML_val ), "false", FSEND );
                break;
        }
    }

    void DocxAttributeOutput::CharColor( const SvxColorItem& rColor )
    {
        const Color aColor( rColor.GetValue() );
        OString aColorString;

        aColorString = msfilter::util::ConvertColor( aColor );

        const char* pExistingValue(nullptr);
        if (m_pColorAttrList.is() && m_pColorAttrList->getAsChar(FSNS(XML_w, XML_val), pExistingValue))
        {
            assert(aColorString.equalsL(pExistingValue, rtl_str_getLength(pExistingValue)));
            return;
        }

        AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_val ), aColorString.getStr() );
    }

    void DocxAttributeOutput::CharContour( const SvxContourItem& rContour )
    {
        if ( rContour.GetValue() )
            m_pSerializer->singleElementNS( XML_w, XML_outline, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_outline, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharCrossedOut( const SvxCrossedOutItem& rCrossedOut )
    {
        switch ( rCrossedOut.GetStrikeout() )
        {
            case STRIKEOUT_DOUBLE:
                m_pSerializer->singleElementNS( XML_w, XML_dstrike, FSEND );
                break;
            case STRIKEOUT_NONE:
                m_pSerializer->singleElementNS( XML_w, XML_dstrike, FSNS( XML_w, XML_val ), "false", FSEND );
                m_pSerializer->singleElementNS( XML_w, XML_strike, FSNS( XML_w, XML_val ), "false", FSEND );
                break;
            default:
                m_pSerializer->singleElementNS( XML_w, XML_strike, FSEND );
                break;
        }
    }

    void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement )
    {
        OString sIss;
        short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight();
        if ( !nEsc )
        {
            sIss = OString( "baseline" );
            nEsc = 0;
            nProp = 100;
        }
        else if ( DFLT_ESC_PROP == nProp )
        {
            if ( DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc )
                sIss = OString( "subscript" );
            else if ( DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc )
                sIss = OString( "superscript" );
        }

        if ( !sIss.isEmpty() )
            m_pSerializer->singleElementNS( XML_w, XML_vertAlign,
               FSNS( XML_w, XML_val ), sIss.getStr(), FSEND );

        const SvxFontHeightItem& rItem = m_rExport.GetItem(RES_CHRATR_FONTSIZE);
        if (sIss.isEmpty() || sIss.match(OString("baseline")))
        {
            long nHeight = rItem.GetHeight();
            OString sPos = OString::number( ( nHeight * nEsc + 500 ) / 1000 );
            m_pSerializer->singleElementNS( XML_w, XML_position,
                    FSNS( XML_w, XML_val ), sPos.getStr( ), FSEND );

            if( ( 100 != nProp || sIss.match( OString( "baseline" ) ) ) && !m_rExport.m_bFontSizeWritten )
            {
                OString sSize = OString::number( ( nHeight * nProp + 500 ) / 1000 );
                    m_pSerializer->singleElementNS( XML_w, XML_sz,
                        FSNS( XML_w, XML_val ), sSize.getStr( ), FSEND );
            }
        }
    }

    void DocxAttributeOutput::CharFont( const SvxFontItem& rFont)
    {
        GetExport().GetId( rFont ); // ensure font info is written to fontTable.xml
        const OUString& sFontName(rFont.GetFamilyName());
        const OString sFontNameUtf8 = OUStringToOString(sFontName, RTL_TEXTENCODING_UTF8);
        if (!sFontNameUtf8.isEmpty())
        {
            if (m_pFontsAttrList &&
                (   m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_ascii )) ||
                    m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_hAnsi ))    )
                )
            {
                // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
                // that all sub runs of the field will have correct font inside.
                // For DOCX we should do not add the same font information twice in the same node
                return;
            }

            AddToAttrList( m_pFontsAttrList, 2,
                FSNS( XML_w, XML_ascii ), sFontNameUtf8.getStr(),
                FSNS( XML_w, XML_hAnsi ), sFontNameUtf8.getStr() );
        }
    }

    void DocxAttributeOutput::CharFontSize( const SvxFontHeightItem& rFontSize)
    {
        OString fontSize = OString::number( ( rFontSize.GetHeight() + 5 ) / 10 );

        switch ( rFontSize.Which() )
        {
            case RES_CHRATR_FONTSIZE:
            case RES_CHRATR_CJK_FONTSIZE:
                m_pSerializer->singleElementNS( XML_w, XML_sz, FSNS( XML_w, XML_val ), fontSize.getStr(), FSEND );
                break;
            case RES_CHRATR_CTL_FONTSIZE:
                m_pSerializer->singleElementNS( XML_w, XML_szCs, FSNS( XML_w, XML_val ), fontSize.getStr(), FSEND );
                break;
        }
    }

    void DocxAttributeOutput::CharKerning( const SvxKerningItem& rKerning )
    {
        OString aKerning = OString::number(  rKerning.GetValue() );
        m_pSerializer->singleElementNS( XML_w, XML_spacing, FSNS(XML_w, XML_val), aKerning.getStr(), FSEND );
    }

    void DocxAttributeOutput::CharLanguage( const SvxLanguageItem& rLanguage )
    {
        OString aLanguageCode( OUStringToOString(
                    LanguageTag( rLanguage.GetLanguage()).getBcp47MS(),
                    RTL_TEXTENCODING_UTF8));

        switch ( rLanguage.Which() )
        {
            case RES_CHRATR_LANGUAGE:
                AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_val ), aLanguageCode.getStr() );
                break;
            case RES_CHRATR_CJK_LANGUAGE:
                AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_eastAsia ), aLanguageCode.getStr() );
                break;
            case RES_CHRATR_CTL_LANGUAGE:
                AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_bidi ), aLanguageCode.getStr() );
                break;
        }
    }

    void DocxAttributeOutput::CharPosture( const SvxPostureItem& rPosture )
    {
        if ( rPosture.GetPosture() != ITALIC_NONE )
            m_pSerializer->singleElementNS( XML_w, XML_i, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_i, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharShadow( const SvxShadowedItem& rShadow )
    {
        if ( rShadow.GetValue() )
            m_pSerializer->singleElementNS( XML_w, XML_shadow, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_shadow, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharUnderline( const SvxUnderlineItem& rUnderline )
    {
        const char *pUnderlineValue;

        switch ( rUnderline.GetLineStyle() )
        {
            case LINESTYLE_SINGLE:         pUnderlineValue = "single";          break;
            case LINESTYLE_BOLD:           pUnderlineValue = "thick";           break;
            case LINESTYLE_DOUBLE:         pUnderlineValue = "double";          break;
            case LINESTYLE_DOTTED:         pUnderlineValue = "dotted";          break;
            case LINESTYLE_DASH:           pUnderlineValue = "dash";            break;
            case LINESTYLE_DASHDOT:        pUnderlineValue = "dotDash";         break;
            case LINESTYLE_DASHDOTDOT:     pUnderlineValue = "dotDotDash";      break;
            case LINESTYLE_WAVE:           pUnderlineValue = "wave";            break;
            case LINESTYLE_BOLDDOTTED:     pUnderlineValue = "dottedHeavy";     break;
            case LINESTYLE_BOLDDASH:       pUnderlineValue = "dashedHeavy";     break;
            case LINESTYLE_LONGDASH:       pUnderlineValue = "dashLongHeavy";   break;
            case LINESTYLE_BOLDLONGDASH:   pUnderlineValue = "dashLongHeavy";   break;
            case LINESTYLE_BOLDDASHDOT:    pUnderlineValue = "dashDotHeavy";    break;
            case LINESTYLE_BOLDDASHDOTDOT: pUnderlineValue = "dashDotDotHeavy"; break;
            case LINESTYLE_BOLDWAVE:       pUnderlineValue = "wavyHeavy";       break;
            case LINESTYLE_DOUBLEWAVE:     pUnderlineValue = "wavyDouble";      break;
            case LINESTYLE_NONE:           // fall through
            default:                       pUnderlineValue = "none";            break;
        }

        Color aUnderlineColor = rUnderline.GetColor();
        bool  bUnderlineHasColor = aUnderlineColor.GetTransparency() == 0;
        if (bUnderlineHasColor)
        {
            // Underline has a color
            m_pSerializer->singleElementNS( XML_w, XML_u,
                                            FSNS( XML_w, XML_val ), pUnderlineValue,
                                            FSNS( XML_w, XML_color ), msfilter::util::ConvertColor( aUnderlineColor ).getStr(),
                                        FSEND );
        }
        else
        {
            // Underline has no color
            m_pSerializer->singleElementNS( XML_w, XML_u, FSNS( XML_w, XML_val ), pUnderlineValue, FSEND );
        }
    }

    void DocxAttributeOutput::CharWeight( const SvxWeightItem& rWeight )
    {
        if ( rWeight.GetWeight() == WEIGHT_BOLD )
            m_pSerializer->singleElementNS( XML_w, XML_b, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_b, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharAutoKern( const SvxAutoKernItem& rAutoKern )
    {
        // auto kerning is bound to a minimum font size in Word - but is just a boolean in Writer :-(
        // kerning is based on half-point sizes, so 2 enables kerning for fontsize 1pt or higher. (1 is treated as size 12, and 0 is treated as disabled.)
        const OString sFontSize = OString::number( static_cast<sal_uInt32>(rAutoKern.GetValue()) * 2 );
        m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS( XML_w, XML_val ), sFontSize.getStr(), FSEND );
    }

    void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
    {
        if ( rBlink.GetValue() )
            m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS( XML_w, XML_val ), "blinkBackground", FSEND );
        else
            m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS( XML_w, XML_val ), "none", FSEND );
    }

    #define MSWORD_CH_SHADING_FILL "FFFFFF" // The attribute w:fill of w:shd, for MS-Word's character shading,
    #define MSWORD_CH_SHADING_COLOR "auto" // The attribute w:color of w:shd, for MS-Word's character shading,
    #define MSWORD_CH_SHADING_VAL "pct15" // The attribute w:value of w:shd, for MS-Word's character shading,

    void DocxAttributeOutput::CharBackground( const SvxBrushItem& rBrush )
    {
        // Check if the brush shading pattern is 'PCT15'. If so - write it back to the DOCX
        if (rBrush.GetShadingValue() == ShadingPattern::PCT15)
        {
            m_pSerializer->singleElementNS( XML_w, XML_shd,
                FSNS( XML_w, XML_val ), MSWORD_CH_SHADING_VAL,
                FSNS( XML_w, XML_color ), MSWORD_CH_SHADING_COLOR,
                FSNS( XML_w, XML_fill ), MSWORD_CH_SHADING_FILL,
                FSEND );
        }
        else
        {
            m_pSerializer->singleElementNS( XML_w, XML_shd,
                FSNS( XML_w, XML_fill ), msfilter::util::ConvertColor( rBrush.GetColor() ).getStr(),
                FSNS( XML_w, XML_val ), "clear",
                FSEND );
        }
    }

    void DocxAttributeOutput::CharFontCJK( const SvxFontItem& rFont )
    {
        if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_eastAsia)))
        {
            // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
            // that all sub runs of the field will have correct font inside.
            // For DOCX we should do not add the same font information twice in the same node
            return;
        }

        const OUString& sFontName(rFont.GetFamilyName());
        OString sFontNameUtf8 = OUStringToOString(sFontName, RTL_TEXTENCODING_UTF8);
        AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsia ), sFontNameUtf8.getStr() );
    }

    void DocxAttributeOutput::CharPostureCJK( const SvxPostureItem& rPosture )
    {
        if ( rPosture.GetPosture() != ITALIC_NONE )
            m_pSerializer->singleElementNS( XML_w, XML_i, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_i, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharWeightCJK( const SvxWeightItem& rWeight )
    {
        if ( rWeight.GetWeight() == WEIGHT_BOLD )
            m_pSerializer->singleElementNS( XML_w, XML_b, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_b, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharFontCTL( const SvxFontItem& rFont )
    {
        if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_cs)))
        {
            // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
            // that all sub runs of the field will have correct font inside.
            // For DOCX we should do not add the same font information twice in the same node
            return;
        }

        const OUString& sFontName(rFont.GetFamilyName());
        OString sFontNameUtf8 = OUStringToOString(sFontName, RTL_TEXTENCODING_UTF8);
        AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cs ), sFontNameUtf8.getStr() );
    }

    void DocxAttributeOutput::CharPostureCTL( const SvxPostureItem& rPosture)
    {
        if ( rPosture.GetPosture() != ITALIC_NONE )
            m_pSerializer->singleElementNS( XML_w, XML_iCs, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_iCs, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharWeightCTL( const SvxWeightItem& rWeight )
    {
        if ( rWeight.GetWeight() == WEIGHT_BOLD )
            m_pSerializer->singleElementNS( XML_w, XML_bCs, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_bCs, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharBidiRTL( const SfxPoolItem& )
    {
    }

    void DocxAttributeOutput::CharIdctHint( const SfxPoolItem& )
    {
    }

    void DocxAttributeOutput::CharRotate( const SvxCharRotateItem& rRotate)
    {
        // Not rotated or we the rotation already handled?
        if ( !rRotate.GetValue() || m_bBtLr || m_rExport.SdrExporter().getFrameBtLr())
            return;

        AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vert ), "true" );

        if (rRotate.IsFitToLine())
            AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vertCompress ), "true" );
    }

    void DocxAttributeOutput::CharEmphasisMark( const SvxEmphasisMarkItem& rEmphasisMark )
    {
        const char *pEmphasis;
        const FontEmphasisMark v = rEmphasisMark.GetEmphasisMark();

        if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove))
            pEmphasis = "dot";
        else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove))
            pEmphasis = "comma";
        else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove))
            pEmphasis = "circle";
        else if (v == (FontEmphasisMark::Dot|FontEmphasisMark::PosBelow))
            pEmphasis = "underDot";
        else
            pEmphasis = "none";

        m_pSerializer->singleElementNS( XML_w, XML_em, FSNS( XML_w, XML_val ), pEmphasis, FSEND );
    }

    void DocxAttributeOutput::CharTwoLines( const SvxTwoLinesItem& rTwoLines )
    {
        if ( !rTwoLines.GetValue() )
            return;

        AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combine ), "true" );

        sal_Unicode cStart = rTwoLines.GetStartBracket();
        sal_Unicode cEnd = rTwoLines.GetEndBracket();

        if (!cStart && !cEnd)
            return;

        OString sBracket;
        if ((cStart == '{') || (cEnd == '}'))
            sBracket = const_cast<sal_Char *>("curly");
        else if ((cStart == '<') || (cEnd == '>'))
            sBracket = const_cast<sal_Char *>("angle");
        else if ((cStart == '[') || (cEnd == ']'))
            sBracket = const_cast<sal_Char *>("square");
        else
            sBracket = const_cast<sal_Char *>("round");
        AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combineBrackets ), sBracket.getStr() );
    }

    void DocxAttributeOutput::CharScaleWidth( const SvxCharScaleWidthItem& rScaleWidth )
    {
        // Clamp CharScaleWidth to OOXML limits ([1..600])
        const sal_Int16 nScaleWidth( std::max<sal_Int16>( 1,
            std::min<sal_Int16>( rScaleWidth.GetValue(), 600 ) ) );
        m_pSerializer->singleElementNS( XML_w, XML_w,
            FSNS( XML_w, XML_val ), OString::number( nScaleWidth ).getStr(), FSEND );
    }

    void DocxAttributeOutput::CharRelief( const SvxCharReliefItem& rRelief )
    {
        switch ( rRelief.GetValue() )
        {
            case FontRelief::Embossed:
                m_pSerializer->singleElementNS( XML_w, XML_emboss, FSEND );
                break;
            case FontRelief::Engraved:
                m_pSerializer->singleElementNS( XML_w, XML_imprint, FSEND );
                break;
            default:
                m_pSerializer->singleElementNS( XML_w, XML_emboss, FSNS( XML_w, XML_val ), "false", FSEND );
                m_pSerializer->singleElementNS( XML_w, XML_imprint, FSNS( XML_w, XML_val ), "false", FSEND );
                break;
        }
    }

    void DocxAttributeOutput::CharHidden( const SvxCharHiddenItem& rHidden )
    {
        if ( rHidden.GetValue() )
            m_pSerializer->singleElementNS( XML_w, XML_vanish, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_vanish, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    void DocxAttributeOutput::CharBorder(
        const SvxBorderLine* pAllBorder, const sal_uInt16 nDist, const bool bShadow )
    {
        impl_borderLine( m_pSerializer, XML_bdr, pAllBorder, nDist, bShadow );
    }

    void DocxAttributeOutput::CharHighlight( const SvxBrushItem& rHighlight )
    {
        const OString sColor = TransHighlightColor( msfilter::util::TransColToIco(rHighlight.GetColor()) );
        if ( !sColor.isEmpty() )
        {
            m_pSerializer->singleElementNS( XML_w, XML_highlight,
                FSNS( XML_w, XML_val ), sColor.getStr(), FSEND );
        }
    }

    void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink )
    {
        const SwTextINetFormat* pINetFormat = rLink.GetTextINetFormat();
        const SwCharFormat* pCharFormat = pINetFormat->GetCharFormat();

        OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));

        m_pSerializer->singleElementNS( XML_w, XML_rStyle, FSNS( XML_w, XML_val ), aStyleId.getStr(), FSEND );
    }

    void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat )
    {
        OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat())));

        m_pSerializer->singleElementNS( XML_w, XML_rStyle, FSNS( XML_w, XML_val ), aStyleId.getStr(), FSEND );
    }

    void DocxAttributeOutput::RefField( const SwField&  rField, const OUString& rRef )
    {
        SwFieldIds nType = rField.GetTyp( )->Which( );
        if ( nType == SwFieldIds::GetExp )
        {
            OUString sCmd = FieldString( ww::eREF );
            sCmd += "\"" + rRef + "\" ";

            m_rExport.OutputField( &rField, ww::eREF, sCmd );
        }

        // There is nothing to do here for the set fields
    }

    void DocxAttributeOutput::HiddenField( const SwField& /*rField*/ )
    {
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::HiddenField()" );
    }

    void DocxAttributeOutput::PostitField( const SwField* pField )
    {
        assert( dynamic_cast< const SwPostItField* >( pField ));
        const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField);
        OString aName = OUStringToOString(pPostItField->GetName(), RTL_TEXTENCODING_UTF8);
        sal_Int32 nId = 0;
        std::map< OString, sal_Int32 >::iterator it = m_rOpenedAnnotationMarksIds.find(aName);
        if (it != m_rOpenedAnnotationMarksIds.end())
            // If the postit field has an annotation mark associated, we already have an id.
            nId = it->second;
        else
            // Otherwise get a new one.
            nId = m_nNextAnnotationMarkId++;
        m_postitFields.emplace_back(pPostItField, nId);
    }

    void DocxAttributeOutput::WritePostitFieldReference()
    {
        while( m_postitFieldsMaxId < m_postitFields.size())
        {
            OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second);

            // In case this file is inside annotation marks, we want to write the
            // comment reference after the annotation mark is closed, not here.
            OString idname = OUStringToOString(m_postitFields[m_postitFieldsMaxId].first->GetName(), RTL_TEXTENCODING_UTF8);
            std::map< OString, sal_Int32 >::iterator it = m_rOpenedAnnotationMarksIds.find( idname );
            if ( it == m_rOpenedAnnotationMarksIds.end(  ) )
                m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ), idstr.getStr(), FSEND );
            ++m_postitFieldsMaxId;
        }
    }

    void DocxAttributeOutput::WritePostitFields()
    {
        for (const std::pair<const SwPostItField*,int> & rPair : m_postitFields)
        {
            OString idstr = OString::number( rPair.second);
            const SwPostItField* f = rPair.first;
            m_pSerializer->startElementNS( XML_w, XML_comment, FSNS( XML_w, XML_id ), idstr.getStr(),
                FSNS( XML_w, XML_author ), OUStringToOString( f->GetPar1(), RTL_TEXTENCODING_UTF8 ).getStr(),
                FSNS( XML_w, XML_date ), DateTimeToOString(f->GetDateTime()).getStr(),
                FSNS( XML_w, XML_initials ), OUStringToOString( f->GetInitials(), RTL_TEXTENCODING_UTF8 ).getStr(), FSEND );

            if (f->GetTextObject() != nullptr)
            {
                // richtext
                GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN);
            }
            else
            {
                // just plain text - eg. when the field was created via the
                // .uno:InsertAnnotation API
                m_pSerializer->startElementNS(XML_w, XML_p, FSEND);
                m_pSerializer->startElementNS(XML_w, XML_r, FSEND);
                RunText(f->GetText());
                m_pSerializer->endElementNS(XML_w, XML_r);
                m_pSerializer->endElementNS(XML_w, XML_p);
            }

            m_pSerializer->endElementNS( XML_w, XML_comment );
        }
    }

    bool DocxAttributeOutput::DropdownField( const SwField* pField )
    {
        ww::eField eType = ww::eFORMDROPDOWN;
        OUString sCmd = FieldString( eType  );
        GetExport( ).OutputField( pField, eType, sCmd );

        return false;
    }

    bool DocxAttributeOutput::PlaceholderField( const SwField* pField )
    {
        assert( pendingPlaceholder == nullptr );
        pendingPlaceholder = pField;
        return false; // do not expand
    }

    void DocxAttributeOutput::WritePendingPlaceholder()
    {
        if( pendingPlaceholder == nullptr )
            return;
        const SwField* pField = pendingPlaceholder;
        pendingPlaceholder = nullptr;
        m_pSerializer->startElementNS( XML_w, XML_sdt, FSEND );
        m_pSerializer->startElementNS( XML_w, XML_sdtPr, FSEND );
        if( !pField->GetPar2().isEmpty())
            m_pSerializer->singleElementNS( XML_w, XML_alias,
                FSNS( XML_w, XML_val ), OUStringToOString( pField->GetPar2(), RTL_TEXTENCODING_UTF8 ), FSEND );
        m_pSerializer->singleElementNS( XML_w, XML_temporary, FSEND );
        m_pSerializer->singleElementNS( XML_w, XML_showingPlcHdr, FSEND );
        m_pSerializer->singleElementNS( XML_w, XML_text, FSEND );
        m_pSerializer->endElementNS( XML_w, XML_sdtPr );
        m_pSerializer->startElementNS( XML_w, XML_sdtContent, FSEND );
        m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
        RunText( pField->GetPar1());
        m_pSerializer->endElementNS( XML_w, XML_r );
        m_pSerializer->endElementNS( XML_w, XML_sdtContent );
        m_pSerializer->endElementNS( XML_w, XML_sdt );
    }

    void DocxAttributeOutput::SetField( const SwField& rField, ww::eField eType, const OUString& rCmd )
    {
        // field bookmarks are handled in the EndRun method
        GetExport().OutputField(&rField, eType, rCmd );
    }

    void DocxAttributeOutput::WriteExpand( const SwField* pField )
    {
        // Will be written in the next End Run
        m_rExport.OutputField( pField, ww::eUNKNOWN, OUString() );
    }

    void DocxAttributeOutput::WriteField_Impl( const SwField* pField, ww::eField eType, const OUString& rFieldCmd, FieldFlags nMode )
    {
        if (m_bPreventDoubleFieldsHandling)
            return;

        struct FieldInfos infos;
        if (pField)
            infos.pField = pField->CopyField();
        infos.sCmd = rFieldCmd;
        infos.eType = eType;
        infos.bClose = bool(FieldFlags::Close & nMode);
        infos.bOpen = bool(FieldFlags::Start & nMode);
        m_Fields.push_back( infos );

        if ( pField )
        {
            SwFieldIds nType = pField->GetTyp( )->Which( );
            sal_uInt16 nSubType = pField->GetSubType();

            // TODO Any other field types here ?
            if ( ( nType == SwFieldIds::SetExp ) && ( nSubType & nsSwGetSetExpType::GSE_STRING ) )
            {
                const SwSetExpField *pSet = static_cast<const SwSetExpField*>( pField );
                m_sFieldBkm = pSet->GetPar1( );
            }
            else if ( nType == SwFieldIds::Dropdown )
            {
                const SwDropDownField* pDropDown = static_cast<const SwDropDownField*>( pField );
                m_sFieldBkm = pDropDown->GetName( );
            }
        }
    }

    void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark )
    {
        if ( !m_Fields.empty() )
            m_Fields.begin()->pFieldmark = &rFieldmark;
    }

    void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds )
    {
        for ( const OUString & name : rStarts )
        {
            if (name.startsWith("permission-for-group:") ||
                name.startsWith("permission-for-user:"))
            {
                m_rPermissionsStart.push_back(name);
            }
            else
            {
                m_rBookmarksStart.push_back(name);
            }
        }
        rStarts.clear();

        for ( const OUString & name : rEnds )
        {
            if (name.startsWith("permission-for-group:") ||
                name.startsWith("permission-for-user:"))
            {
                m_rPermissionsEnd.push_back(name);
            }
            else
            {
                m_rBookmarksEnd.push_back(name);
            }
        }
        rEnds.clear();
    }

    void DocxAttributeOutput::WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds )
    {
        for ( const OUString & name : rStarts )
        {
            if (name.startsWith("permission-for-group:") ||
                name.startsWith("permission-for-user:"))
            {
                m_rPermissionsStart.push_back(name);
            }
            else
            {
                m_rFinalBookmarksStart.push_back(name);
            }
        }
        rStarts.clear();

        for ( const OUString & name : rEnds )
        {
            if (name.startsWith("permission-for-group:") ||
                name.startsWith("permission-for-user:"))
            {
                m_rPermissionsEnd.push_back(name);
            }
            else
            {
                m_rFinalBookmarksEnd.push_back(name);
            }
        }
        rEnds.clear();
    }

    void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts,
            std::vector< OUString >& rEnds )
    {
        for ( const auto & rAnnotationName : rStarts )
        {
            OString rName = OUStringToOString(rAnnotationName, RTL_TEXTENCODING_UTF8 ).getStr( );
            m_rAnnotationMarksStart.push_back( rName );
        }
        rStarts.clear();

        for ( const auto & rAnnotationName : rEnds )
        {
            OString rName = OUStringToOString( rAnnotationName, RTL_TEXTENCODING_UTF8 ).getStr( );
            m_rAnnotationMarksEnd.push_back( rName );
        }
        rEnds.clear();
    }

    void DocxAttributeOutput::TextFootnote_Impl( const SwFormatFootnote& rFootnote )
    {
        const SwEndNoteInfo& rInfo = rFootnote.IsEndNote()?
            m_rExport.m_pDoc->GetEndNoteInfo(): m_rExport.m_pDoc->GetFootnoteInfo();

        // footnote/endnote run properties
        const SwCharFormat* pCharFormat = rInfo.GetAnchorCharFormat( *m_rExport.m_pDoc );

        OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));

        m_pSerializer->singleElementNS( XML_w, XML_rStyle, FSNS( XML_w, XML_val ), aStyleId.getStr(), FSEND );

        // remember the footnote/endnote to
        // 1) write the footnoteReference/endnoteReference in EndRunProperties()
        // 2) be able to dump them all to footnotes.xml/endnotes.xml
        if ( !rFootnote.IsEndNote() && m_rExport.m_pDoc->GetFootnoteInfo().ePos != FTNPOS_CHAPTER )
            m_pFootnotesList->add( rFootnote );
        else
            m_pEndnotesList->add( rFootnote );
    }

    void DocxAttributeOutput::FootnoteEndnoteReference()
    {
        sal_Int32 nId;
        const SwFormatFootnote *pFootnote = m_pFootnotesList->getCurrent( nId );
        sal_Int32 nToken = XML_footnoteReference;

        // both cannot be set at the same time - if they are, it's a bug
        if ( !pFootnote )
        {
            pFootnote = m_pEndnotesList->getCurrent( nId );
            nToken = XML_endnoteReference;
        }

        if ( !pFootnote )
            return;

        // write it
        if ( pFootnote->GetNumStr().isEmpty() )
        {
            // autonumbered
            m_pSerializer->singleElementNS( XML_w, nToken,
                    FSNS( XML_w, XML_id ), OString::number( nId ).getStr(),
                    FSEND );
        }
        else
        {
            // not autonumbered
            m_pSerializer->singleElementNS( XML_w, nToken,
                    FSNS( XML_w, XML_customMarkFollows ), "1",
                    FSNS( XML_w, XML_id ), OString::number( nId ).getStr(),
                    FSEND );

            RunText( pFootnote->GetNumStr() );
        }
    }

    void DocxAttributeOutput::FootnotesEndnotes( bool bFootnotes )
    {
        m_setFootnote = true;
        const FootnotesVector& rVector = bFootnotes? m_pFootnotesList->getVector(): m_pEndnotesList->getVector();

        sal_Int32 nBody = bFootnotes? XML_footnotes: XML_endnotes;
        sal_Int32 nItem = bFootnotes? XML_footnote:  XML_endnote;

        m_pSerializer->startElementNS( XML_w, nBody, m_rExport.MainXmlNamespaces() );

        sal_Int32 nIndex = 0;

        // separator
        m_pSerializer->startElementNS( XML_w, nItem,
                FSNS( XML_w, XML_id ), OString::number( nIndex++ ).getStr(),
                FSNS( XML_w, XML_type ), "separator",
                FSEND );
        m_pSerializer->startElementNS( XML_w, XML_p, FSEND );
        m_pSerializer->startElementNS( XML_w, XML_r, FSEND );

        bool bSeparator = true;
        if (bFootnotes)
        {
            const SwPageFootnoteInfo& rFootnoteInfo = m_rExport.m_pDoc->GetPageDesc(0).GetFootnoteInfo();
            // Request a separator only in case the width is larger than zero.
            bSeparator = double(rFootnoteInfo.GetWidth()) > 0;
        }

        if (bSeparator)
            m_pSerializer->singleElementNS( XML_w, XML_separator, FSEND );
        m_pSerializer->endElementNS( XML_w, XML_r );
        m_pSerializer->endElementNS( XML_w, XML_p );
        m_pSerializer->endElementNS( XML_w, nItem );

        // separator
        m_pSerializer->startElementNS( XML_w, nItem,
                FSNS( XML_w, XML_id ), OString::number( nIndex++ ).getStr(),
                FSNS( XML_w, XML_type ), "continuationSeparator",
                FSEND );
        m_pSerializer->startElementNS( XML_w, XML_p, FSEND );
        m_pSerializer->startElementNS( XML_w, XML_r, FSEND );
        m_pSerializer->singleElementNS( XML_w, XML_continuationSeparator, FSEND );
        m_pSerializer->endElementNS( XML_w, XML_r );
        m_pSerializer->endElementNS( XML_w, XML_p );
        m_pSerializer->endElementNS( XML_w, nItem );

        // if new special ones are added, update also WriteFootnoteEndnotePr()

        // footnotes/endnotes themselves
        for ( FootnotesVector::const_iterator i = rVector.begin(); i != rVector.end(); ++i, ++nIndex )
        {
            m_pSerializer->startElementNS( XML_w, nItem,
                    FSNS( XML_w, XML_id ), OString::number( nIndex ).getStr(),
                    FSEND );

            const SwNodeIndex* pIndex = (*i)->GetTextFootnote()->GetStartNode();
            // tag required at the start of each footnote/endnote
            m_footnoteEndnoteRefTag = bFootnotes ? XML_footnoteRef : XML_endnoteRef;

            m_rExport.WriteSpecialText( pIndex->GetIndex() + 1,
                    pIndex->GetNode().EndOfSectionIndex(),
                    bFootnotes? TXT_FTN: TXT_EDN );

            m_pSerializer->endElementNS( XML_w, nItem );
        }

        m_pSerializer->endElementNS( XML_w, nBody );

    }

    void DocxAttributeOutput::WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag,
        const SwEndNoteInfo& info, int listtag )
    {
        fs->startElementNS( XML_w, tag, FSEND );
        const char* fmt = nullptr;
        switch( info.aFormat.GetNumberingType())
        {
            case SVX_NUM_CHARS_UPPER_LETTER_N: // fall through, map to upper letters
            case SVX_NUM_CHARS_UPPER_LETTER:
                fmt = "upperLetter";
                break;
            case SVX_NUM_CHARS_LOWER_LETTER_N: // fall through, map to lower letters
            case SVX_NUM_CHARS_LOWER_LETTER:
                fmt = "lowerLetter";
                break;
            case SVX_NUM_ROMAN_UPPER:
                fmt = "upperRoman";
                break;
            case SVX_NUM_ROMAN_LOWER:
                fmt = "lowerRoman";
                break;
            case SVX_NUM_ARABIC:
                fmt = "decimal";
                break;
            case SVX_NUM_NUMBER_NONE:
                fmt = "none";
                break;
            case SVX_NUM_CHAR_SPECIAL:
                fmt = "bullet";
                break;
            case SVX_NUM_PAGEDESC:
            case SVX_NUM_BITMAP:
            default:
                break; // no format
        }
        if( fmt != nullptr )
            fs->singleElementNS( XML_w, XML_numFmt, FSNS( XML_w, XML_val ), fmt, FSEND );
        if( info.nFootnoteOffset != 0 )
            fs->singleElementNS( XML_w, XML_numStart, FSNS( XML_w, XML_val ),
                OString::number( info.nFootnoteOffset + 1).getStr(), FSEND );

        const SwFootnoteInfo* pFootnoteInfo = dynamic_cast<const SwFootnoteInfo*>(&info);
        if( pFootnoteInfo )
        {
            switch( pFootnoteInfo->eNum )
            {
                case FTNNUM_PAGE:       fmt = "eachPage"; break;
                case FTNNUM_CHAPTER:    fmt = "eachSect"; break;
                default:                fmt = nullptr;    break;
            }
            if( fmt != nullptr )
                fs->singleElementNS( XML_w, XML_numRestart, FSNS( XML_w, XML_val ), fmt, FSEND );
        }

        if( listtag != 0 ) // we are writing to settings.xml, write also special footnote/endnote list
        { // there are currently only two hardcoded ones ( see FootnotesEndnotes())
            fs->singleElementNS( XML_w, listtag, FSNS( XML_w, XML_id ), "0", FSEND );
            fs->singleElementNS( XML_w, listtag, FSNS( XML_w, XML_id ), "1", FSEND );
        }
        fs->endElementNS( XML_w, tag );
    }

    void DocxAttributeOutput::SectFootnoteEndnotePr()
    {
        if( HasFootnotes())
            WriteFootnoteEndnotePr( m_pSerializer, XML_footnotePr, m_rExport.m_pDoc->GetFootnoteInfo(), 0 );
        if( HasEndnotes())
            WriteFootnoteEndnotePr( m_pSerializer, XML_endnotePr, m_rExport.m_pDoc->GetEndNoteInfo(), 0 );
    }

    void DocxAttributeOutput::ParaLineSpacing_Impl( short nSpace, short nMulti )
    {
        if ( nSpace < 0 )
        {
            AddToAttrList( m_pParagraphSpacingAttrList, 2,
                    FSNS( XML_w, XML_lineRule ), "exact",
                    FSNS( XML_w, XML_line ), OString::number( -nSpace ).getStr() );
        }
        else if( nMulti )
        {
            AddToAttrList( m_pParagraphSpacingAttrList, 2,
                    FSNS( XML_w, XML_lineRule ), "auto",
                    FSNS( XML_w, XML_line ), OString::number( nSpace ).getStr() );
        }
        else if ( nSpace > 0 )
        {
            AddToAttrList( m_pParagraphSpacingAttrList, 2,
                    FSNS( XML_w, XML_lineRule ), "atLeast",
                    FSNS( XML_w, XML_line ), OString::number( nSpace ).getStr() );
        }
        else
            AddToAttrList( m_pParagraphSpacingAttrList, FSNS( XML_w, XML_lineRule ), "auto" );
    }

    void DocxAttributeOutput::ParaAdjust( const SvxAdjustItem& rAdjust )
    {
        const char *pAdjustString;

        bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;

        const SfxItemSet* pItems = GetExport().GetCurItemSet();
        const SvxFrameDirectionItem* rFrameDir = pItems?
            pItems->GetItem( RES_FRAMEDIR ) : nullptr;

        SvxFrameDirection nDir = SvxFrameDirection::Environment;
        if( rFrameDir != nullptr )
            nDir = rFrameDir->GetValue();
        if ( nDir == SvxFrameDirection::Environment )
            nDir = GetExport( ).GetDefaultFrameDirection( );
        bool bRtl = ( nDir == SvxFrameDirection::Horizontal_RL_TB );

        switch ( rAdjust.GetAdjust() )
        {
            case SvxAdjust::Left:
                if ( bEcma )
                {
                    if ( bRtl )
                        pAdjustString = "right";
                    else
                        pAdjustString = "left";
                }
                else if ( bRtl )
                    pAdjustString = "end";
                else
                    pAdjustString = "start";
                break;
            case SvxAdjust::Right:
                if ( bEcma )
                {
                    if ( bRtl )
                        pAdjustString = "left";
                    else
                        pAdjustString = "right";
                }
                else if ( bRtl )
                    pAdjustString = "start";
                else
                    pAdjustString = "end";
                break;
            case SvxAdjust::BlockLine:
            case SvxAdjust::Block:
                pAdjustString = "both";
                break;
            case SvxAdjust::Center:
                pAdjustString = "center";
                break;
            default:
                return; // not supported attribute
        }
        m_pSerializer->singleElementNS( XML_w, XML_jc, FSNS( XML_w, XML_val ), pAdjustString, FSEND );
    }

    void DocxAttributeOutput::ParaSplit( const SvxFormatSplitItem& rSplit )
    {
        if (rSplit.GetValue())
            m_pSerializer->singleElementNS( XML_w, XML_keepLines, FSNS( XML_w, XML_val ), "false", FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_keepLines, FSEND );
    }

    void DocxAttributeOutput::ParaWidows( const SvxWidowsItem& rWidows )
    {
        if (rWidows.GetValue())
            m_pSerializer->singleElementNS( XML_w, XML_widowControl, FSEND );
        else
            m_pSerializer->singleElementNS( XML_w, XML_widowControl, FSNS( XML_w, XML_val ), "false", FSEND );
    }

    static void impl_WriteTabElement( FSHelperPtr const & pSerializer,
                                      const SvxTabStop& rTab )
    {
        FastAttributeList *pTabElementAttrList = FastSerializerHelper::createAttrList();

        switch (rTab.GetAdjustment())
        {
        case SvxTabAdjust::Right:
            pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "right" ) );
            break;
        case SvxTabAdjust::Decimal:
            pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "decimal" ) );
            break;
        case SvxTabAdjust::Center:
            pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "center" ) );
            break;
        case SvxTabAdjust::Default:
        case SvxTabAdjust::Left:
        default:
            pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "left" ) );
            break;
        }

        // Because GetTabPos already includes indent, we don't need to add nCurrentLeft (CurrentLeft is indentation information)
        //pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::valueOf( rTab.GetTabPos() + nCurrentLeft ) );
        pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::number( rTab.GetTabPos()                ) );

        sal_Unicode cFillChar = rTab.GetFill();

        if ('.' == cFillChar )
            pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "dot" ) );
        else if ( '-' == cFillChar )
            pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "hyphen" ) );
        else if ( u'\x00B7' == cFillChar ) // middle dot
            pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "middleDot" ) );
        else if ( '_' == cFillChar )
            pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "underscore" ) );
        else
            pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "none" ) );

        pSerializer->singleElementNS( XML_w, XML_tab, pTabElementAttrList );
    }

    void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop )
    {
        const SvxTabStopItem* pInheritedTabs = nullptr;
        if ( GetExport().m_pStyAttr )
            pInheritedTabs = GetExport().m_pStyAttr->GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
        else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
            pInheritedTabs = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
        const sal_uInt16 nInheritedTabCount = pInheritedTabs ? pInheritedTabs->Count() : 0;
        const sal_uInt16 nCount = rTabStop.Count();

        // <w:tabs> must contain at least one <w:tab>, so don't write it empty
        if ( !nCount && !nInheritedTabCount )
            return;
        if( nCount == 1 && rTabStop[ 0 ].GetAdjustment() == SvxTabAdjust::Default )
        {
            GetExport().setDefaultTabStop( rTabStop[ 0 ].GetTabPos());
            return;
        }

        m_pSerializer->startElementNS( XML_w, XML_tabs, FSEND );

        // clear unused inherited tabs - otherwise the style will add them back in
        sal_Int32 nCurrTab = 0;
        for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i )
        {
            while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) )
                ++nCurrTab;

            if ( nCurrTab == nCount || pInheritedTabs->At(i) < rTabStop[nCurrTab] )
            {
                m_pSerializer->singleElementNS( XML_w, XML_tab,
                    FSNS( XML_w, XML_val ), OString("clear"),
                    FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()),
                    FSEND );
            }
        }

        for (sal_uInt16 i = 0; i < nCount; i++ )
        {
            if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default )
                impl_WriteTabElement( m_pSerializer, rTabStop[i] );
            else
                GetExport().setDefaultTabStop( rTabStop[i].GetTabPos());
        }

        m_pSerializer->endElementNS( XML_w, XML_tabs );
    }

    void DocxAttributeOutput::ParaHyphenZone( const SvxHyphenZoneItem& rHyphenZone )
    {
        m_pSerializer->singleElementNS( XML_w, XML_suppressAutoHyphens,
                FSNS( XML_w, XML_val ), OString::boolean( !rHyphenZone.IsHyphen() ),
                FSEND );
    }

    void DocxAttributeOutput::ParaNumRule_Impl( const SwTextNode* pTextNd, sal_Int32 nLvl, sal_Int32 nNumId )
    {
        if ( USHRT_MAX != nNumId )
        {
            const sal_Int32 nTableSize = m_rExport.m_pUsedNumTable ? m_rExport.m_pUsedNumTable->size() : 0;
            const SwNumRule* pRule = nNumId > 0 && nNumId <= nTableSize ? (*m_rExport.m_pUsedNumTable)[nNumId-1] : nullptr;
            const bool bOutlineRule = pRule && pRule->IsOutlineRule();

            // Do not export outline rules (Chapter Numbering) as paragraph properties, only as style properties.
            if ( !pTextNd || !bOutlineRule )
            {
                m_pSerializer->startElementNS( XML_w, XML_numPr, FSEND );
                m_pSerializer->singleElementNS( XML_w, XML_ilvl, FSNS( XML_w, XML_val ), OString::number( nLvl).getStr(), FSEND );
                m_pSerializer->singleElementNS( XML_w, XML_numId, FSNS( XML_w, XML_val ), OString::number( nNumId).getStr(), FSEND );
                m_pSerializer->endElementNS( XML_w, XML_numPr );
            }
        }
    }

    void DocxAttributeOutput::ParaScriptSpace( const SfxBoolItem& rScriptSpace )
    {
        m_pSerializer->singleElementNS( XML_w, XML_autoSpaceDE,
               FSNS( XML_w, XML_val ), OString::boolean( rScriptSpace.GetValue() ),
               FSEND );
    }

    void DocxAttributeOutput::ParaHangingPunctuation( const SfxBoolItem& rItem )
    {
        m_pSerializer->singleElementNS( XML_w, XML_overflowPunct,
               FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ),
               FSEND );
    }

    void DocxAttributeOutput::ParaForbiddenRules( const SfxBoolItem& rItem )
    {
        m_pSerializer->singleElementNS( XML_w, XML_kinsoku,
               FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ),
               FSEND );
    }

    void DocxAttributeOutput::ParaVerticalAlign( const SvxParaVertAlignItem& rAlign )
    {
        const char *pAlignString;

        switch ( rAlign.GetValue() )
        {
            case SvxParaVertAlignItem::Align::Baseline:
                pAlignString = "baseline";
                break;
            case SvxParaVertAlignItem::Align::Top:
                pAlignString = "top";
                break;
            case SvxParaVertAlignItem::Align::Center:
                pAlignString = "center";
                break;
            case SvxParaVertAlignItem::Align::Bottom:
                pAlignString = "bottom";
                break;
            case SvxParaVertAlignItem::Align::Automatic:
                pAlignString = "auto";
                break;
            default:
                return; // not supported attribute
        }
        m_pSerializer->singleElementNS( XML_w, XML_textAlignment, FSNS( XML_w, XML_val ), pAlignString, FSEND );
    }

    void DocxAttributeOutput::ParaSnapToGrid( const SvxParaGridItem& rGrid )
    {
        m_pSerializer->singleElementNS( XML_w, XML_snapToGrid,
                FSNS( XML_w, XML_val ), OString::boolean( rGrid.GetValue() ),
                FSEND );
    }

    void DocxAttributeOutput::FormatFrameSize( const SwFormatFrameSize& rSize )
    {
        if (m_rExport.SdrExporter().getTextFrameSyntax() && m_rExport.SdrExporter().getFlyFrameSize())
        {
            const Size* pSize = m_rExport.SdrExporter().getFlyFrameSize();
            m_rExport.SdrExporter().getTextFrameStyle().append(";width:").append(double(pSize->Width()) / 20);
            m_rExport.SdrExporter().getTextFrameStyle().append("pt;height:").append(double(pSize->Height()) / 20).append("pt");
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
        }
        else if ( m_rExport.m_bOutFlyFrameAttrs )
        {
            if ( rSize.GetWidth() && rSize.GetWidthSizeType() == ATT_FIX_SIZE )
                AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
                        FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ).getStr() );

            if ( rSize.GetHeight() )
            {
                OString sRule( "exact" );
                if ( rSize.GetHeightSizeType() == ATT_MIN_SIZE )
                    sRule = OString( "atLeast" );
                AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), 2,
                        FSNS( XML_w, XML_hRule ), sRule.getStr(),
                        FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ).getStr() );
            }
        }
        else if ( m_rExport.m_bOutPageDescs )
        {
            FastAttributeList *attrList = FastSerializerHelper::createAttrList( );
            if ( m_rExport.m_pCurrentPageDesc->GetLandscape( ) )
                attrList->add( FSNS( XML_w, XML_orient ), "landscape" );

            attrList->add( FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
            attrList->add( FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );

            XFastAttributeListRef xAttrList( attrList );
            attrList = nullptr;

            m_pSerializer->singleElementNS( XML_w, XML_pgSz, xAttrList );
        }
    }

    void DocxAttributeOutput::FormatPaperBin( const SvxPaperBinItem& )
    {
        SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatPaperBin()" );
    }

    void DocxAttributeOutput::FormatLRSpace( const SvxLRSpaceItem& rLRSpace )
    {
        bool bEcma = m_rExport.GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;

        if (m_rExport.SdrExporter().getTextFrameSyntax())
        {
            m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-left:").append(double(rLRSpace.GetLeft()) / 20).append("pt");
            m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-right:").append(double(rLRSpace.GetRight()) / 20).append("pt");
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
        }
        else if ( m_rExport.m_bOutFlyFrameAttrs )
        {
            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hSpace ),
                    OString::number(
                        ( rLRSpace.GetLeft() + rLRSpace.GetRight() ) / 2 ).getStr() );
        }
        else if ( m_rExport.m_bOutPageDescs )
        {
            m_pageMargins.nLeft = 0;
            m_pageMargins.nRight = 0;

            if ( auto pBoxItem = static_cast<const SvxBoxItem*>(m_rExport.HasItem( RES_BOX )) )
            {
                m_pageMargins.nLeft = pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/true );
                m_pageMargins.nRight = pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/true );
            }

            m_pageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft());
            m_pageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight());

            AddToAttrList( m_pSectionSpacingAttrList, 2,
                    FSNS( XML_w, XML_left ), OString::number( m_pageMargins.nLeft ).getStr(),
                    FSNS( XML_w, XML_right ), OString::number( m_pageMargins.nRight ).getStr() );
        }
        else
        {
            FastAttributeList *pLRSpaceAttrList = FastSerializerHelper::createAttrList();
            if((0 != rLRSpace.GetTextLeft()) || (rLRSpace.IsExplicitZeroMarginValLeft()))
            {
                pLRSpaceAttrList->add( FSNS( XML_w, ( bEcma ? XML_left : XML_start ) ), OString::number(  rLRSpace.GetTextLeft() ) );
            }
            if((0 != rLRSpace.GetRight()) || (rLRSpace.IsExplicitZeroMarginValRight()))
            {
                pLRSpaceAttrList->add( FSNS( XML_w, ( bEcma ? XML_right : XML_end ) ), OString::number(  rLRSpace.GetRight() ) );
            }
            sal_Int32 nFirstLineAdjustment = rLRSpace.GetTextFirstLineOfst();
            if (nFirstLineAdjustment > 0)
                pLRSpaceAttrList->add( FSNS( XML_w, XML_firstLine ), OString::number( nFirstLineAdjustment ) );
            else
                pLRSpaceAttrList->add( FSNS( XML_w, XML_hanging ), OString::number( - nFirstLineAdjustment ) );
            m_pSerializer->singleElementNS( XML_w, XML_ind, pLRSpaceAttrList );
        }
    }

    void DocxAttributeOutput::FormatULSpace( const SvxULSpaceItem& rULSpace )
    {

        if (m_rExport.SdrExporter().getTextFrameSyntax())
        {
            m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-top:").append(double(rULSpace.GetUpper()) / 20).append("pt");
            m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-bottom:").append(double(rULSpace.GetLower()) / 20).append("pt");
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
        }
        else if ( m_rExport.m_bOutFlyFrameAttrs )
        {
            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vSpace ),
                    OString::number(
                        ( rULSpace.GetLower() + rULSpace.GetUpper() ) / 2 ).getStr() );
        }
        else if (m_rExport.m_bOutPageDescs )
        {
            OSL_ENSURE( m_rExport.GetCurItemSet(), "Impossible" );
            if ( !m_rExport.GetCurItemSet() )
                return;

            HdFtDistanceGlue aDistances( *m_rExport.GetCurItemSet() );

            sal_Int32 nHeader = 720;
            if ( aDistances.HasHeader() )
                nHeader = sal_Int32( aDistances.dyaHdrTop );

            // Page top
            m_pageMargins.nTop = aDistances.dyaTop;

            sal_Int32 nFooter = 720;
            if ( aDistances.HasFooter() )
                nFooter = sal_Int32( aDistances.dyaHdrBottom );

            // Page Bottom
            m_pageMargins.nBottom = aDistances.dyaBottom;

            AddToAttrList( m_pSectionSpacingAttrList, 5,
                    FSNS( XML_w, XML_header ), OString::number( nHeader ).getStr(),
                    FSNS( XML_w, XML_top ), OString::number( m_pageMargins.nTop ).getStr(),
                    FSNS( XML_w, XML_footer ), OString::number( nFooter ).getStr(),
                    FSNS( XML_w, XML_bottom ), OString::number( m_pageMargins.nBottom ).getStr(),
                    // FIXME Page Gutter is not handled ATM, setting to 0 as it's mandatory for OOXML
                    FSNS( XML_w, XML_gutter ), "0" );
        }
        else
        {
            SAL_INFO("sw.ww8", "DocxAttributeOutput::FormatULSpace: setting spacing" << rULSpace.GetUpper() );
            // check if before auto spacing was set during import and spacing we get from actual object is same
            // that we set in import. If yes just write beforeAutoSpacing tag.
            if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper())
            {
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_beforeAutospacing ), "1" );
            }
            else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1)
            {
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_beforeAutospacing ), "0" );
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ).getStr() );
            }
            else
            {
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ).getStr() );
            }
            m_bParaBeforeAutoSpacing = false;
            // check if after auto spacing was set during import and spacing we get from actual object is same
            // that we set in import. If yes just write afterAutoSpacing tag.
            if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower())
            {
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_afterAutospacing ), "1" );
            }
            else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1)
            {
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_afterAutospacing ), "0" );
                AddToAttrList( m_pParagraphSpacingAttrList,
                                    FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()).getStr() );
            }
            else
            {
                AddToAttrList( m_pParagraphSpacingAttrList,
                        FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()).getStr() );
            }
            m_bParaAfterAutoSpacing = false;

            if (rULSpace.GetContext())
                m_pSerializer->singleElementNS( XML_w, XML_contextualSpacing, FSEND );
        }
    }

    void DocxAttributeOutput::FormatSurround( const SwFormatSurround& rSurround )
    {
        if (m_rExport.SdrExporter().getTextFrameSyntax())
        {
            OString sType, sSide;
            switch (rSurround.GetSurround())
            {
                case css::text::WrapTextMode_NONE:
                    sType = "topAndBottom";
                    break;
                case css::text::WrapTextMode_PARALLEL:
                    sType = "square";
                    break;
                case css::text::WrapTextMode_DYNAMIC:
                    sType = "square";
                    sSide = "largest";
                    break;
                case css::text::WrapTextMode_LEFT:
                    sType = "square";
                    sSide = "left";
                    break;
                case css::text::WrapTextMode_RIGHT:
                    sType = "square";
                    sSide = "right";
                    break;
                case css::text::WrapTextMode_THROUGH:
                    /* empty type and side means through */
                default:
                    break;
            }
            if (!sType.isEmpty() || !sSide.isEmpty())
            {
                m_rExport.SdrExporter().setFlyWrapAttrList(FastSerializerHelper::createAttrList());
                if (!sType.isEmpty())
                    m_rExport.SdrExporter().getFlyWrapAttrList()->add(XML_type, sType);
                if (!sSide.isEmpty())
                    m_rExport.SdrExporter().getFlyWrapAttrList()->add(XML_side, sSide);
            }
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
        }
        else if ( m_rExport.m_bOutFlyFrameAttrs )
        {
            OString sWrap( "auto" );
            switch ( rSurround.GetSurround( ) )
            {
                case css::text::WrapTextMode_NONE:
                    sWrap = OString( "none" );
                    break;
                case css::text::WrapTextMode_THROUGH:
                    sWrap = OString( "through" );
                    break;
                case css::text::WrapTextMode_DYNAMIC:
                case css::text::WrapTextMode_PARALLEL:
                case css::text::WrapTextMode_LEFT:
                case css::text::WrapTextMode_RIGHT:
                default:
                    sWrap = OString( "around" );
            }

            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_wrap ), sWrap.getStr() );
        }
    }

    void DocxAttributeOutput::FormatVertOrientation( const SwFormatVertOrient& rFlyVert )
    {
        OString sAlign;
        switch( rFlyVert.GetVertOrient() )
        {
            case text::VertOrientation::NONE:
                break;
            case text::VertOrientation::CENTER:
            case text::VertOrientation::LINE_CENTER:
                sAlign = OString( "center" );
                break;
            case text::VertOrientation::BOTTOM:
                sAlign = OString( "bottom" );
                break;
            case text::VertOrientation::LINE_BOTTOM:
                sAlign = OString( "outside" );
                break;
            case text::VertOrientation::TOP:
                sAlign = OString( "top" );
                break;
            case text::VertOrientation::LINE_TOP:
            default:
                sAlign = OString( "inside" );
                break;
        }
        OString sVAnchor( "page" );
        switch ( rFlyVert.GetRelationOrient( ) )
        {
            case text::RelOrientation::CHAR:
            case text::RelOrientation::PRINT_AREA:
            case text::RelOrientation::TEXT_LINE:
            case text::RelOrientation::FRAME:
                sVAnchor = OString( "text" );
                break;
            case text::RelOrientation::PAGE_LEFT:
            case text::RelOrientation::PAGE_RIGHT:
            case text::RelOrientation::FRAME_LEFT:
            case text::RelOrientation::FRAME_RIGHT:
            case text::RelOrientation::PAGE_PRINT_AREA:
                sVAnchor = OString( "margin" );
                break;
            case text::RelOrientation::PAGE_FRAME:
            default:
                break;
        }

        if (m_rExport.SdrExporter().getTextFrameSyntax())
        {
            m_rExport.SdrExporter().getTextFrameStyle().append(";margin-top:").append(double(rFlyVert.GetPos()) / 20).append("pt");
            if ( !sAlign.isEmpty() )
                m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical:").append(sAlign);
            m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical-relative:").append(sVAnchor);
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
        }
        else if ( m_rExport.m_bOutFlyFrameAttrs )
        {
            if ( !sAlign.isEmpty() )
                AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_yAlign ), sAlign.getStr() );
            else
                AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_y ),
                    OString::number( rFlyVert.GetPos() ).getStr() );
            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vAnchor ), sVAnchor.getStr() );
        }
    }

    void DocxAttributeOutput::FormatHorizOrientation( const SwFormatHoriOrient& rFlyHori )
    {
        OString sAlign;
        switch( rFlyHori.GetHoriOrient() )
        {
            case text::HoriOrientation::NONE:
                break;
            case text::HoriOrientation::LEFT:
                sAlign = OString( rFlyHori.IsPosToggle( ) ? "inside" : "left" );
                break;
            case text::HoriOrientation::RIGHT:
                sAlign = OString( rFlyHori.IsPosToggle( ) ? "outside" : "right" );
                break;
            case text::HoriOrientation::CENTER:
            case text::HoriOrientation::FULL: // FULL only for tables
            default:
                sAlign = OString( "center" );
                break;
        }
        OString sHAnchor( "page" );
        switch ( rFlyHori.GetRelationOrient( ) )
        {
            case text::RelOrientation::CHAR:
            case text::RelOrientation::PRINT_AREA:
            case text::RelOrientation::FRAME:
                sHAnchor = OString( "text" );
                break;
            case text::RelOrientation::PAGE_LEFT:
            case text::RelOrientation::PAGE_RIGHT:
            case text::RelOrientation::FRAME_LEFT:
            case text::RelOrientation::FRAME_RIGHT:
            case text::RelOrientation::PAGE_PRINT_AREA:
                sHAnchor = OString( "margin" );
                break;
            case text::RelOrientation::PAGE_FRAME:
            default:
                break;
        }

        if (m_rExport.SdrExporter().getTextFrameSyntax())
        {
            m_rExport.SdrExporter().getTextFrameStyle().append(";margin-left:").append(double(rFlyHori.GetPos()) / 20).append("pt");
            if ( !sAlign.isEmpty() )
                m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal:").append(sAlign);
            m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal-relative:").append(sHAnchor);
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
        }
        else if ( m_rExport.m_bOutFlyFrameAttrs )
        {
            if ( !sAlign.isEmpty() )
                AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_xAlign ), sAlign.getStr() );
            else
                AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_x ),
                    OString::number( rFlyHori.GetPos() ).getStr() );
            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hAnchor ), sHAnchor.getStr() );
        }
    }

    void DocxAttributeOutput::FormatAnchor( const SwFormatAnchor& )
    {
        // Fly frames: anchors here aren't matching the anchors in docx
    }

    boost::optional<sal_Int32> lcl_getDmlAlpha(const SvxBrushItem& rBrush)
    {
        boost::optional<sal_Int32> oRet;
        sal_Int32 nTransparency = rBrush.GetColor().GetTransparency();
        if (nTransparency)
        {
            // Convert transparency to percent
            sal_Int8 nTransparencyPercent = SvxBrushItem::TransparencyToPercent(nTransparency);

            // Calculate alpha value
            // Consider oox/source/drawingml/color.cxx : getTransparency() function.
            sal_Int32 nAlpha = (::oox::drawingml::MAX_PERCENT - ( ::oox::drawingml::PER_PERCENT * nTransparencyPercent ) );
            oRet = nAlpha;
        }
        return oRet;
    }

    void DocxAttributeOutput::FormatBackground( const SvxBrushItem& rBrush )
    {
        const Color aColor = rBrush.GetColor();
        OString sColor = msfilter::util::ConvertColor( aColor.GetRGBColor() );
        boost::optional<sal_Int32> oAlpha = lcl_getDmlAlpha(rBrush);
        if (m_rExport.SdrExporter().getTextFrameSyntax())
        {
            // Handle 'Opacity'
            if (oAlpha)
            {
                // Calculate opacity value
                // Consider oox/source/vml/vmlformatting.cxx : decodeColor() function.
                double fOpacity = static_cast<double>(*oAlpha) * 65535 / ::oox::drawingml::MAX_PERCENT;
                OUString sOpacity = OUString::number(fOpacity) + "f";

                AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_opacity, OUStringToOString(sOpacity, RTL_TEXTENCODING_UTF8).getStr() );
            }

            sColor = "#" + sColor;
            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, sColor.getStr() );
        }
        else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
            bool bImageBackground = false;
            const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
            if (pItem)
            {
                const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
                if(pFillStyle->GetValue() == drawing::FillStyle_BITMAP)
                {
                    bImageBackground = true;
                }
            }
            if (!bImageBackground)
            {
                m_pSerializer->startElementNS(XML_a, XML_solidFill, FSEND);
                m_pSerializer->startElementNS(XML_a, XML_srgbClr,
                                              XML_val, sColor,
                                              FSEND);
                if (oAlpha)
                    m_pSerializer->singleElementNS(XML_a, XML_alpha,
                                                  XML_val, OString::number(*oAlpha),
                                                  FSEND);
                m_pSerializer->endElementNS(XML_a, XML_srgbClr);
                m_pSerializer->endElementNS(XML_a, XML_solidFill);
            }
        }
        else if ( !m_rExport.m_bOutPageDescs )
        {
            // compare fill color with the original fill color
            OString sOriginalFill = OUStringToOString(
                    m_sOriginalBackgroundColor, RTL_TEXTENCODING_UTF8 );

            if ( aColor == COL_AUTO )
                sColor = "auto";

            if( !m_pBackgroundAttrList.is() )
            {
                m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
                m_pBackgroundAttrList->add( FSNS( XML_w, XML_fill ), sColor.getStr() );
                m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
            }
            else if ( sOriginalFill != sColor )
            {
                // fill was modified during edition, theme fill attribute must be dropped
                m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
                m_pBackgroundAttrList->add( FSNS( XML_w, XML_fill ), sColor.getStr() );
                m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
            }
            m_sOriginalBackgroundColor.clear();
        }
    }

    void DocxAttributeOutput::FormatFillStyle( const XFillStyleItem& rFillStyle )
    {
        if (!m_bIgnoreNextFill)
            m_oFillStyle.reset(rFillStyle.GetValue());
        else
            m_bIgnoreNextFill = false;

        // Don't round-trip grabbag OriginalBackground if the background has been cleared.
        if ( m_pBackgroundAttrList.is() && m_sOriginalBackgroundColor != "auto" && rFillStyle.GetValue() == drawing::FillStyle_NONE )
            m_pBackgroundAttrList.clear();
    }

    void DocxAttributeOutput::FormatFillGradient( const XFillGradientItem& rFillGradient )
    {
        if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && !m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
            AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, "gradient" );

            const XGradient& rGradient = rFillGradient.GetGradientValue();
            OString sStartColor = msfilter::util::ConvertColor(rGradient.GetStartColor());
            OString sEndColor = msfilter::util::ConvertColor(rGradient.GetEndColor());

            // Calculate the angle that was originally in the imported DOCX file
            // (reverse calculate the angle that was converted in the file
            //     /oox/source/vml/vmlformatting.cxx :: FillModel::pushToPropMap
            // and also in
            //     /oox/source/drawingml/fillproperties.cxx :: FillProperties::pushToPropMap
            sal_Int32 nReverseAngle = 4500 - rGradient.GetAngle();
            nReverseAngle = nReverseAngle / 10;
            nReverseAngle = (270 - nReverseAngle) % 360;
            if (nReverseAngle != 0)
                AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(),
                        XML_angle, OString::number( nReverseAngle ).getStr() );

            OString sColor1 = sStartColor;
            OString sColor2 = sEndColor;

            switch (rGradient.GetGradientStyle())
            {
                case css::awt::GradientStyle_AXIAL:
                    AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_focus, "50%" );
                    // If it is an 'axial' gradient - swap the colors
                    // (because in the import process they were imported swapped)
                    sColor1 = sEndColor;
                    sColor2 = sStartColor;
                    break;
                case css::awt::GradientStyle_LINEAR: break;
                case css::awt::GradientStyle_RADIAL: break;
                case css::awt::GradientStyle_ELLIPTICAL: break;
                case css::awt::GradientStyle_SQUARE: break;
                case css::awt::GradientStyle_RECT: break;
                default:
                    break;
            }

            sColor1 = "#" + sColor1;
            sColor2 = "#" + sColor2;
            AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, sColor1.getStr() );
            AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_color2, sColor2.getStr() );
        }
        else if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
            SwFrameFormat & rFormat(
                    const_cast<SwFrameFormat&>(m_rExport.m_pParentFrame->GetFrameFormat()));
            uno::Reference<beans::XPropertySet> const xPropertySet(
                SwXTextFrame::CreateXTextFrame(*rFormat.GetDoc(), &rFormat),
                uno::UNO_QUERY);
            m_rDrawingML.SetFS(m_pSerializer);
            m_rDrawingML.WriteGradientFill(xPropertySet);
        }
        m_oFillStyle.reset();
    }

    void DocxAttributeOutput::FormatBox( const SvxBoxItem& rBox )
    {
        if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
            // <a:gradFill> should be before <a:ln>.
            const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
            if (pItem)
            {
                const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
                FormatFillStyle(*pFillStyle);
                if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_BITMAP)
                {
                    const SdrObject* pSdrObj = m_rExport.m_pParentFrame->GetFrameFormat().FindRealSdrObject();
                    if (pSdrObj)
                    {
                        uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
                        uno::Reference< beans::XPropertySet > xPropertySet( xShape, uno::UNO_QUERY );
                        m_rDrawingML.SetFS(m_pSerializer);
                        m_rDrawingML.WriteBlipFill(xPropertySet, "BackGraphic");
                    }
                }
            }

            pItem = GetExport().HasItem(XATTR_FILLGRADIENT);
            if (pItem)
            {
                const XFillGradientItem* pFillGradient = static_cast<const XFillGradientItem*>(pItem);
                FormatFillGradient(*pFillGradient);
            }
            m_bIgnoreNextFill = true;
        }
        if (m_rExport.SdrExporter().getTextFrameSyntax() || m_rExport.SdrExporter().getDMLTextFrameSyntax())
        {
            const SvxBorderLine* pLeft = rBox.GetLeft( );
            const SvxBorderLine* pTop = rBox.GetTop( );
            const SvxBorderLine* pRight = rBox.GetRight( );
            const SvxBorderLine* pBottom = rBox.GetBottom( );

            if (pLeft && pRight && pTop && pBottom &&
                    *pLeft == *pRight && *pLeft == *pTop && *pLeft == *pBottom)
            {
                // Check border style
                SvxBorderLineStyle eBorderStyle = pTop->GetBorderLineStyle();
                if (eBorderStyle == SvxBorderLineStyle::NONE)
                {
                    if (m_rExport.SdrExporter().getTextFrameSyntax())
                    {
                        AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), 2,
                                XML_stroked, "f", XML_strokeweight, "0pt" );
                    }
                }
                else
                {
                    OString sColor(msfilter::util::ConvertColor(pTop->GetColor()));
                    double const fConverted(editeng::ConvertBorderWidthToWord(pTop->GetBorderLineStyle(), pTop->GetWidth()));

                    if (m_rExport.SdrExporter().getTextFrameSyntax())
                    {
                        sColor = "#" + sColor;
                        sal_Int32 nWidth = sal_Int32(fConverted / 20);
                        OString sWidth = OString::number(nWidth) + "pt";
                        AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), 2,
                                XML_strokecolor, sColor.getStr(),
                                XML_strokeweight, sWidth.getStr() );
                        if( SvxBorderLineStyle::DASHED == pTop->GetBorderLineStyle() ) // Line Style is Dash type
                            AddToAttrList( m_rExport.SdrExporter().getDashLineStyle(),
                                XML_dashstyle, "dash" );
                    }
                    else
                        m_rExport.SdrExporter().writeBoxItemLine(rBox);
                }
            }

            if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
            {
                m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_lIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::LEFT))));
                m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_tIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::TOP))));
                m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_rIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::RIGHT))));
                m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_bIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::BOTTOM))));
                return;
            }

            // v:textbox's inset attribute: inner margin values for textbox text - write only non-default values
            double fDistanceLeftTwips = double(rBox.GetDistance(SvxBoxItemLine::LEFT));
            double fDistanceTopTwips = double(rBox.GetDistance(SvxBoxItemLine::TOP));
            double fDistanceRightTwips = double(rBox.GetDistance(SvxBoxItemLine::RIGHT));
            double fDistanceBottomTwips = double(rBox.GetDistance(SvxBoxItemLine::BOTTOM));

            // Convert 'TWIPS' to 'INCH' (because in Word the default values are in Inches)
            double fDistanceLeftInch = fDistanceLeftTwips / 1440;
            double fDistanceTopInch = fDistanceTopTwips / 1440;
            double fDistanceRightInch = fDistanceRightTwips / 1440;
            double fDistanceBottomInch = fDistanceBottomTwips / 1440;

            // This code will write ONLY the non-default values. The values are in 'left','top','right','bottom' order.
            // so 'bottom' is checked if it is default and if it is non-default - all the values will be written
            // otherwise - 'right' is checked if it is default and if it is non-default - all the values except for 'bottom' will be written
            // and so on.
            OStringBuffer aInset;
            if(!aInset.isEmpty() || fDistanceBottomInch != 0.05)
                aInset.insert(0, "," + OString::number(fDistanceBottomInch) + "in");

            if(!aInset.isEmpty() || fDistanceRightInch != 0.1)
                aInset.insert(0, "," + OString::number(fDistanceRightInch) + "in");

            if(!aInset.isEmpty() || fDistanceTopInch != 0.05)
                aInset.insert(0, "," + OString::number(fDistanceTopInch) + "in");

            if(!aInset.isEmpty() || fDistanceLeftInch != 0.1)
                aInset.insert(0, OString::number(fDistanceLeftInch) + "in");

            if (!aInset.isEmpty())
                m_rExport.SdrExporter().getTextboxAttrList()->add(XML_inset, aInset.makeStringAndClear());

            return;
        }

        OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
        // Check if there is a shadow item
        const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
        if ( pItem )
        {
            const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
            aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
        }

        if ( !m_bOpenedSectPr || GetWritingHeaderFooter())
        {
            // Not inside a section

            // Open the paragraph's borders tag
            m_pSerializer->startElementNS( XML_w, XML_pBdr, FSEND );

            std::map<SvxBoxItemLine, css::table::BorderLine2> aStyleBorders;
            const SvxBoxItem* pInherited = nullptr;
            if ( GetExport().m_pStyAttr )
                pInherited = GetExport().m_pStyAttr->GetItem<SvxBoxItem>(RES_BOX);
            else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
                pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_BOX);

            if ( pInherited )
            {
                aStyleBorders[ SvxBoxItemLine::TOP ] = SvxBoxItem::SvxLineToLine(pInherited->GetTop(), /*bConvert=*/false);
                aStyleBorders[ SvxBoxItemLine::BOTTOM ] = SvxBoxItem::SvxLineToLine(pInherited->GetBottom(), false);
                aStyleBorders[ SvxBoxItemLine::LEFT ] = SvxBoxItem::SvxLineToLine(pInherited->GetLeft(), false);
                aStyleBorders[ SvxBoxItemLine::RIGHT ] = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
            }

            impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aStyleBorders );

            // Close the paragraph's borders tag
            m_pSerializer->endElementNS( XML_w, XML_pBdr );
        }
    }

    void DocxAttributeOutput::FormatColumns_Impl( sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, SwTwips nPageSize )
    {
        // Get the columns attributes
        FastAttributeList *pColsAttrList = FastSerializerHelper::createAttrList();

        pColsAttrList->add( FSNS( XML_w, XML_num ),
                OString::number( nCols ). getStr( ) );

        const char* pEquals = "false";
        if ( bEven )
        {
            sal_uInt16 nWidth = rCol.GetGutterWidth( true );
            pColsAttrList->add( FSNS( XML_w, XML_space ),
                   OString::number( nWidth ).getStr( ) );

            pEquals = "true";
        }

        pColsAttrList->add( FSNS( XML_w, XML_equalWidth ), pEquals );

        bool bHasSep = (COLADJ_NONE != rCol.GetLineAdj());

        pColsAttrList->add( FSNS( XML_w, XML_sep ), OString::boolean( bHasSep ) );

        // Write the element
        m_pSerializer->startElementNS( XML_w, XML_cols, pColsAttrList );

        // Write the columns width if non-equals
        const SwColumns & rColumns = rCol.GetColumns(  );
        if ( !bEven )
        {
            for ( sal_uInt16 n = 0; n < nCols; ++n )
            {
                FastAttributeList *pColAttrList = FastSerializerHelper::createAttrList();
                sal_uInt16 nWidth = rCol.CalcPrtColWidth( n, static_cast<sal_uInt16>(nPageSize) );
                pColAttrList->add( FSNS( XML_w, XML_w ),
                        OString::number( nWidth ).getStr( ) );

                if ( n + 1 != nCols )
                {
                    sal_uInt16 nSpacing = rColumns[n].GetRight( ) + rColumns[n + 1].GetLeft( );
                    pColAttrList->add( FSNS( XML_w, XML_space ),
                        OString::number( nSpacing ).getStr( ) );
                }

                m_pSerializer->singleElementNS( XML_w, XML_col, pColAttrList );
            }
        }

        m_pSerializer->endElementNS( XML_w, XML_cols );
    }

    void DocxAttributeOutput::FormatKeep( const SvxFormatKeepItem& rItem )
    {
        m_pSerializer->singleElementNS( XML_w, XML_keepNext,
                FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ),
                FSEND );
    }

    void DocxAttributeOutput::FormatTextGrid( const SwTextGridItem& rGrid )
    {
        FastAttributeList *pGridAttrList = FastSerializerHelper::createAttrList();

        OString sGridType;
        switch ( rGrid.GetGridType( ) )
        {
            default:
            case GRID_NONE:
                sGridType = OString( "default" );
                break;
            case GRID_LINES_ONLY:
                sGridType = OString( "lines" );
                break;
            case GRID_LINES_CHARS:
                if ( rGrid.IsSnapToChars( ) )
                    sGridType = OString( "snapToChars" );
                else
                    sGridType = OString( "linesAndChars" );
                break;
        }
        pGridAttrList->add( FSNS( XML_w, XML_type ), sGridType.getStr( ) );

        sal_uInt16 nHeight = rGrid.GetBaseHeight() + rGrid.GetRubyHeight();
        pGridAttrList->add( FSNS( XML_w, XML_linePitch ),
                OString::number( nHeight ).getStr( ) );

        pGridAttrList->add( FSNS( XML_w, XML_charSpace ),
                OString::number( GridCharacterPitch( rGrid ) ).getStr( ) );

        m_pSerializer->singleElementNS( XML_w, XML_docGrid, pGridAttrList );
    }

    void DocxAttributeOutput::FormatLineNumbering( const SwFormatLineNumber& rNumbering )
    {
        if ( !rNumbering.IsCount( ) )
            m_pSerializer->singleElementNS( XML_w, XML_suppressLineNumbers, FSEND );
    }

    void DocxAttributeOutput::FormatFrameDirection( const SvxFrameDirectionItem& rDirection )
    {
        OString sTextFlow;
        bool bBiDi = false;
        SvxFrameDirection nDir = rDirection.GetValue();

        if ( nDir == SvxFrameDirection::Environment )
            nDir = GetExport( ).GetDefaultFrameDirection( );

        switch ( nDir )
        {
            default:
            case SvxFrameDirection::Horizontal_LR_TB:
                sTextFlow = OString( "lrTb" );
                break;
            case SvxFrameDirection::Horizontal_RL_TB:
                sTextFlow = OString( "lrTb" );
                bBiDi = true;
                break;
            case SvxFrameDirection::Vertical_LR_TB: // many things but not this one
            case SvxFrameDirection::Vertical_RL_TB:
                sTextFlow = OString( "tbRl" );
                break;
        }

        if ( m_rExport.m_bOutPageDescs )
        {
            m_pSerializer->singleElementNS( XML_w, XML_textDirection,
                   FSNS( XML_w, XML_val ), sTextFlow.getStr( ),
                   FSEND );
            if ( bBiDi )
                m_pSerializer->singleElementNS( XML_w, XML_bidi, FSEND );
        }
        else if ( !m_rExport.m_bOutFlyFrameAttrs )
        {
            if ( bBiDi )
                m_pSerializer->singleElementNS( XML_w, XML_bidi, FSNS( XML_w, XML_val ), "1", FSEND );
            else
                m_pSerializer->singleElementNS( XML_w, XML_bidi, FSNS( XML_w, XML_val ), "0", FSEND );
        }
    }

    void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem)
    {
        const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag();
        for ( const auto & rGrabBagElement : rMap )
        {
            if (rGrabBagElement.first == "MirrorIndents")
                m_pSerializer->singleElementNS(XML_w, XML_mirrorIndents, FSEND);
            else if (rGrabBagElement.first == "ParaTopMarginBeforeAutoSpacing")
            {
                m_bParaBeforeAutoSpacing = true;
                // get fixed value which was set during import
                rGrabBagElement.second >>= m_nParaBeforeSpacing;
                m_nParaBeforeSpacing = convertMm100ToTwip(m_nParaBeforeSpacing);
                SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaBeforeSpacing);
            }
            else if (rGrabBagElement.first == "ParaBottomMarginAfterAutoSpacing")
            {
                m_bParaAfterAutoSpacing = true;
                // get fixed value which was set during import
                rGrabBagElement.second >>= m_nParaAfterSpacing;
                m_nParaAfterSpacing = convertMm100ToTwip(m_nParaAfterSpacing);
                SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaAfterSpacing);
            }
            else if (rGrabBagElement.first == "CharThemeFill")
            {
                uno::Sequence<beans::PropertyValue> aGrabBagSeq;
                rGrabBagElement.second >>= aGrabBagSeq;

                for (sal_Int32 j=0; j < aGrabBagSeq.getLength(); ++j)
                {
                    OString sVal = OUStringToOString(aGrabBagSeq[j].Value.get<OUString>(), RTL_TEXTENCODING_UTF8);

                    if (sVal.isEmpty())
                        continue;

                    if (aGrabBagSeq[j].Name == "val")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_val), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "color")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_color), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "themeColor")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeColor), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "themeTint")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeTint), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "themeShade")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeShade), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "fill")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_fill), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "themeFill")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFill), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "themeFillTint")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillTint), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "themeFillShade")
                        AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillShade), sVal.getStr());
                    else if (aGrabBagSeq[j].Name == "originalColor")
                        aGrabBagSeq[j].Value >>= m_sOriginalBackgroundColor;
                }
            }
            else if (rGrabBagElement.first == "SdtPr")
            {
                uno::Sequence<beans::PropertyValue> aGrabBagSdt =
                        rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
                for (sal_Int32 k=0; k < aGrabBagSdt.getLength(); ++k)
                {
                    beans::PropertyValue aPropertyValue = aGrabBagSdt[k];
                    if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj" ||
                            aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
                    {
                        if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj")
                            m_nParagraphSdtPrToken = FSNS( XML_w, XML_docPartObj );
                        else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
                            m_nParagraphSdtPrToken = FSNS( XML_w, XML_docPartList );

                        uno::Sequence<beans::PropertyValue> aGrabBag;
                        aPropertyValue.Value >>= aGrabBag;
                        for (sal_Int32 j=0; j < aGrabBag.getLength(); ++j)
                        {
                            OUString sValue = aGrabBag[j].Value.get<OUString>();
                            if (aGrabBag[j].Name == "ooxml:CT_SdtDocPart_docPartGallery")
                                AddToAttrList( m_pParagraphSdtPrTokenChildren,
                                               FSNS( XML_w, XML_docPartGallery ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtDocPart_docPartCategory")
                                AddToAttrList( m_pParagraphSdtPrTokenChildren,
                                               FSNS( XML_w, XML_docPartCategory ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtDocPart_docPartUnique")
                            {
                                if (sValue.isEmpty())
                                    sValue = "true";
                                AddToAttrList( m_pParagraphSdtPrTokenChildren, FSNS( XML_w, XML_docPartUnique ),
                                OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            }
                        }
                    }
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_equation")
                        m_nParagraphSdtPrToken = FSNS( XML_w, XML_equation );
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_picture")
                        m_nParagraphSdtPrToken = FSNS( XML_w, XML_picture );
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
                        m_nParagraphSdtPrToken = FSNS( XML_w, XML_citation );
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_group")
                        m_nParagraphSdtPrToken = FSNS( XML_w, XML_group );
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text")
                        m_nParagraphSdtPrToken = FSNS(XML_w, XML_text);
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pParagraphSdtPrDataBindingAttrs.is())
                    {
                        uno::Sequence<beans::PropertyValue> aGrabBag;
                        aPropertyValue.Value >>= aGrabBag;
                        for (sal_Int32 j = 0; j < aGrabBag.getLength(); ++j)
                        {
                            OUString sValue = aGrabBag[j].Value.get<OUString>();
                            if (aGrabBag[j].Name == "ooxml:CT_DataBinding_prefixMappings")
                                AddToAttrList( m_pParagraphSdtPrDataBindingAttrs,
                                               FSNS( XML_w, XML_prefixMappings ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_DataBinding_xpath")
                                AddToAttrList( m_pParagraphSdtPrDataBindingAttrs,
                                               FSNS( XML_w, XML_xpath ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_DataBinding_storeItemID")
                                AddToAttrList( m_pParagraphSdtPrDataBindingAttrs,
                                               FSNS( XML_w, XML_storeItemID ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                        }
                    }
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aParagraphSdtPrAlias.isEmpty())
                    {
                        if (!(aPropertyValue.Value >>= m_aParagraphSdtPrAlias))
                            SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unexpected sdt alias value");
                        m_aStartedParagraphSdtPrAlias = m_aParagraphSdtPrAlias;
                    }
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox")
                    {
                        m_nParagraphSdtPrToken = FSNS( XML_w14, XML_checkbox );
                        uno::Sequence<beans::PropertyValue> aGrabBag;
                        aPropertyValue.Value >>= aGrabBag;
                        for (sal_Int32 j=0; j < aGrabBag.getLength(); ++j)
                        {
                            OUString sValue = aGrabBag[j].Value.get<OUString>();
                            if (aGrabBag[j].Name == "ooxml:CT_SdtCheckbox_checked")
                                AddToAttrList( m_pParagraphSdtPrTokenChildren,
                                               FSNS( XML_w14, XML_checked ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtCheckbox_checkedState")
                                AddToAttrList( m_pParagraphSdtPrTokenChildren,
                                               FSNS( XML_w14, XML_checkedState ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtCheckbox_uncheckedState")
                                AddToAttrList( m_pParagraphSdtPrTokenChildren,
                                               FSNS( XML_w14, XML_uncheckedState ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                        }
                    }
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id")
                        m_bParagraphSdtHasId = true;
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_date")
                    {
                        m_nParagraphSdtPrToken = FSNS(XML_w, XML_date);
                        uno::Sequence<beans::PropertyValue> aGrabBag = aPropertyValue.Value.get< uno::Sequence<beans::PropertyValue> >();
                        for (sal_Int32 j=0; j < aGrabBag.getLength(); ++j)
                        {
                            OString sValue = OUStringToOString(aGrabBag[j].Value.get<OUString>(), RTL_TEXTENCODING_UTF8);

                            if (aGrabBag[j].Name == "ooxml:CT_SdtDate_fullDate")
                                AddToAttrList(m_pParagraphSdtPrTokenAttributes, FSNS(XML_w, XML_fullDate), sValue.getStr());
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtDate_dateFormat")
                                AddToAttrList(m_pParagraphSdtPrTokenChildren, FSNS(XML_w, XML_dateFormat), sValue.getStr());
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtDate_lid")
                                AddToAttrList(m_pParagraphSdtPrTokenChildren, FSNS(XML_w, XML_lid), sValue.getStr());
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtDate_storeMappedDataAs")
                                AddToAttrList(m_pParagraphSdtPrTokenChildren, FSNS(XML_w, XML_storeMappedDataAs), sValue.getStr());
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtDate_calendar")
                                AddToAttrList(m_pParagraphSdtPrTokenChildren, FSNS(XML_w, XML_calendar), sValue.getStr());
                            else
                                SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled SdtPr / ooxml:CT_SdtPr_date grab bag property " << aGrabBag[j].Name);
                        }
                    }
                    else
                        SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled SdtPr grab bag property " << aPropertyValue.Name);
                }
            }
            else if (rGrabBagElement.first == "ParaCnfStyle")
            {
                uno::Sequence<beans::PropertyValue> aAttributes = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
                m_pTableStyleExport->CnfStyle(aAttributes);
            }
            else if (rGrabBagElement.first == "ParaSdtEndBefore")
            {
                // Handled already in StartParagraph().
            }
            else
                SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first );
        }
    }

    void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem )
    {
        if (m_bPreventDoubleFieldsHandling)
            return;

        const std::map< OUString, css::uno::Any >& rMap = rItem.GetGrabBag();

        // get original values of theme-derived properties to check if they have changed during the edition
        bool bWriteCSTheme = true;
        bool bWriteAsciiTheme = true;
        bool bWriteEastAsiaTheme = true;
        bool bWriteThemeFontColor = true;
        OUString sOriginalValue;
        for ( const auto & rGrabBagElement : rMap )
        {
            if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameCs" )
            {
                if ( rGrabBagElement.second >>= sOriginalValue )
                    bWriteCSTheme =
                            ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_cs ) ) == sOriginalValue );
            }
            else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameAscii" )
            {
                if ( rGrabBagElement.second >>= sOriginalValue )
                    bWriteAsciiTheme =
                            ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_ascii ) ) == sOriginalValue );
            }
            else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameEastAsia" )
            {
                if ( rGrabBagElement.second >>= sOriginalValue )
                    bWriteEastAsiaTheme =
                            ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_eastAsia ) ) == sOriginalValue );
            }
            else if ( m_pColorAttrList.is() && rGrabBagElement.first == "CharThemeOriginalColor" )
            {
                if ( rGrabBagElement.second >>= sOriginalValue )
                    bWriteThemeFontColor =
                            ( m_pColorAttrList->getOptionalValue( FSNS( XML_w, XML_val ) ) == sOriginalValue );
            }
        }

        // save theme attributes back to the run properties
        OUString str;
        for ( const auto & rGrabBagElement : rMap )
        {
            if ( rGrabBagElement.first == "CharThemeNameAscii" && bWriteAsciiTheme )
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_asciiTheme ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if ( rGrabBagElement.first == "CharThemeNameCs" && bWriteCSTheme )
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cstheme ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if ( rGrabBagElement.first == "CharThemeNameEastAsia" && bWriteEastAsiaTheme )
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsiaTheme ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if ( rGrabBagElement.first == "CharThemeNameHAnsi" && bWriteAsciiTheme )
            // this is not a mistake: in LibO we don't directly support the hAnsi family
            // of attributes so we save the same value from ascii attributes instead
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_hAnsiTheme ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if ( rGrabBagElement.first == "CharThemeColor" && bWriteThemeFontColor )
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_themeColor ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if ( rGrabBagElement.first == "CharThemeColorShade" )
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_themeShade ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if ( rGrabBagElement.first == "CharThemeColorTint" )
            {
                rGrabBagElement.second >>= str;
                AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_themeTint ),
                        OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
            }
            else if( rGrabBagElement.first == "CharThemeFontNameCs"   ||
                    rGrabBagElement.first == "CharThemeFontNameAscii" ||
                    rGrabBagElement.first == "CharThemeFontNameEastAsia" ||
                    rGrabBagElement.first == "CharThemeOriginalColor" )
            {
                // just skip these, they were processed before
            }
            else if(rGrabBagElement.first == "CharGlowTextEffect" ||
                    rGrabBagElement.first == "CharShadowTextEffect" ||
                    rGrabBagElement.first == "CharReflectionTextEffect" ||
                    rGrabBagElement.first == "CharTextOutlineTextEffect" ||
                    rGrabBagElement.first == "CharTextFillTextEffect" ||
                    rGrabBagElement.first == "CharScene3DTextEffect" ||
                    rGrabBagElement.first == "CharProps3DTextEffect" ||
                    rGrabBagElement.first == "CharLigaturesTextEffect" ||
                    rGrabBagElement.first == "CharNumFormTextEffect" ||
                    rGrabBagElement.first == "CharNumSpacingTextEffect" ||
                    rGrabBagElement.first == "CharStylisticSetsTextEffect" ||
                    rGrabBagElement.first == "CharCntxtAltsTextEffect")
            {
                beans::PropertyValue aPropertyValue;
                rGrabBagElement.second >>= aPropertyValue;
                m_aTextEffectsGrabBag.push_back(aPropertyValue);
            }
            else if (rGrabBagElement.first == "SdtEndBefore")
            {
                if (m_bStartedCharSdt)
                    m_bEndCharSdt = true;
            }
            else if (rGrabBagElement.first == "SdtPr" && FLY_NOT_PROCESSED != m_nStateOfFlyFrame )
            {
                uno::Sequence<beans::PropertyValue> aGrabBagSdt =
                        rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
                for (sal_Int32 k=0; k < aGrabBagSdt.getLength(); ++k)
                {
                    beans::PropertyValue aPropertyValue = aGrabBagSdt[k];
                    if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox")
                    {
                        m_nRunSdtPrToken = FSNS( XML_w14, XML_checkbox );
                        uno::Sequence<beans::PropertyValue> aGrabBag;
                        aPropertyValue.Value >>= aGrabBag;
                        for (sal_Int32 j=0; j < aGrabBag.getLength(); ++j)
                        {
                            OUString sValue = aGrabBag[j].Value.get<OUString>();
                            if (aGrabBag[j].Name == "ooxml:CT_SdtCheckbox_checked")
                                AddToAttrList( m_pRunSdtPrTokenChildren,
                                               FSNS( XML_w14, XML_checked ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtCheckbox_checkedState")
                                AddToAttrList( m_pRunSdtPrTokenChildren,
                                               FSNS( XML_w14, XML_checkedState ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_SdtCheckbox_uncheckedState")
                                AddToAttrList( m_pRunSdtPrTokenChildren,
                                               FSNS( XML_w14, XML_uncheckedState ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                        }
                    }
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pRunSdtPrDataBindingAttrs.is())
                    {
                        uno::Sequence<beans::PropertyValue> aGrabBag;
                        aPropertyValue.Value >>= aGrabBag;
                        for (sal_Int32 j=0; j < aGrabBag.getLength(); ++j)
                        {
                            OUString sValue = aGrabBag[j].Value.get<OUString>();
                            if (aGrabBag[j].Name == "ooxml:CT_DataBinding_prefixMappings")
                                AddToAttrList( m_pRunSdtPrDataBindingAttrs,
                                               FSNS( XML_w, XML_prefixMappings ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_DataBinding_xpath")
                                AddToAttrList( m_pRunSdtPrDataBindingAttrs,
                                               FSNS( XML_w, XML_xpath ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                            else if (aGrabBag[j].Name == "ooxml:CT_DataBinding_storeItemID")
                                AddToAttrList( m_pRunSdtPrDataBindingAttrs,
                                               FSNS( XML_w, XML_storeItemID ),
                                               OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
                        }
                    }
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aRunSdtPrAlias.isEmpty())
                    {
                        if (!(aPropertyValue.Value >>= m_aRunSdtPrAlias))
                            SAL_WARN("sw.ww8", "DocxAttributeOutput::CharGrabBag: unexpected sdt alias value");
                    }
                    //do not overwrite the parent node.
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text" && !m_pRunSdtPrTokenChildren.is())
                        m_nRunSdtPrToken = FSNS( XML_w, XML_text );
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id" && m_nRunSdtPrToken == 0)
                        // only write id token as a marker if no other exist
                        m_nRunSdtPrToken = FSNS( XML_w, XML_id );
                    else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
                        m_nRunSdtPrToken = FSNS( XML_w, XML_citation );
                }
            }
            else
                SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first);
        }
    }

    DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr& pSerializer, oox::drawingml::DrawingML* pDrawingML )
        : m_rExport( rExport ),
          m_pSerializer( pSerializer ),
          m_rDrawingML( *pDrawingML ),
          m_bEndCharSdt(false),
          m_bStartedCharSdt(false),
          m_bStartedParaSdt(false),
          m_endPageRef( false ),
          m_pFootnotesList( new ::docx::FootnotesList() ),
          m_pEndnotesList( new ::docx::FootnotesList() ),
          m_footnoteEndnoteRefTag( 0 ),
          m_pRedlineData( nullptr ),
          m_nRedlineId( 0 ),
          m_bOpenedSectPr( false ),
          m_bHadSectPr(false),
          m_bRunTextIsOn( false ),
          m_bWritingHeaderFooter( false ),
          m_bAnchorLinkedToNode(false),
          m_bPreventDoubleFieldsHandling( false ),
          m_sFieldBkm( ),
          m_nNextBookmarkId( 0 ),
          m_nNextAnnotationMarkId( 0 ),
          m_pCurrentFrame( nullptr ),
          m_bParagraphOpened( false ),
          m_bParagraphFrameOpen( false ),
          m_bIsFirstParagraph( true ),
          m_bAlternateContentChoiceOpen( false ),
          m_bPostponedProcessingFly( false ),
          m_nColBreakStatus( COLBRK_NONE ),
          m_bPostponedPageBreak( false ),
          m_nTextFrameLevel( 0 ),
          m_closeHyperlinkInThisRun( false ),
          m_closeHyperlinkInPreviousRun( false ),
          m_startedHyperlink( false ),
          m_nHyperLinkCount(0),
          m_nFieldsInHyperlink( 0 ),
          m_bExportingOutline(false),
          m_nChartCount(0),
          m_postponedChart( nullptr ),
          pendingPlaceholder( nullptr ),
          m_postitFieldsMaxId( 0 ),
          m_anchorId( 1 ),
          m_nextFontId( 1 ),
          m_tableReference(new TableReference()),
          m_bIgnoreNextFill(false),
          m_bBtLr(false),
          m_pTableStyleExport(new DocxTableStyleExport(rExport.m_pDoc, pSerializer)),
          m_bParaBeforeAutoSpacing(false),
          m_bParaAfterAutoSpacing(false),
          m_nParaBeforeSpacing(0),
          m_nParaAfterSpacing(0),
          m_setFootnote(false)
        , m_nParagraphSdtPrToken(0)
        , m_nRunSdtPrToken(0)
        , m_nStateOfFlyFrame( FLY_NOT_PROCESSED )
        , m_bParagraphSdtHasId(false)
    {
        // Push initial items to the RelId cache. In case the document contains no
        // special streams (headers, footers, etc.) then these items are used
        // during the full export.
        PushRelIdCache();
    }

    DocxAttributeOutput::~DocxAttributeOutput()
    {
    }

    DocxExport& DocxAttributeOutput::GetExport()
    {
        return m_rExport;
    }

    void DocxAttributeOutput::SetSerializer( ::sax_fastparser::FSHelperPtr const & pSerializer )
    {
        m_pSerializer = pSerializer;
        m_pTableStyleExport->SetSerializer(pSerializer);
    }

    bool DocxAttributeOutput::HasFootnotes() const
    {
        return !m_pFootnotesList->isEmpty();
    }

    bool DocxAttributeOutput::HasEndnotes() const
    {
        return !m_pEndnotesList->isEmpty();
    }

    bool DocxAttributeOutput::HasPostitFields() const
    {
        return !m_postitFields.empty();
    }

    void DocxAttributeOutput::BulletDefinition(int nId, const Graphic& rGraphic, Size aSize)
    {
        m_pSerializer->startElementNS(XML_w, XML_numPicBullet,
                FSNS(XML_w, XML_numPicBulletId), OString::number(nId).getStr(),
                FSEND);

        OStringBuffer aStyle;
        // Size is in twips, we need it in points.
        aStyle.append("width:").append(double(aSize.Width()) / 20);
        aStyle.append("pt;height:").append(double(aSize.Height()) / 20).append("pt");
        m_pSerializer->startElementNS( XML_w, XML_pict, FSEND);
        m_pSerializer->startElementNS( XML_v, XML_shape,
                XML_style, aStyle.getStr(),
                FSNS(XML_o, XML_bullet), "t",
                FSEND);

        OUString aRelId = m_rDrawingML.WriteImage(rGraphic);
        m_pSerializer->singleElementNS( XML_v, XML_imagedata,
                FSNS(XML_r, XML_id), OUStringToOString(aRelId, RTL_TEXTENCODING_UTF8),
                FSNS(XML_o, XML_title), "",
                FSEND);

        m_pSerializer->endElementNS(XML_v, XML_shape);
        m_pSerializer->endElementNS(XML_w, XML_pict);

        m_pSerializer->endElementNS(XML_w, XML_numPicBullet);
    }

    void DocxAttributeOutput::AddToAttrList( rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, sal_Int32 nAttrName, const sal_Char* sAttrValue )
    {
        AddToAttrList( pAttrList, 1, nAttrName, sAttrValue );
    }

    void DocxAttributeOutput::AddToAttrList( rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, sal_Int32 nAttrs, ... )
    {
        if( !pAttrList.is() )
            pAttrList = FastSerializerHelper::createAttrList();

        va_list args;
        va_start( args, nAttrs );
        for( sal_Int32 i = 0; i<nAttrs; i++)
        {
            sal_Int32 nName = va_arg( args, sal_Int32 );
            const char* pValue = va_arg( args, const char* );
            if( pValue )
                pAttrList->add( nName, pValue );
        }
        va_end( args );
    }

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

↑ V523 The 'then' statement is equivalent to the 'else' statement.

↑ V522 There might be dereferencing of a potential null pointer 'pTableRowRedline'.

↑ V1001 The 'aFrameProperties' variable is assigned but is not used by the end of the function.

↑ V560 A part of conditional expression is always false.

↑ V547 Expression 'pPitch' is always true.

↑ V547 Expression 'pFamily' is always true.

↑ V547 Expression 'pType' is always true.

↑ V560 A part of conditional expression is always true: nWwId >= ww::stiLev1.

↑ V560 A part of conditional expression is always false.

↑ V522 There might be dereferencing of a potential null pointer 'pTableCellRedline'.