/* -*- 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'.