/* -*- 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 <vector>
#include <list>
#include <utility>
#include <algorithm>
#include <iostream>
 
#include <oox/core/filterbase.hxx>
#include "docxexport.hxx"
#include "docxexportfilter.hxx"
 
#include <i18nlangtag/mslangid.hxx>
#include <hintids.hxx>
#include <tools/urlobj.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/cmapitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/svxfont.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/keepitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/tstpitem.hxx>
#include <svl/grabbagitem.hxx>
#include <svl/urihelper.hxx>
#include <svl/whiter.hxx>
#include <fmtpdsc.hxx>
#include <fmtfsize.hxx>
#include <fmtornt.hxx>
#include <fmtlsplt.hxx>
#include <fmtflcnt.hxx>
#include <fmtanchr.hxx>
#include <fmtcntnt.hxx>
#include <frmatr.hxx>
#include <paratr.hxx>
#include <txatbase.hxx>
#include <fmtinfmt.hxx>
#include <fmtrfmrk.hxx>
#include <fchrfmt.hxx>
#include <fmtautofmt.hxx>
#include <charfmt.hxx>
#include <tox.hxx>
#include <ndtxt.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <docary.hxx>
#include <swtable.hxx>
#include <swtblfmt.hxx>
#include <section.hxx>
#include <pagedesc.hxx>
#include <swrect.hxx>
#include <reffld.hxx>
#include <redline.hxx>
#include <wrtswtbl.hxx>
#include <htmltbl.hxx>
#include <txttxmrk.hxx>
#include <fmtline.hxx>
#include <fmtruby.hxx>
#include <breakit.hxx>
#include <txtatr.hxx>
#include <fmtsrnd.hxx>
#include <fmtrowsplt.hxx>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/text/RubyPosition.hpp>
#include <oox/export/vmlexport.hxx>
#include <sfx2/docfile.hxx>
#include <sal/log.hxx>
 
#include "sprmids.hxx"
 
#include "writerhelper.hxx"
#include "writerwordglue.hxx"
#include <numrule.hxx>
#include "wrtww8.hxx"
#include "ww8par.hxx"
#include <IMark.hxx>
#include "ww8attributeoutput.hxx"
 
#include <ndgrf.hxx>
#include <ndole.hxx>
 
#include <cstdio>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::i18n;
using namespace sw::util;
using namespace sw::types;
using namespace sw::mark;
using namespace ::oox::vml;
 
static OUString lcl_getFieldCode( const IFieldmark* pFieldmark )
{
    OSL_ENSURE(pFieldmark!=nullptr, "where is my fieldmark???");
 
    if ( !pFieldmark)
        return OUString();
    if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
        return OUString(" FORMTEXT ");
    if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
        return OUString(" FORMDROPDOWN ");
    if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
        return OUString(" FORMCHECKBOX ");
    if ( pFieldmark->GetFieldname( ) == ODF_TOC )
        return OUString(" TOC ");
    if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
        return OUString(" HYPERLINK ");
    if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
        return OUString(" PAGEREF ");
    return pFieldmark->GetFieldname();
}
 
static ww::eField lcl_getFieldId( const IFieldmark* pFieldmark ) {
    OSL_ENSURE(pFieldmark!=nullptr, "where is my fieldmark???");
    if ( !pFieldmark )
        return ww::eUNKNOWN;
    if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
        return ww::eFORMTEXT;
    if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
        return ww::eFORMDROPDOWN;
    if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
        return ww::eFORMCHECKBOX;
    if ( pFieldmark->GetFieldname( ) == ODF_TOC )
        return ww::eTOC;
    if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
        return ww::eHYPERLINK;
    if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
        return ww::ePAGEREF;
    return ww::eUNKNOWN;
}
 
MSWordAttrIter::MSWordAttrIter( MSWordExportBase& rExport )
    : pOld( rExport.m_pChpIter ), m_rExport( rExport )
{
    m_rExport.m_pChpIter = this;
}
 
MSWordAttrIter::~MSWordAttrIter()
{
    m_rExport.m_pChpIter = pOld;
}
 
class sortswflys
{
public:
    bool operator()(const ww8::Frame &rOne, const ww8::Frame &rTwo) const
    {
        return rOne.GetPosition() < rTwo.GetPosition();
    }
};
 
void SwWW8AttrIter::IterToCurrent()
{
    OSL_ENSURE(maCharRuns.begin() != maCharRuns.end(), "Impossible");
    mnScript = maCharRunIter->mnScript;
    meChrSet = maCharRunIter->meCharSet;
    mbCharIsRTL = maCharRunIter->mbRTL;
}
 
SwWW8AttrIter::SwWW8AttrIter(MSWordExportBase& rWr, const SwTextNode& rTextNd) :
    MSWordAttrIter(rWr),
    rNd(rTextNd),
    maCharRuns(GetPseudoCharRuns(rTextNd)),
    pCurRedline(nullptr),
    nCurrentSwPos(0),
    nCurRedlinePos(SwRedlineTable::npos),
    mrSwFormatDrop(rTextNd.GetSwAttrSet().GetDrop())
{
 
    SwPosition aPos(rTextNd);
    if (SvxFrameDirection::Horizontal_RL_TB == rWr.m_pDoc->GetTextDirection(aPos))
        mbParaIsRTL = true;
    else
        mbParaIsRTL = false;
 
    maCharRunIter = maCharRuns.begin();
    IterToCurrent();
 
    /*
     #i2916#
     Get list of any graphics which may be anchored from this paragraph.
    */
    maFlyFrames = GetFramesInNode(rWr.m_aFrames, rNd);
    std::sort(maFlyFrames.begin(), maFlyFrames.end(), sortswflys());
 
    /*
     #i18480#
     If we are inside a frame then anything anchored inside this frame can
     only be supported by word anchored inline ("as character"), so force
     this in the supportable case.
    */
    if (rWr.m_bInWriteEscher)
    {
        for ( auto& aFlyFrame : maFlyFrames )
            aFlyFrame.ForceTreatAsInline();
    }
 
    maFlyIter = maFlyFrames.begin();
 
    if ( !m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )
    {
        SwPosition aPosition( rNd, SwIndex( const_cast<SwTextNode*>(&rNd) ) );
        pCurRedline = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedline( aPosition, &nCurRedlinePos );
    }
 
    nCurrentSwPos = SearchNext(1);
}
 
sal_Int32 lcl_getMinPos( sal_Int32 pos1, sal_Int32 pos2 )
{
    if ( pos1 >= 0 && pos2 >= 0 )
    {
        // both valid: return minimum one
        return std::min(pos1, pos2);
    }
 
    // return the valid one, if any, or -1
    return std::max(pos1, pos2);
}
 
sal_Int32 SwWW8AttrIter::SearchNext( sal_Int32 nStartPos )
{
    const OUString aText = rNd.GetText();
    sal_Int32 fieldEndPos = aText.indexOf(CH_TXT_ATR_FIELDEND, nStartPos);
    sal_Int32 fieldStartPos = aText.indexOf(CH_TXT_ATR_FIELDSTART, nStartPos);
    sal_Int32 formElementPos = aText.indexOf(CH_TXT_ATR_FORMELEMENT, nStartPos);
 
    const sal_Int32 pos = lcl_getMinPos(
        lcl_getMinPos( fieldEndPos, fieldStartPos ),
        formElementPos );
 
    sal_Int32 nMinPos = (pos>=0) ? pos : SAL_MAX_INT32;
 
    // first the redline, then the attributes
    if( pCurRedline )
    {
        const SwPosition* pEnd = pCurRedline->End();
        if (pEnd->nNode == rNd)
        {
            const sal_Int32 i = pEnd->nContent.GetIndex();
            if ( i >= nStartPos && i < nMinPos )
            {
                nMinPos = i;
            }
        }
    }
 
    if ( nCurRedlinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() )
    {
        // nCurRedlinePos point to the next redline
        SwRedlineTable::size_type nRedLinePos = nCurRedlinePos;
        if( pCurRedline )
            ++nRedLinePos;
 
        for ( ; nRedLinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedLinePos )
        {
            const SwRangeRedline* pRedl = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nRedLinePos ];
 
            const SwPosition* pStt = pRedl->Start();
            const SwPosition* pEnd = pStt == pRedl->GetPoint()
                                        ? pRedl->GetMark()
                                        : pRedl->GetPoint();
 
            if( pStt->nNode == rNd )
            {
                const sal_Int32 i = pStt->nContent.GetIndex();
                if( i >= nStartPos && i < nMinPos )
                    nMinPos = i;
            }
            else
                break;
 
            if( pEnd->nNode == rNd )
            {
                const sal_Int32 i = pEnd->nContent.GetIndex();
                if( i >= nStartPos && i < nMinPos )
                {
                    nMinPos = i;
                }
            }
        }
    }
 
    if (mrSwFormatDrop.GetWholeWord() && nStartPos <= rNd.GetDropLen(0))
        nMinPos = rNd.GetDropLen(0);
    else if(nStartPos <= mrSwFormatDrop.GetChars())
        nMinPos = mrSwFormatDrop.GetChars();
 
    if(const SwpHints* pTextAttrs = rNd.GetpSwpHints())
    {
 
// can be optimized if we consider that the TextAttrs are sorted by start position.
// but then we'd have to save 2 indices
        for( size_t i = 0; i < pTextAttrs->Count(); ++i )
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            sal_Int32 nPos = pHt->GetStart();    // first Attr characters
            if( nPos >= nStartPos && nPos <= nMinPos )
                nMinPos = nPos;
 
            if( pHt->End() )         // Attr with end
            {
                nPos = *pHt->End();      // last Attr character + 1
                if( nPos >= nStartPos && nPos <= nMinPos )
                    nMinPos = nPos;
            }
            if (pHt->HasDummyChar())
            {
                // pos + 1 because of CH_TXTATR in Text
                nPos = pHt->GetStart() + 1;
                if( nPos >= nStartPos && nPos <= nMinPos )
                    nMinPos = nPos;
            }
        }
    }
 
    if (maCharRunIter != maCharRuns.end())
    {
        if (maCharRunIter->mnEndPos < nMinPos)
            nMinPos = maCharRunIter->mnEndPos;
        IterToCurrent();
    }
 
    /*
     #i2916#
     Check to see if there are any graphics anchored to characters in this
     paragraph's text. Set nMinPos to 1 past the placement for anchored to
     character because anchors in Word appear after the character they are
     anchored to.
    */
    if (maFlyIter != maFlyFrames.end())
    {
        const SwPosition &rAnchor = maFlyIter->GetPosition();
 
        sal_Int32 nPos = rAnchor.nContent.GetIndex();
        if (nPos >= nStartPos && nPos <= nMinPos)
            nMinPos = nPos;
 
        if (maFlyIter->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR)
        {
            ++nPos;
            if (nPos >= nStartPos && nPos <= nMinPos)
                nMinPos = nPos;
        }
    }
 
    //nMinPos found and not going to change at this point
 
    if (maCharRunIter != maCharRuns.end())
    {
        if (maCharRunIter->mnEndPos == nMinPos)
            ++maCharRunIter;
    }
 
    return nMinPos;
}
 
void SwWW8AttrIter::OutAttr( sal_Int32 nSwPos, bool bWriteCombChars)
{
    m_rExport.AttrOutput().RTLAndCJKState( mbCharIsRTL, GetScript() );
 
    /*
     Depending on whether text is in CTL/CJK or Western, get the id of that
     script, the idea is that the font that is actually in use to render this
     range of text ends up in pFont
    */
    sal_uInt16 nFontId = GetWhichOfScript( RES_CHRATR_FONT, GetScript() );
 
    const SvxFontItem &rParentFont = ItemGet<SvxFontItem>(
        static_cast<const SwTextFormatColl&>(rNd.GetAnyFormatColl()), nFontId);
    const SvxFontItem *pFont = &rParentFont;
    const SfxPoolItem *pGrabBag = nullptr;
 
    SfxItemSet aExportSet(*rNd.GetSwAttrSet().GetPool(),
        svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END - 1>{});
 
    //The hard formatting properties that affect the entire paragraph
    if (rNd.HasSwAttrSet())
    {
        // only copy hard attributes - bDeep = false
        aExportSet.Set(rNd.GetSwAttrSet(), false/*bDeep*/);
        // get the current font item. Use rNd.GetSwAttrSet instead of aExportSet:
        const SvxFontItem &rNdFont = ItemGet<SvxFontItem>(rNd.GetSwAttrSet(), nFontId);
        pFont = &rNdFont;
        aExportSet.ClearItem(nFontId);
    }
 
    //The additional hard formatting properties that affect this range in the
    //paragraph
    ww8::PoolItems aRangeItems;
    if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
    {
        for( size_t i = 0; i < pTextAttrs->Count(); ++i )
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            const sal_Int32* pEnd = pHt->End();
 
            if (pEnd ? ( nSwPos >= pHt->GetStart() && nSwPos < *pEnd)
                        : nSwPos == pHt->GetStart() )
            {
                sal_uInt16 nWhich = pHt->GetAttr().Which();
                if (nWhich == RES_TXTATR_AUTOFMT)
                {
                    const SwFormatAutoFormat& rAutoFormat = static_cast<const SwFormatAutoFormat&>(pHt->GetAttr());
                    const std::shared_ptr<SfxItemSet>& pSet = rAutoFormat.GetStyleHandle();
                    SfxWhichIter aIter( *pSet );
                    const SfxPoolItem* pItem;
                    sal_uInt16 nWhichId = aIter.FirstWhich();
                    while( nWhichId )
                    {
                        if( SfxItemState::SET == pSet->GetItemState( nWhichId, false, &pItem ))
                        {
                            if (nWhichId == nFontId)
                                pFont = &(item_cast<SvxFontItem>(*pItem));
                            else if (nWhichId == RES_CHRATR_GRABBAG)
                                pGrabBag = pItem;
                            else
                                aRangeItems[nWhichId] = pItem;
                        }
                        nWhichId = aIter.NextWhich();
                    }
                }
                else
                    aRangeItems[nWhich] = (&(pHt->GetAttr()));
            }
            else if (nSwPos < pHt->GetStart())
                break;
        }
    }
 
    /*
     For #i24291# we need to explicitly remove any properties from the
     aExportSet which a SwCharFormat would override, we can't rely on word doing
     this for us like writer does
    */
    const SwFormatCharFormat *pCharFormatItem =
        HasItem< SwFormatCharFormat >( aRangeItems, RES_TXTATR_CHARFMT );
    if ( pCharFormatItem )
        ClearOverridesFromSet( *pCharFormatItem, aExportSet );
 
    // tdf#113790: AutoFormat style overwrites char style, so remove all
    // elements from CHARFMT grab bag which are set in AUTOFMT grab bag
    if (const SfxGrabBagItem *pAutoFmtGrabBag = dynamic_cast<const SfxGrabBagItem*>(pGrabBag))
    {
        if (const SfxGrabBagItem *pCharFmtGrabBag = aExportSet.GetItem<SfxGrabBagItem>(RES_CHRATR_GRABBAG, false))
        {
            std::unique_ptr<SfxPoolItem> pNewItem(pCharFmtGrabBag->Clone());
            SfxGrabBagItem* pNewCharFmtGrabBag = dynamic_cast<SfxGrabBagItem*>(pNewItem.get());
            assert(pNewCharFmtGrabBag);
            auto & rNewFmtMap = pNewCharFmtGrabBag->GetGrabBag();
            for (auto const & item : pAutoFmtGrabBag->GetGrabBag())
            {
                if (item.second.hasValue())
                    rNewFmtMap.erase(item.first);
            }
            aExportSet.Put(*pNewCharFmtGrabBag);
        }
    }
 
    ww8::PoolItems aExportItems;
    GetPoolItems( aExportSet, aExportItems, false );
 
    if( rNd.GetpSwpHints() == nullptr )
        m_rExport.SetCurItemSet(&aExportSet);
 
    for ( const auto& aRangeItem : aRangeItems )
    {
        aExportItems[aRangeItem.first] = aRangeItem.second;
    }
 
    if ( !aExportItems.empty() )
    {
        const SwModify* pOldMod = m_rExport.m_pOutFormatNode;
        m_rExport.m_pOutFormatNode = &rNd;
        m_rExport.m_aCurrentCharPropStarts.push( nSwPos );
 
        // tdf#38778 Fix output of the font in DOC run for fields
        const SvxFontItem * pFontToOutput = ( rParentFont != *pFont )? pFont : nullptr;
 
        m_rExport.ExportPoolItemsToCHP( aExportItems, GetScript(), pFontToOutput, bWriteCombChars );
 
        // HasTextItem only allowed in the above range
        m_rExport.m_aCurrentCharPropStarts.pop();
        m_rExport.m_pOutFormatNode = pOldMod;
    }
 
    if( rNd.GetpSwpHints() == nullptr )
        m_rExport.SetCurItemSet(nullptr);
 
    OSL_ENSURE( pFont, "must be *some* font associated with this txtnode" );
    if ( pFont )
    {
        SvxFontItem aFont( *pFont );
 
        if ( rParentFont != aFont )
            m_rExport.AttrOutput().OutputItem( aFont );
    }
 
    // Output grab bag attributes
    if (pGrabBag)
        m_rExport.AttrOutput().OutputItem( *pGrabBag );
}
 
bool SwWW8AttrIter::IsWatermarkFrame()
{
    if (maFlyFrames.size() != 1)
        return false;
 
    while ( maFlyIter != maFlyFrames.end() )
    {
        const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
 
        if (pSdrObj)
        {
            if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
                  return true;
        }
        ++maFlyIter;
    }
 
    return false;
}
 
bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos )
{
    ww8::FrameIter aTmpFlyIter = maFlyIter ;
 
    while ( aTmpFlyIter != maFlyFrames.end() )
    {
        const SwPosition &rAnchor  = maFlyIter->GetPosition();
        sal_uLong nAnchorPos = rAnchor.nNode.GetIndex();
        /* if current node position and the anchor position are the same
           then the frame anchor is linked to this node
        */
        if ( nAnchorPos == nNodePos )
            return true ;
 
        ++aTmpFlyIter;
    }
    return false ;
}
 
FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
{
    // collection point to first gather info about all of the potentially linked textboxes: to be analyzed later.
    OUString sLinkChainName;
    ww8::FrameIter linkedTextboxesIter = maFlyIter;
    while ( linkedTextboxesIter != maFlyFrames.end() )
    {
        uno::Reference< drawing::XShape > xShape;
        ww8::Frame aFrame = *linkedTextboxesIter;
        const SdrObject* pSdrObj = aFrame.GetFrameFormat().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 > xPropertySetInfo;
        if( xPropertySet.is() )
            xPropertySetInfo = xPropertySet->getPropertySetInfo();
        if( xPropertySetInfo.is() )
        {
            MSWordExportBase::LinkedTextboxInfo aLinkedTextboxInfo = MSWordExportBase::LinkedTextboxInfo();
 
            if( xPropertySetInfo->hasPropertyByName("LinkDisplayName") )
                xPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
            else if( xPropertySetInfo->hasPropertyByName("ChainName") )
                xPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
 
            if( xPropertySetInfo->hasPropertyByName("ChainNextName") )
                xPropertySet->getPropertyValue("ChainNextName") >>= aLinkedTextboxInfo.sNextChain;
            if( xPropertySetInfo->hasPropertyByName("ChainPrevName") )
                xPropertySet->getPropertyValue("ChainPrevName") >>= aLinkedTextboxInfo.sPrevChain;
 
            //collect a list of linked textboxes: those with a NEXT or PREVIOUS link
            if( !aLinkedTextboxInfo.sNextChain.isEmpty() || !aLinkedTextboxInfo.sPrevChain.isEmpty() )
            {
                assert( !sLinkChainName.isEmpty() );
 
                //there are many discarded duplicates in documents - no duplicates allowed in the list, so try to find the real one.
                //if this LinkDisplayName/ChainName already exists on a different shape...
                //  the earlier processed duplicates are thrown out unless this one can be proved as bad. (last processed duplicate usually is stored)
                std::map<OUString,MSWordExportBase::LinkedTextboxInfo>::iterator linkFinder;
                linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(sLinkChainName);
                if( linkFinder != m_rExport.m_aLinkedTextboxesHelper.end() )
                {
                    //If my NEXT/PREV targets have already been discovered, but don't match me, then assume I'm an abandoned remnant
                    //    (this logic fails if both me and one of my links are duplicated, and the remnants were added first.)
                    linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sNextChain);
                    if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sPrevChain != sLinkChainName) )
                    {
                        ++linkedTextboxesIter;
                        break;
                    }
 
                    linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sPrevChain);
                    if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sNextChain != sLinkChainName) )
                    {
                        ++linkedTextboxesIter;
                        break;
                    }
                }
                m_rExport.m_bLinkedTextboxesHelperInitialized = false;
                m_rExport.m_aLinkedTextboxesHelper[sLinkChainName] = aLinkedTextboxInfo;
            }
        }
        ++linkedTextboxesIter;
    }
 
    /*
     #i2916#
     May have an anchored graphic to be placed, loop through sorted array
     and output all at this position
    */
    while ( maFlyIter != maFlyFrames.end() )
    {
        const SwPosition &rAnchor = maFlyIter->GetPosition();
        const sal_Int32 nPos = rAnchor.nContent.GetIndex();
 
        if ( nPos != nSwPos )
            return FLY_NOT_PROCESSED ; // We haven't processed the fly
 
        const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
 
        if (pSdrObj)
        {
            if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
            {
                 // This is a watermark object. Should be written ONLY in the header
                 if(m_rExport.m_nTextTyp == TXT_HDFT)
                 {
                       // Should write a watermark in the header
                       m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
                 }
                 else
                 {
                       // Should not write watermark object in the main body text
                 }
            }
            else
            {
                 // This is not a watermark object - write normally
                 m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
            }
        }
        else
        {
            // This is not a watermark object - write normally
            m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
        }
        ++maFlyIter;
    }
    return ( m_rExport.AttrOutput().IsFlyProcessingPostponed() ? FLY_POSTPONED : FLY_PROCESSED ) ;
}
 
bool SwWW8AttrIter::IsTextAttr( sal_Int32 nSwPos ) const
{
    // search for attrs with dummy character or content
    if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
    {
        for (size_t i = 0; i < pTextAttrs->Count(); ++i)
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            if (nSwPos == pHt->GetStart())
            {
                if (pHt->HasDummyChar() || pHt->HasContent() )
                {
                    return true;
                }
            }
            else if (nSwPos < pHt->GetStart())
            {
                break; // sorted by start
            }
        }
    }
 
    return false;
}
 
bool SwWW8AttrIter::IsExportableAttr(sal_Int32 nSwPos) const
{
    if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
    {
        for (size_t i = 0; i < pTextAttrs->Count(); ++i)
        {
            const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
            const sal_Int32 nStart = pHt->GetStart();
            const sal_Int32 nEnd = pHt->End() ? *pHt->End() : INT_MAX;
            if (nSwPos >= nStart && nSwPos < nEnd)
            {
                switch (pHt->GetAttr().Which())
                {
                    // Metadata fields should be dynamically generated, not dumped as text.
                case RES_TXTATR_METAFIELD:
                    return false;
                }
            }
        }
    }
 
    return true;
}
 
bool SwWW8AttrIter::IsDropCap( int nSwPos )
{
    // see if the current position falls on a DropCap
    int nDropChars = mrSwFormatDrop.GetChars();
    bool bWholeWord = mrSwFormatDrop.GetWholeWord();
    if (bWholeWord)
    {
        const sal_Int32 nWordLen = rNd.GetDropLen(0);
        if(nSwPos == nWordLen && nSwPos != 0)
            return true;
    }
    else
    {
        if (nSwPos == nDropChars && nSwPos != 0)
            return true;
    }
    return false;
}
 
bool SwWW8AttrIter::RequiresImplicitBookmark()
{
    std::vector<aBookmarkPair>::iterator bkmkIterEnd = m_rExport.m_aImplicitBookmarks.end();
    for ( std::vector<aBookmarkPair>::iterator aIter = m_rExport.m_aImplicitBookmarks.begin(); aIter != bkmkIterEnd; ++aIter )
    {
        sal_uLong sample  = aIter->second;
 
        if ( sample == rNd.GetIndex() )
            return true;
    }
    return false;
}
 
//HasItem is for the summary of the double attributes: Underline and WordlineMode as TextItems.
// OutAttr () calls the output function, which can call HasItem() for other items at the attribute's start position.
// Only attributes with end can be queried.
// It searches with bDeep
const SfxPoolItem* SwWW8AttrIter::HasTextItem( sal_uInt16 nWhich ) const
{
    const SfxPoolItem* pRet = nullptr;
    const SwpHints* pTextAttrs = rNd.GetpSwpHints();
    if (pTextAttrs && !m_rExport.m_aCurrentCharPropStarts.empty())
    {
        const sal_Int32 nTmpSwPos = m_rExport.m_aCurrentCharPropStarts.top();
        for (size_t i = 0; i < pTextAttrs->Count(); ++i)
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            const SfxPoolItem* pItem = &pHt->GetAttr();
            const sal_Int32 * pAtrEnd = nullptr;
            if( nullptr != ( pAtrEnd = pHt->End() ) &&        // only Attr with an end
                nTmpSwPos >= pHt->GetStart() && nTmpSwPos < *pAtrEnd )
            {
                if ( nWhich == pItem->Which() )
                {
                    pRet = pItem;       // found it
                    break;
                }
                else if( RES_TXTATR_INETFMT == pHt->Which() ||
                         RES_TXTATR_CHARFMT == pHt->Which() ||
                         RES_TXTATR_AUTOFMT == pHt->Which() )
                {
                    const SfxItemSet* pSet = CharFormat::GetItemSet( pHt->GetAttr() );
                    const SfxPoolItem* pCharItem;
                    if ( pSet &&
                         SfxItemState::SET == pSet->GetItemState( nWhich, pHt->Which() != RES_TXTATR_AUTOFMT, &pCharItem ) )
                    {
                        pRet = pCharItem;       // found it
                        break;
                    }
                }
            }
            else if (nTmpSwPos < pHt->GetStart())
                break;              // nothing more to come
        }
    }
    return pRet;
}
 
void WW8Export::GetCurrentItems(ww::bytes &rItems) const
{
    rItems.insert(rItems.end(), pO->begin(), pO->end());
}
 
const SfxPoolItem& SwWW8AttrIter::GetItem(sal_uInt16 nWhich) const
{
    const SfxPoolItem* pRet = HasTextItem(nWhich);
    return pRet ? *pRet : rNd.SwContentNode::GetAttr(nWhich);
}
 
void WW8AttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 /*nPos*/, const SwFormatRuby& rRuby )
{
    WW8Ruby aWW8Ruby(rNode, rRuby, GetExport());
    OUString aStr( FieldString( ww::eEQ ) );
    aStr += "\\* jc";
    aStr += OUString::number(aWW8Ruby.GetJC());
 
 
    aStr += " \\* \"Font:";
    aStr += aWW8Ruby.GetFontFamily();
    aStr += "\" \\* hps";
    aStr += OUString::number((aWW8Ruby.GetRubyHeight() + 5) / 10);
    aStr += " \\o";
    if (aWW8Ruby.GetDirective())
    {
        aStr += "\\a" + OUString(aWW8Ruby.GetDirective());
    }
    aStr += "(\\s\\up ";
 
    aStr += OUString::number((aWW8Ruby.GetBaseHeight() + 10) / 20 - 1);
    aStr += "(";
    aStr += rRuby.GetText();
    aStr += ")";
 
    // The parameter separator depends on the FIB.lid
    if ( m_rWW8Export.pFib->getNumDecimalSep() == '.' )
        aStr += ",";
    else
        aStr += ";";
 
    m_rWW8Export.OutputField( nullptr, ww::eEQ, aStr,
            FieldFlags::Start | FieldFlags::CmdStart );
}
 
void WW8AttributeOutput::EndRuby(const SwTextNode& /*rNode*/, sal_Int32 /*nPos*/)
{
    m_rWW8Export.WriteChar( ')' );
    m_rWW8Export.OutputField( nullptr, ww::eEQ, OUString(), FieldFlags::End | FieldFlags::Close );
}
 
/*#i15387# Better ideas welcome*/
OUString &TruncateBookmark( OUString &rRet )
{
    if ( rRet.getLength() > 40 )
        rRet = rRet.copy( 0, 40 );
    OSL_ENSURE( rRet.getLength() <= 40, "Word cannot have bookmarks longer than 40 chars" );
    return rRet;
}
 
OUString AttributeOutputBase::ConvertURL( const OUString& rUrl, bool bAbsoluteOut )
{
    OUString sURL = rUrl;
    OUString sExportedDocumentURL = "";
    {
        DocxExport* pDocxExport = dynamic_cast<DocxExport*>(&GetExport());
        if ( pDocxExport )
        {
            // DOCX
            DocxExportFilter& rFilter = pDocxExport->GetFilter();
            sExportedDocumentURL = rFilter.getFileUrl();
        }
        else
        {
            // DOC
            WW8Export* pWW8Export = dynamic_cast<WW8Export*>(&GetExport());
            if ( pWW8Export )
            {
                SwWW8Writer& rWriter = pWW8Export->GetWriter();
                sExportedDocumentURL = rWriter.GetMedia()->GetURLObject().GetPath();
            }
        }
    }
 
    INetURLObject anAbsoluteParent( sExportedDocumentURL );
    if ( anAbsoluteParent.GetURLPath().isEmpty() )
    {
        // DOC filter returns system path (without file:///)
        anAbsoluteParent.setFSysPath( sExportedDocumentURL, FSysStyle::Detect );
        anAbsoluteParent.setFinalSlash();
    }
    OUString sConvertedParent = INetURLObject::GetScheme( anAbsoluteParent.GetProtocol() ) + anAbsoluteParent.GetURLPath();
    OUString sParentPath = sConvertedParent.isEmpty() ? sExportedDocumentURL : sConvertedParent;
 
    if ( bAbsoluteOut )
    {
        INetURLObject anAbsoluteNew;
 
        if ( anAbsoluteParent.GetNewAbsURL( rUrl, &anAbsoluteNew ) )
            sURL = anAbsoluteNew.GetMainURL( INetURLObject::DecodeMechanism::NONE );
        else
            sURL = rUrl;
    }
    else
    {
        OUString sToConvert = rUrl.replaceAll( "\\", "/" );
        INetURLObject aURL( sToConvert );
        sToConvert = INetURLObject::GetScheme( aURL.GetProtocol() ) + aURL.GetURLPath();
        OUString sRelative = INetURLObject::GetRelURL( sParentPath, sToConvert, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::NONE );
        if ( !sRelative.isEmpty() )
            sURL = sRelative;
    }
 
    return sURL;
}
 
bool AttributeOutputBase::AnalyzeURL( const OUString& rUrl, const OUString& /*rTarget*/, OUString* pLinkURL, OUString* pMark )
{
    bool bBookMarkOnly = false;
 
    OUString sMark;
    OUString sURL;
 
    if ( rUrl.getLength() > 1 && rUrl[0] == '#' )
    {
        sMark = BookmarkToWriter( rUrl.copy(1) );
 
        const sal_Int32 nPos = sMark.lastIndexOf( cMarkSeparator );
 
        const OUString sRefType(nPos>=0 && nPos+1<sMark.getLength() ?
                                sMark.copy(nPos+1).replaceAll(" ", "") :
                                OUString());
 
        // #i21465# Only interested in outline references
        if ( sRefType == "outline" )
        {
            OUString sLink = sMark.copy(0, nPos);
            std::vector<aBookmarkPair>::iterator bkmkIterEnd = GetExport().m_aImplicitBookmarks.end();
            for ( std::vector<aBookmarkPair>::iterator aIter = GetExport().m_aImplicitBookmarks.begin(); aIter != bkmkIterEnd; ++aIter )
            {
                if ( aIter->first == sLink )
                {
                    sMark = "_toc" + OUString::number( aIter->second );
                }
            }
        }
    }
    else
    {
        INetURLObject aURL( rUrl, INetProtocol::NotValid );
        sURL = aURL.GetURLNoMark( INetURLObject::DecodeMechanism::Unambiguous );
        sMark = aURL.GetMark( INetURLObject::DecodeMechanism::Unambiguous );
        INetProtocol aProtocol = aURL.GetProtocol();
 
        if ( aProtocol == INetProtocol::File || aProtocol == INetProtocol::NotValid )
        {
            // INetProtocol::NotValid - may be a relative link
            bool bExportRelative = m_aSaveOpt.IsSaveRelFSys();
            sURL = ConvertURL( rUrl, !bExportRelative );
        }
    }
 
    if ( !sMark.isEmpty() && sURL.isEmpty() )
        bBookMarkOnly = true;
 
    *pMark = sMark;
    *pLinkURL = sURL;
    return bBookMarkOnly;
}
 
bool WW8AttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
{
    bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
 
    OUString sURL = *pLinkURL;
 
    if ( !sURL.isEmpty() )
        sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
 
    if ( bBookMarkOnly )
        sURL = FieldString( ww::eHYPERLINK );
    else
        sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
 
    if ( !pMark->isEmpty() )
        sURL += " \\l \"" + *pMark + "\"";
 
    if ( !rTarget.isEmpty() )
        sURL += " \\n " + rTarget;
 
    *pLinkURL = sURL;
 
    return bBookMarkOnly;
}
 
void WW8AttributeOutput::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 WW8AttributeOutput::StartURL( const OUString &rUrl, const OUString &rTarget )
{
    INetURLObject aURL( rUrl );
    OUString sURL;
    OUString sMark;
 
    bool bBookMarkOnly = AnalyzeURL( rUrl, rTarget, &sURL, &sMark );
 
    m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::Start | FieldFlags::CmdStart );
 
    // write the reference to the "picture" structure
    sal_uLong nDataStt = m_rWW8Export.pDataStrm->Tell();
    m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell() );
 
    // WinWord 2000 doesn't write this - so it's a temp solution by W97 ?
    m_rWW8Export.WriteChar( 0x01 );
 
    static sal_uInt8 aArr1[] = {
        0x03, 0x6a, 0,0,0,0,    // sprmCPicLocation
 
        0x06, 0x08, 0x01,       // sprmCFData
        0x55, 0x08, 0x01,       // sprmCFSpec
        0x02, 0x08, 0x01        // sprmCFFieldVanish
    };
    sal_uInt8* pDataAdr = aArr1 + 2;
    Set_UInt32( pDataAdr, nDataStt );
 
    m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), sizeof( aArr1 ), aArr1 );
 
    m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::CmdEnd );
 
    // now write the picture structure
    sURL = aURL.GetURLNoMark();
 
    // Compare the URL written by AnalyzeURL with the original one to see if
    // the output URL is absolute or relative.
    OUString sRelativeURL;
    if ( !rUrl.isEmpty() )
        sRelativeURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), rUrl );
    bool bAbsolute = sRelativeURL == rUrl;
 
    static sal_uInt8 aURLData1[] = {
        0,0,0,0,        // len of struct
        0x44,0,         // the start of "next" data
        0,0,0,0,0,0,0,0,0,0,                // PIC-Structure!
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,    //  |
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,    //  |
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,    //  |
        0,0,0,0,                            // /
    };
    static sal_uInt8 MAGIC_A[] = {
        // start of "next" data
        0xD0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
        0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
    };
 
    m_rWW8Export.pDataStrm->WriteBytes(aURLData1, sizeof(aURLData1));
    /* Write HFD Structure */
    sal_uInt8 nAnchor = 0x00;
    if ( !sMark.isEmpty() )
        nAnchor = 0x08;
    m_rWW8Export.pDataStrm->WriteUChar(nAnchor); // HFDBits
    m_rWW8Export.pDataStrm->WriteBytes(MAGIC_A, sizeof(MAGIC_A)); //clsid
 
    /* Write Hyperlink Object see [MS-OSHARED] spec*/
    SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 0x00000002);
    sal_uInt32 nFlag = bBookMarkOnly ? 0 : 0x01;
    if ( bAbsolute )
        nFlag |= 0x02;
    if ( !sMark.isEmpty() )
        nFlag |= 0x08;
    SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, nFlag );
 
    INetProtocol eProto = aURL.GetProtocol();
    if ( eProto == INetProtocol::File || eProto == INetProtocol::Smb )
    {
        // version 1 (for a document)
 
        static sal_uInt8 MAGIC_C[] = {
            0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
            0x00, 0x00
        };
 
        static sal_uInt8 MAGIC_D[] = {
            0xFF, 0xFF, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        };
 
        // save the links to files as relative
        sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
        if ( eProto == INetProtocol::File && sURL.startsWith( "/" ) )
            sURL = aURL.PathToFileName();
 
        // special case for the absolute windows names
        // (convert '/c:/foo/bar.doc' into 'c:\foo\bar.doc')
        if (sURL.getLength()>=3)
        {
            const sal_Unicode aDrive = sURL[1];
            if ( sURL[0]=='/' && sURL[2]==':' &&
                 ( (aDrive>='A' && aDrive<='Z' ) || (aDrive>='a' && aDrive<='z') ) )
            {
                sURL = sURL.copy(1).replaceAll("/", "\\");
            }
        }
 
        // n#261623 convert smb notation to '\\'
        const char pSmb[] = "smb://";
        if ( eProto == INetProtocol::Smb && sURL.startsWith( pSmb ) )
        {
            sURL = sURL.copy( sizeof(pSmb)-3 ).replaceAll( "/", "\\" );
        }
 
        m_rWW8Export.pDataStrm->WriteBytes(MAGIC_C, sizeof(MAGIC_C));
        SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, sURL.getLength()+1 );
        SwWW8Writer::WriteString8( *m_rWW8Export.pDataStrm, sURL, true,
                                    RTL_TEXTENCODING_MS_1252 );
        m_rWW8Export.pDataStrm->WriteBytes(MAGIC_D, sizeof(MAGIC_D));
 
        SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2*sURL.getLength() + 6 );
        SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2*sURL.getLength() );
        SwWW8Writer::WriteShort( *m_rWW8Export.pDataStrm, 3 );
        SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sURL, false );
    }
    else if ( eProto != INetProtocol::NotValid )
    {
        // version 2 (simple url)
        // and write some data to the data stream, but don't ask
        // what the data mean, except for the URL.
        // The First piece is the WW8_PIC structure.
        static sal_uInt8 MAGIC_B[] = {
            0xE0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
            0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
        };
 
        m_rWW8Export.pDataStrm->WriteBytes(MAGIC_B, sizeof(MAGIC_B));
        SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2 * ( sURL.getLength() + 1 ) );
        SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sURL, true );
    }
 
    if ( !sMark.isEmpty() )
    {
        SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, sMark.getLength()+1 );
        SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sMark, true );
    }
    SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, nDataStt,
        m_rWW8Export.pDataStrm->Tell() - nDataStt );
 
    return true;
}
 
bool WW8AttributeOutput::EndURL(bool const)
{
    m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, OUString(), FieldFlags::Close );
 
    return true;
}
 
OUString BookmarkToWord(const OUString &rBookmark)
{
    OUString sRet(INetURLObject::encode(
        rBookmark.replace(' ', '_'), // Spaces are prohibited in bookmark name
        INetURLObject::PART_REL_SEGMENT_EXTRA,
        INetURLObject::EncodeMechanism::All, RTL_TEXTENCODING_ASCII_US));
    return TruncateBookmark(sRet);
}
 
OUString BookmarkToWriter(const OUString &rBookmark)
{
    return INetURLObject::decode(rBookmark,
        INetURLObject::DecodeMechanism::Unambiguous, RTL_TEXTENCODING_ASCII_US);
}
 
void SwWW8AttrIter::OutSwFormatRefMark(const SwFormatRefMark& rAttr)
{
    if ( m_rExport.HasRefToObject( REF_SETREFATTR, &rAttr.GetRefName(), 0 ) )
        m_rExport.AppendBookmark( MSWordExportBase::GetBookmarkName( REF_SETREFATTR,
                                            &rAttr.GetRefName(), 0 ));
}
 
void SwWW8AttrIter::SplitRun( sal_Int32 nSplitEndPos )
{
    for(auto aIter = maCharRuns.begin(); aIter != maCharRuns.end(); ++aIter)
    {
        if(aIter->mnEndPos == nSplitEndPos)
            return;
        else if (aIter->mnEndPos > nSplitEndPos)
        {
            CharRunEntry aNewEntry = *aIter;
            aIter->mnEndPos = nSplitEndPos;
            maCharRuns.insert( ++aIter, aNewEntry);
            maCharRunIter = maCharRuns.begin();
            IterToCurrent();
            nCurrentSwPos = SearchNext(1);
            break;
        }
    }
}
 
void WW8AttributeOutput::FieldVanish( const OUString& rText, ww::eField /*eType*/ )
{
    ww::bytes aItems;
    m_rWW8Export.GetCurrentItems( aItems );
 
    // sprmCFFieldVanish
    SwWW8Writer::InsUInt16( aItems, NS_sprm::sprmCFFldVanish );
    aItems.push_back( 1 );
 
    sal_uInt16 nStt_sprmCFSpec = aItems.size();
 
    // sprmCFSpec --  fSpec-Attribut true
    SwWW8Writer::InsUInt16( aItems, 0x855 );
    aItems.push_back( 1 );
 
    m_rWW8Export.WriteChar( '\x13' );
    m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
                                    aItems.data() );
    m_rWW8Export.OutSwString(rText, 0, rText.getLength());
    m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), nStt_sprmCFSpec,
                                    aItems.data() );
    m_rWW8Export.WriteChar( '\x15' );
    m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
                                    aItems.data() );
}
 
void AttributeOutputBase::TOXMark( const SwTextNode& rNode, const SwTOXMark& rAttr )
{
    // it's a field; so get the Text form the Node and build the field
    OUString sText;
    ww::eField eType = ww::eNONE;
 
    const SwTextTOXMark& rTextTOXMark = *rAttr.GetTextTOXMark();
    const sal_Int32* pTextEnd = rTextTOXMark.End();
    if ( pTextEnd ) // has range?
    {
        sText = rNode.GetExpandText( rTextTOXMark.GetStart(),
                                   *pTextEnd - rTextTOXMark.GetStart() );
    }
    else
        sText = rAttr.GetAlternativeText();
 
    switch ( rAttr.GetTOXType()->GetType() )
    {
        case TOX_INDEX:
            eType = ww::eXE;
            if ( !rAttr.GetPrimaryKey().isEmpty() )
            {
                if ( !rAttr.GetSecondaryKey().isEmpty() )
                {
                    sText = rAttr.GetSecondaryKey() + ":" + sText;
                }
 
                sText = rAttr.GetPrimaryKey() + ":" + sText;
            }
            sText = " XE \"" + sText + "\" ";
            break;
 
        case TOX_USER:
            sText += "\" \\f \"" + OUString(static_cast<sal_Char>( 'A' + GetExport( ).GetId( *rAttr.GetTOXType() ) ));
            SAL_FALLTHROUGH;
        case TOX_CONTENT:
            {
                eType = ww::eTC;
                sText = " TC \"" + sText;
                sal_uInt16 nLvl = rAttr.GetLevel();
                if (nLvl > WW8ListManager::nMaxLevel)
                    nLvl = WW8ListManager::nMaxLevel;
 
                sText += "\" \\l " + OUString::number(nLvl) + " ";
            }
            break;
        default:
            OSL_ENSURE( false, "Unhandled option for toc export" );
            break;
    }
 
    if (!sText.isEmpty())
        FieldVanish( sText, eType );
}
 
int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& rNode, sal_Int32 nPos)
{
    int nRet = 0;
    if ( const SwpHints* pTextAttrs = rNd.GetpSwpHints() )
    {
        m_rExport.m_aCurrentCharPropStarts.push( nPos );
        const sal_Int32* pEnd;
        // first process ends of attributes with extent
        for (size_t i = 0; i < pTextAttrs->Count(); ++i)
        {
            const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
            const SfxPoolItem* pItem = &pHt->GetAttr();
            switch ( pItem->Which() )
            {
                case RES_TXTATR_INETFMT:
                    pEnd = pHt->End();
                    if (nPos == *pEnd && nPos != pHt->GetStart())
                    {
                        if (m_rExport.AttrOutput().EndURL(nPos == rNd.Len()))
                            --nRet;
                    }
                    break;
                case RES_TXTATR_REFMARK:
                    pEnd = pHt->End();
                    if (nullptr != pEnd && nPos == *pEnd && nPos != pHt->GetStart())
                    {
                        OutSwFormatRefMark(*static_cast<const SwFormatRefMark*>(pItem));
                        --nRet;
                    }
                    break;
                case RES_TXTATR_CJK_RUBY:
                    pEnd = pHt->End();
                    if (nPos == *pEnd && nPos != pHt->GetStart())
                    {
                        m_rExport.AttrOutput().EndRuby(rNode, nPos);
                        --nRet;
                    }
                    break;
            }
            if (nPos < *pHt->GetAnyEnd())
                break; // sorted by end
        }
        for ( size_t i = 0; i < pTextAttrs->Count(); ++i )
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            const SfxPoolItem* pItem = &pHt->GetAttr();
            switch ( pItem->Which() )
            {
                case RES_TXTATR_INETFMT:
                    if ( nPos == pHt->GetStart() )
                    {
                        const SwFormatINetFormat *rINet = static_cast< const SwFormatINetFormat* >( pItem );
                        if ( m_rExport.AttrOutput().StartURL( rINet->GetValue(), rINet->GetTargetFrame() ) )
                            ++nRet;
                    }
                    pEnd = pHt->End();
                    if (nPos == *pEnd && nPos == pHt->GetStart())
                    {   // special case: empty must be handled here
                        if (m_rExport.AttrOutput().EndURL(nPos == rNd.Len()))
                            --nRet;
                    }
                    break;
                case RES_TXTATR_REFMARK:
                    if ( nPos == pHt->GetStart() )
                    {
                        OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
                        ++nRet;
                    }
                    pEnd = pHt->End();
                    if (nullptr != pEnd && nPos == *pEnd && nPos == pHt->GetStart())
                    {   // special case: empty TODO: is this possible or would empty one have pEnd null?
                        OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
                        --nRet;
                    }
                    break;
                case RES_TXTATR_TOXMARK:
                    if ( nPos == pHt->GetStart() )
                        m_rExport.AttrOutput().TOXMark( rNd, *static_cast< const SwTOXMark* >( pItem ) );
                    break;
                case RES_TXTATR_CJK_RUBY:
                    if ( nPos == pHt->GetStart() )
                    {
                        m_rExport.AttrOutput().StartRuby( rNd, nPos, *static_cast< const SwFormatRuby* >( pItem ) );
                        ++nRet;
                    }
                    pEnd = pHt->End();
                    if (nPos == *pEnd && nPos == pHt->GetStart())
                    {   // special case: empty must be handled here
                        m_rExport.AttrOutput().EndRuby( rNd, nPos );
                        --nRet;
                    }
                    break;
            }
            if (nPos < pHt->GetStart())
                break; // sorted by start
        }
        m_rExport.m_aCurrentCharPropStarts.pop(); // HasTextItem only allowed in the above range
    }
    return nRet;
}
 
bool SwWW8AttrIter::IncludeEndOfParaCRInRedlineProperties( sal_Int32 nEnd ) const
{
    // search next Redline
    for( SwRedlineTable::size_type nPos = nCurRedlinePos;
        nPos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); ++nPos )
    {
        const SwRangeRedline *pRange = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[nPos];
        const SwPosition* pEnd = pRange->End();
        const SwPosition* pStart = pRange->Start();
        bool bBreak = true;
        // In word the paragraph end marker is a real character, in writer it is not.
        // Here we find out if the para end marker we will emit is affected by
        // redlining, in which case it must be included by the range of character
        // attributes that contains the redlining information.
        if (pEnd->nNode == rNd)
        {
            if (pEnd->nContent.GetIndex() == nEnd)
            {
                // This condition detects if the pseudo-char we will export
                // should be explicitly included by the redlining char
                // properties on this node because the redlining ends right
                // after it
                return true;
            }
            bBreak = false;
        }
        if (pStart->nNode == rNd)
        {
            if (pStart->nContent.GetIndex() == nEnd)
            {
                // This condition detects if the pseudo-char we will export
                // should be explicitly included by the redlining char
                // properties on this node because the redlining starts right
                // before it
                return true;
            }
            bBreak = false;
        }
        if (pStart->nNode.GetIndex()-1 == rNd.GetIndex())
        {
            if (pStart->nContent.GetIndex() == 0)
            {
                // This condition detects if the pseudo-char we will export
                // should be implicitly excluded by the redlining char
                // properties starting on the next node.
                return true;
            }
            bBreak = false;
        }
 
        if (bBreak)
            break;
    }
    return false;
}
 
const SwRedlineData* SwWW8AttrIter::GetParagraphLevelRedline( )
{
    pCurRedline = nullptr;
 
    // ToDo : this is not the most ideal ... should start maybe from 'nCurRedlinePos'
    for(SwRangeRedline* pRedl : m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable())
    {
        const SwPosition* pCheckedStt = pRedl->Start();
 
        if( pCheckedStt->nNode == rNd )
        {
            // Maybe add here a check that also the start & end of the redline is the entire paragraph
 
            // Only return if this is a paragraph formatting redline
            if (pRedl->GetType() == nsRedlineType_t::REDLINE_PARAGRAPH_FORMAT)
            {
                // write data of this redline
                pCurRedline = pRedl;
                return &( pCurRedline->GetRedlineData() );
            }
        }
    }
    return nullptr;
}
 
const SwRedlineData* SwWW8AttrIter::GetRunLevelRedline( sal_Int32 nPos )
{
    if( pCurRedline )
    {
        const SwPosition* pEnd = pCurRedline->End();
        if( pEnd->nNode == rNd &&
            pEnd->nContent.GetIndex() <= nPos )
        {
            pCurRedline = nullptr;
            ++nCurRedlinePos;
        }
        else
        {
            switch( pCurRedline->GetType() )
            {
                case nsRedlineType_t::REDLINE_INSERT:
                case nsRedlineType_t::REDLINE_DELETE:
                case nsRedlineType_t::REDLINE_FORMAT:
                    // write data of this redline
                    return &( pCurRedline->GetRedlineData() );
                    break;
                default:
                    break;
            }
            pCurRedline = nullptr;
            ++nCurRedlinePos;
        }
    }
 
    if( !pCurRedline )
    {
        // search next Redline
        for( ; nCurRedlinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size();
                ++nCurRedlinePos )
        {
            const SwRangeRedline* pRedl = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nCurRedlinePos ];
 
            const SwPosition* pStt = pRedl->Start();
            const SwPosition* pEnd = pStt == pRedl->GetPoint()
                                        ? pRedl->GetMark()
                                        : pRedl->GetPoint();
 
            if( pStt->nNode == rNd )
            {
                if( pStt->nContent.GetIndex() >= nPos )
                {
                    if( pStt->nContent.GetIndex() == nPos )
                    {
                            switch( pRedl->GetType() )
                            {
                                case nsRedlineType_t::REDLINE_INSERT:
                                case nsRedlineType_t::REDLINE_DELETE:
                                case nsRedlineType_t::REDLINE_FORMAT:
                                    // write data of this redline
                                    pCurRedline = pRedl;
                                    return &( pCurRedline->GetRedlineData() );
                                    break;
                                default:
                                    break;
                            }
                    }
                    break;
                }
            }
            else
            {
                break;
            }
 
            if( pEnd->nNode == rNd &&
                pEnd->nContent.GetIndex() < nPos )
            {
                pCurRedline = pRedl;
                break;
            }
        }
    }
    return nullptr;
}
 
SvxFrameDirection MSWordExportBase::GetCurrentPageDirection() const
{
    const SwFrameFormat &rFormat = m_pCurrentPageDesc
                    ? m_pCurrentPageDesc->GetMaster()
                    : m_pDoc->GetPageDesc( 0 ).GetMaster();
    return rFormat.GetFrameDir().GetValue();
}
 
SvxFrameDirection MSWordExportBase::GetDefaultFrameDirection( ) const
{
    SvxFrameDirection nDir = SvxFrameDirection::Environment;
 
    if ( m_bOutPageDescs )
        nDir = GetCurrentPageDirection(  );
    else if ( m_pOutFormatNode )
    {
        if ( m_bOutFlyFrameAttrs ) //frame
        {
            nDir = TrueFrameDirection( *static_cast< const SwFrameFormat * >(m_pOutFormatNode) );
        }
        else if ( dynamic_cast< const SwContentNode *>( m_pOutFormatNode ) !=  nullptr )    //pagagraph
        {
            const SwContentNode *pNd = static_cast<const SwContentNode *>(m_pOutFormatNode);
            SwPosition aPos( *pNd );
            nDir = m_pDoc->GetTextDirection( aPos );
        }
        else if ( dynamic_cast< const SwTextFormatColl *>( m_pOutFormatNode ) !=  nullptr )
        {
            if ( MsLangId::isRightToLeft( GetAppLanguage()) )
                nDir = SvxFrameDirection::Horizontal_RL_TB;
            else
                nDir = SvxFrameDirection::Horizontal_LR_TB;    //what else can we do :-(
        }
    }
 
    if ( nDir == SvxFrameDirection::Environment )
    {
        // fdo#44029 put direction right when the locale are RTL.
        if( MsLangId::isRightToLeft( GetAppLanguage()) )
            nDir = SvxFrameDirection::Horizontal_RL_TB;
        else
            nDir = SvxFrameDirection::Horizontal_LR_TB;        //Set something
    }
 
    return nDir;
}
 
SvxFrameDirection MSWordExportBase::TrueFrameDirection( const SwFrameFormat &rFlyFormat ) const
{
    const SwFrameFormat *pFlyFormat = &rFlyFormat;
    const SvxFrameDirectionItem* pItem = nullptr;
    while ( pFlyFormat )
    {
        pItem = &pFlyFormat->GetFrameDir();
        if ( SvxFrameDirection::Environment == pItem->GetValue() )
        {
            pItem = nullptr;
            const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
            if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
                pAnchor->GetContentAnchor() )
            {
                pFlyFormat = pAnchor->GetContentAnchor()->nNode.GetNode().GetFlyFormat();
            }
            else
                pFlyFormat = nullptr;
        }
        else
            pFlyFormat = nullptr;
    }
 
    SvxFrameDirection nRet;
    if ( pItem )
        nRet = pItem->GetValue();
    else
        nRet = GetCurrentPageDirection();
 
    OSL_ENSURE( nRet != SvxFrameDirection::Environment, "leaving with environment direction" );
    return nRet;
}
 
const SvxBrushItem* WW8Export::GetCurrentPageBgBrush() const
{
    const SwFrameFormat  &rFormat = m_pCurrentPageDesc
                    ? m_pCurrentPageDesc->GetMaster()
                    : m_pDoc->GetPageDesc(0).GetMaster();
 
    const SfxPoolItem* pItem = nullptr;
    //If not set, or "no fill", get real bg
    SfxItemState eState = rFormat.GetItemState(RES_BACKGROUND, true, &pItem);
 
    const SvxBrushItem* pRet = static_cast<const SvxBrushItem*>(pItem);
    if (SfxItemState::SET != eState || !pRet || (!pRet->GetGraphic() &&
        pRet->GetColor() == COL_TRANSPARENT))
    {
        pRet = &(DefaultItemGet<SvxBrushItem>(*m_pDoc,RES_BACKGROUND));
    }
    return pRet;
}
 
SvxBrushItem WW8Export::TrueFrameBgBrush(const SwFrameFormat &rFlyFormat) const
{
    const SwFrameFormat *pFlyFormat = &rFlyFormat;
    const SvxBrushItem* pRet = nullptr;
 
    while (pFlyFormat)
    {
        //If not set, or "no fill", get real bg
        const SfxPoolItem* pItem = nullptr;
        SfxItemState eState =
            pFlyFormat->GetItemState(RES_BACKGROUND, true, &pItem);
        pRet = static_cast<const SvxBrushItem*>(pItem);
        if (SfxItemState::SET != eState || !pRet || (!pRet->GetGraphic() &&
            pRet->GetColor() == COL_TRANSPARENT))
        {
            pRet = nullptr;
            const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
            if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
                pAnchor->GetContentAnchor())
            {
                pFlyFormat =
                    pAnchor->GetContentAnchor()->nNode.GetNode().GetFlyFormat();
            }
            else
                pFlyFormat = nullptr;
        }
        else
            pFlyFormat = nullptr;
    }
 
    if (!pRet)
        pRet = GetCurrentPageBgBrush();
 
    const Color aTmpColor( COL_WHITE );
    SvxBrushItem aRet( aTmpColor, RES_BACKGROUND );
    if (pRet && (pRet->GetGraphic() ||( pRet->GetColor() != COL_TRANSPARENT)))
        aRet = *pRet;
 
    return aRet;
}
 
/*
Convert characters that need to be converted, the basic replacements and the
ridiculously complicated title case attribute mapping to hardcoded upper case
because word doesn't have the feature
*/
OUString SwWW8AttrIter::GetSnippet(const OUString &rStr, sal_Int32 nCurrentPos,
    sal_Int32 nLen) const
{
    if (!nLen)
        return OUString();
 
    OUString aSnippet(rStr.copy(nCurrentPos, nLen));
    // 0x0a     ( Hard Line Break ) -> 0x0b
    // 0xad     ( soft hyphen )     -> 0x1f
    // 0x2011   ( hard hyphen )     -> 0x1e
    aSnippet = aSnippet.replace(0x0A, 0x0B);
    aSnippet = aSnippet.replace(CHAR_HARDHYPHEN, 0x1e);
    aSnippet = aSnippet.replace(CHAR_SOFTHYPHEN, 0x1f);
 
    m_rExport.m_aCurrentCharPropStarts.push( nCurrentPos );
    const SfxPoolItem &rItem = GetItem(RES_CHRATR_CASEMAP);
 
    if (SvxCaseMap::Capitalize == static_cast<const SvxCaseMapItem&>(rItem).GetValue())
    {
        assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
        sal_uInt16 nScriptType = g_pBreakIt->GetBreakIter()->getScriptType(aSnippet, 0);
 
        LanguageType nLanguage;
        switch (nScriptType)
        {
        case i18n::ScriptType::ASIAN:
                nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CJK_LANGUAGE)).GetLanguage();
                break;
        case i18n::ScriptType::COMPLEX:
                nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CTL_LANGUAGE)).GetLanguage();
                break;
        case i18n::ScriptType::LATIN:
            default:
                nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_LANGUAGE)).GetLanguage();
                break;
        }
 
        SvxFont aFontHelper;
        aFontHelper.SetCaseMap(SvxCaseMap::Capitalize);
        aFontHelper.SetLanguage(nLanguage);
        aSnippet = aFontHelper.CalcCaseMap(aSnippet);
 
        //If we weren't at the begin of a word undo the case change.
        //not done before doing the casemap because the sequence might start
        //with whitespace
        if (!g_pBreakIt->GetBreakIter()->isBeginWord(
            rStr, nCurrentPos, g_pBreakIt->GetLocale(nLanguage),
            i18n::WordType::ANYWORD_IGNOREWHITESPACES ) )
        {
            aSnippet = OUStringLiteral1(rStr[nCurrentPos]) + aSnippet.copy(1);
        }
    }
    m_rExport.m_aCurrentCharPropStarts.pop();
 
    return aSnippet;
}
 
/** Delivers the right paragraph style
 
    Because of the different style handling for delete operations,
    the track changes have to be analysed. A deletion, starting in paragraph A
    with style A, ending in paragraph B with style B, needs a hack.
*/
static SwTextFormatColl& lcl_getFormatCollection( MSWordExportBase& rExport, const SwTextNode* pTextNode )
{
    SwRedlineTable::size_type nPos = 0;
    SwRedlineTable::size_type nMax = rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size();
    while( nPos < nMax )
    {
        const SwRangeRedline* pRedl = rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nPos++ ];
        const SwPosition* pStt = pRedl->Start();
        const SwPosition* pEnd = pStt == pRedl->GetPoint()
                                    ? pRedl->GetMark()
                                    : pRedl->GetPoint();
        // Looking for deletions, which ends in current pTextNode
        if( nsRedlineType_t::REDLINE_DELETE == pRedl->GetRedlineData().GetType() &&
            pEnd->nNode == *pTextNode && pStt->nNode != *pTextNode &&
            pStt->nNode.GetNode().IsTextNode() )
        {
            pTextNode = pStt->nNode.GetNode().GetTextNode();
            nMax = nPos;
            nPos = 0;
        }
    }
    return static_cast<SwTextFormatColl&>( pTextNode->GetAnyFormatColl() );
}
 
void WW8AttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop &rSwFormatDrop, sal_uInt16 nStyle,
        ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
{
    short nDropLines = rSwFormatDrop.GetLines();
    short nDistance = rSwFormatDrop.GetDistance();
    int rFontHeight, rDropHeight, rDropDescent;
 
    SVBT16 nSty;
    ShortToSVBT16( nStyle, nSty );
    m_rWW8Export.pO->insert( m_rWW8Export.pO->end(), nSty, nSty+2 );     // Style #
 
    m_rWW8Export.InsUInt16( NS_sprm::sprmPPc );            // Alignment (sprmPPc)
    m_rWW8Export.pO->push_back( 0x20 );
 
    m_rWW8Export.InsUInt16( NS_sprm::sprmPWr );            // Wrapping (sprmPWr)
    m_rWW8Export.pO->push_back( 0x02 );
 
    m_rWW8Export.InsUInt16( NS_sprm::sprmPDcs );            // Dropcap (sprmPDcs)
    int nDCS = ( nDropLines << 3 ) | 0x01;
    m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( nDCS ) );
 
    m_rWW8Export.InsUInt16( NS_sprm::sprmPDxaFromText );            // Distance from text (sprmPDxaFromText)
    m_rWW8Export.InsUInt16( nDistance );
 
    if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
    {
        m_rWW8Export.InsUInt16( NS_sprm::sprmPDyaLine );            // Line spacing
        m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -rDropHeight ) );
        m_rWW8Export.InsUInt16( 0 );
    }
 
    m_rWW8Export.WriteCR( pTextNodeInfoInner );
 
    if ( pTextNodeInfo.get() != nullptr )
    {
#ifdef DBG_UTIL
        SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
#endif
        TableInfoCell( pTextNodeInfoInner );
    }
 
    m_rWW8Export.m_pPapPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.pO->size(), m_rWW8Export.pO->data() );
    m_rWW8Export.pO->clear();
 
    if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
    {
        const SwCharFormat *pSwCharFormat = rSwFormatDrop.GetCharFormat();
        if ( pSwCharFormat )
        {
            m_rWW8Export.InsUInt16( NS_sprm::sprmCIstd );
            m_rWW8Export.InsUInt16( m_rWW8Export.GetId( pSwCharFormat ) );
        }
 
        m_rWW8Export.InsUInt16( NS_sprm::sprmCHpsPos );            // Lower the chars
        m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -((nDropLines - 1)*rDropDescent) / 10 ) );
 
        m_rWW8Export.InsUInt16( NS_sprm::sprmCHps );            // Font Size
        m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( rFontHeight / 10 ) );
    }
 
    m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.pO->size(), m_rWW8Export.pO->data() );
    m_rWW8Export.pO->clear();
}
 
sal_Int32 MSWordExportBase::GetNextPos( SwWW8AttrIter const * aAttrIter, const SwTextNode& rNode, sal_Int32 nCurrentPos )
{
    // Get the bookmarks for the normal run
    const sal_Int32 nNextPos = aAttrIter->WhereNext();
    sal_Int32 nNextBookmark = nNextPos;
    sal_Int32 nNextAnnotationMark = nNextPos;
 
    if( nNextBookmark > nCurrentPos ) //no need to search for bookmarks otherwise (checked in UpdatePosition())
    {
        GetSortedBookmarks( rNode, nCurrentPos, nNextBookmark - nCurrentPos );
        NearestBookmark( nNextBookmark, nCurrentPos, false );
        GetSortedAnnotationMarks( rNode, nCurrentPos, nNextAnnotationMark - nCurrentPos );
        NearestAnnotationMark( nNextAnnotationMark, nCurrentPos, false );
    }
    return std::min( nNextPos, std::min( nNextBookmark, nNextAnnotationMark ) );
}
 
void MSWordExportBase::UpdatePosition( SwWW8AttrIter* aAttrIter, sal_Int32 nCurrentPos )
{
    sal_Int32 nNextPos;
 
    // go to next attribute if no bookmark is found or if the bookmark is after the next attribute position
    // It may happened that the WhereNext() wasn't used in the previous increment because there was a
    // bookmark before it. Use that position before trying to find another one.
    bool bNextBookmark = NearestBookmark( nNextPos, nCurrentPos, true );
    if( nCurrentPos == aAttrIter->WhereNext() && ( !bNextBookmark || nNextPos > aAttrIter->WhereNext() ) )
        aAttrIter->NextPos();
}
 
bool MSWordExportBase::GetBookmarks( const SwTextNode& rNd, sal_Int32 nStt,
                    sal_Int32 nEnd, IMarkVector& rArr )
{
    IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
    sal_uLong nNd = rNd.GetIndex( );
 
    const sal_Int32 nMarks = pMarkAccess->getAllMarksCount();
    for ( sal_Int32 i = 0; i < nMarks; i++ )
    {
        IMark* pMark = ( pMarkAccess->getAllMarksBegin() + i )->get();
 
        if ( IDocumentMarkAccess::GetType( *pMark ) == IDocumentMarkAccess::MarkType::ANNOTATIONMARK )
        {
            continue;
        }
 
        // Only keep the bookmarks starting or ending in this node
        if ( pMark->GetMarkStart().nNode == nNd ||
             pMark->GetMarkEnd().nNode == nNd )
        {
            const sal_Int32 nBStart = pMark->GetMarkStart().nContent.GetIndex();
            const sal_Int32 nBEnd = pMark->GetMarkEnd().nContent.GetIndex();
 
            // Keep only the bookmars starting or ending in the snippet
            bool bIsStartOk = ( pMark->GetMarkStart().nNode == nNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
            bool bIsEndOk = ( pMark->GetMarkEnd().nNode == nNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
 
            if ( bIsStartOk || bIsEndOk )
            {
                rArr.push_back( pMark );
            }
        }
    }
    return ( rArr.size() > 0 );
}
 
bool MSWordExportBase::GetAnnotationMarks( const SwTextNode& rNd, sal_Int32 nStt,
                    sal_Int32 nEnd, IMarkVector& rArr )
{
    IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
    sal_uLong nNd = rNd.GetIndex( );
 
    const sal_Int32 nMarks = pMarkAccess->getAnnotationMarksCount();
    for ( sal_Int32 i = 0; i < nMarks; i++ )
    {
        IMark* pMark = ( pMarkAccess->getAnnotationMarksBegin() + i )->get();
 
        // Only keep the bookmarks starting or ending in this node
        if ( pMark->GetMarkStart().nNode == nNd ||
             pMark->GetMarkEnd().nNode == nNd )
        {
            const sal_Int32 nBStart = pMark->GetMarkStart().nContent.GetIndex();
            const sal_Int32 nBEnd = pMark->GetMarkEnd().nContent.GetIndex();
 
            // Keep only the bookmars starting or ending in the snippet
            bool bIsStartOk = ( pMark->GetMarkStart().nNode == nNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
            bool bIsEndOk = ( pMark->GetMarkEnd().nNode == nNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
 
            // Annotation marks always have at least one character: the anchor
            // point of the comment field. In this case Word wants only the
            // comment field, so ignore the annotation mark itself.
            bool bSingleChar = pMark->GetMarkStart().nNode == pMark->GetMarkEnd().nNode && nBStart + 1 == nBEnd;
 
            if ( ( bIsStartOk || bIsEndOk ) && !bSingleChar )
            {
                rArr.push_back( pMark );
            }
        }
    }
    return ( rArr.size() > 0 );
}
 
class CompareMarksEnd
{
public:
    bool operator() ( const IMark * pOneB, const IMark * pTwoB ) const
    {
        const sal_Int32 nOEnd = pOneB->GetMarkEnd().nContent.GetIndex();
        const sal_Int32 nTEnd = pTwoB->GetMarkEnd().nContent.GetIndex();
 
        return nOEnd < nTEnd;
    }
};
 
bool MSWordExportBase::NearestBookmark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
{
    bool bHasBookmark = false;
 
    if ( !m_rSortedBookmarksStart.empty() )
    {
        IMark* pMarkStart = m_rSortedBookmarksStart.front();
        const sal_Int32 nNext = pMarkStart->GetMarkStart().nContent.GetIndex();
        if( !bNextPositionOnly || (nNext > nCurrentPos ))
        {
            rNearest = nNext;
            bHasBookmark = true;
        }
    }
 
    if ( !m_rSortedBookmarksEnd.empty() )
    {
        IMark* pMarkEnd = m_rSortedBookmarksEnd[0];
        const sal_Int32 nNext = pMarkEnd->GetMarkEnd().nContent.GetIndex();
        if( !bNextPositionOnly || nNext > nCurrentPos )
        {
            if ( !bHasBookmark )
                rNearest = nNext;
            else
                rNearest = std::min( rNearest, nNext );
            bHasBookmark = true;
        }
    }
 
    return bHasBookmark;
}
 
void MSWordExportBase::NearestAnnotationMark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
{
    bool bHasAnnotationMark = false;
 
    if ( !m_rSortedAnnotationMarksStart.empty() )
    {
        IMark* pMarkStart = m_rSortedAnnotationMarksStart.front();
        const sal_Int32 nNext = pMarkStart->GetMarkStart().nContent.GetIndex();
        if( !bNextPositionOnly || (nNext > nCurrentPos ))
        {
            rNearest = nNext;
            bHasAnnotationMark = true;
        }
    }
 
    if ( !m_rSortedAnnotationMarksEnd.empty() )
    {
        IMark* pMarkEnd = m_rSortedAnnotationMarksEnd[0];
        const sal_Int32 nNext = pMarkEnd->GetMarkEnd().nContent.GetIndex();
        if( !bNextPositionOnly || nNext > nCurrentPos )
        {
            if ( !bHasAnnotationMark )
                rNearest = nNext;
            else
                rNearest = std::min( rNearest, nNext );
        }
    }
}
 
void MSWordExportBase::GetSortedAnnotationMarks( const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen )
{
    IMarkVector aMarksStart;
    if ( GetAnnotationMarks( rNode, nCurrentPos, nCurrentPos + nLen, aMarksStart ) )
    {
        IMarkVector aSortedEnd;
        IMarkVector aSortedStart;
        for ( IMarkVector::const_iterator it = aMarksStart.begin(), end = aMarksStart.end();
              it != end; ++it )
        {
            IMark* pMark = (*it);
 
            // Remove the positions equal to the current pos
            const sal_Int32 nStart = pMark->GetMarkStart().nContent.GetIndex();
            const sal_Int32 nEnd = pMark->GetMarkEnd().nContent.GetIndex();
 
            if ( nStart > nCurrentPos && ( pMark->GetMarkStart().nNode == rNode.GetIndex()) )
                aSortedStart.push_back( pMark );
 
            if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().nNode == rNode.GetIndex()) )
                aSortedEnd.push_back( pMark );
        }
 
        // Sort the bookmarks by end position
        std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
 
        m_rSortedAnnotationMarksStart.swap( aSortedStart );
        m_rSortedAnnotationMarksEnd.swap( aSortedEnd );
    }
    else
    {
        m_rSortedAnnotationMarksStart.clear( );
        m_rSortedAnnotationMarksEnd.clear( );
    }
}
 
void MSWordExportBase::GetSortedBookmarks( const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen )
{
    IMarkVector aMarksStart;
    if ( GetBookmarks( rNode, nCurrentPos, nCurrentPos + nLen, aMarksStart ) )
    {
        IMarkVector aSortedEnd;
        IMarkVector aSortedStart;
        for ( IMarkVector::const_iterator it = aMarksStart.begin(), end = aMarksStart.end();
              it != end; ++it )
        {
            IMark* pMark = (*it);
 
            // Remove the positions equal to the current pos
            const sal_Int32 nStart = pMark->GetMarkStart().nContent.GetIndex();
            const sal_Int32 nEnd = pMark->GetMarkEnd().nContent.GetIndex();
 
            if ( nStart > nCurrentPos && ( pMark->GetMarkStart().nNode == rNode.GetIndex()) )
                aSortedStart.push_back( pMark );
 
            if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().nNode == rNode.GetIndex()) )
                aSortedEnd.push_back( pMark );
        }
 
        // Sort the bookmarks by end position
        std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
 
        m_rSortedBookmarksStart.swap( aSortedStart );
        m_rSortedBookmarksEnd.swap( aSortedEnd );
    }
    else
    {
        m_rSortedBookmarksStart.clear( );
        m_rSortedBookmarksEnd.clear( );
    }
}
 
bool MSWordExportBase::NeedSectionBreak( const SwNode& rNd ) const
{
    if ( m_bStyDef || m_bOutKF || m_bInWriteEscher || m_bOutPageDescs || m_pCurrentPageDesc == nullptr )
        return false;
 
    const SwPageDesc * pPageDesc = rNd.FindPageDesc()->GetFollow();
 
    if (m_pCurrentPageDesc != pPageDesc)
    {
        if (!sw::util::IsPlausableSingleWordSection(m_pCurrentPageDesc->GetFirstMaster(), pPageDesc->GetMaster()))
        {
            return true;
        }
    }
 
    return false;
}
 
bool MSWordExportBase::NeedTextNodeSplit( const SwTextNode& rNd, SwSoftPageBreakList& pList ) const
{
    rNd.fillSoftPageBreakList( pList );
    pList.insert(0);
    pList.insert( rNd.GetText().getLength() );
    return pList.size() > 2 && NeedSectionBreak( rNd );
}
 
void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
{
    SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" );
 
    ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo( m_pTableInfo->getTableNodeInfo( &rNode ) );
 
    //For i120928,identify the last node
    bool bLastCR = false;
    bool bExported = false;
    {
        SwNodeIndex aNextIdx(rNode,1);
        SwNodeIndex aLastIdx(rNode.GetNodes().GetEndOfContent());
        if (aNextIdx == aLastIdx)
            bLastCR = true;
    }
 
    // In order to make sure watermark is stored in 'header.xml', check nTextTyp.
    // if it is document.xml, don't write the tags (watermark should be only in the 'header')
    SwWW8AttrIter aWatermarkAttrIter( *this, rNode );
    if (( TXT_HDFT != m_nTextTyp) && aWatermarkAttrIter.IsWatermarkFrame())
    {
        return;
    }
 
    bool bFlyInTable = m_pParentFrame && IsInTable();
 
    if ( !bFlyInTable )
        m_nStyleBeforeFly = GetId( lcl_getFormatCollection( *this, &rNode ) );
 
    // nStyleBeforeFly may change when we recurse into another node, so we
    // have to remember it in nStyle
    sal_uInt16 nStyle = m_nStyleBeforeFly;
 
    SwWW8AttrIter aAttrIter( *this, rNode );
    rtl_TextEncoding eChrSet = aAttrIter.GetCharSet();
 
    if ( m_bStartTOX )
    {
        // ignore TOX header section
        const SwSectionNode* pSectNd = rNode.FindSectionNode();
        if ( pSectNd && TOX_CONTENT_SECTION == pSectNd->GetSection().GetType() )
        {
            AttrOutput().StartTOX( pSectNd->GetSection() );
            m_aCurrentCharPropStarts.push( 0 );
        }
    }
 
    // Emulate: If 1-row table is marked as don't split, then set the row as don't split.
    if ( IsInTable() )
    {
        const SwTableNode* pTableNode = rNode.FindTableNode();
        if ( pTableNode )
        {
            const SwTable& rTable = pTableNode->GetTable();
            const bool bKeep = rTable.GetFrameFormat()->GetKeep().GetValue();
            const bool bDontSplit = !rTable.GetFrameFormat()->GetLayoutSplit().GetValue();
            // bKeep handles this a different way later on, so ignore now
            if ( !bKeep && bDontSplit && rTable.GetTabLines().size() == 1 )
            {
                // bDontSplit : set don't split once for the row
                // but only for non-complex tables
                const SwTableBox* pBox = rNode.GetTableBox();
                const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
                if ( pLine && !pLine->GetUpper() )
                {
                    // check if box is first in that line:
                    if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
                    {
                        // check if paragraph is first in that line:
                        if ( 1 == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
                            pLine->GetFrameFormat()->SetFormatAttr(SwFormatRowSplit(!bDontSplit));
                    }
                }
            }
        }
    }
 
    SwSoftPageBreakList softBreakList;
    // Let's decide if we need to split the paragraph because of a section break
    bool bNeedParaSplit = NeedTextNodeSplit( rNode, softBreakList )
                        && !IsInTable();
 
    auto aBreakIt = softBreakList.begin();
    // iterate through portions on different pages
    do
    {
        sal_Int32 nCurrentPos = *aBreakIt;
 
        if( softBreakList.size() > 1 ) // not for empty paragraph
            ++aBreakIt;
 
        AttrOutput().StartParagraph( pTextNodeInfo );
 
        const SwSection* pTOXSect = nullptr;
        if( m_bInWriteTOX )
        {
            // check for end of TOX
            SwNodeIndex aIdx( rNode, 1 );
            if( !aIdx.GetNode().IsTextNode() )
            {
                const SwSectionNode* pTOXSectNd = rNode.FindSectionNode();
                if ( pTOXSectNd )
                {
                    pTOXSect = &pTOXSectNd->GetSection();
 
                    const SwNode* pNxt = rNode.GetNodes().GoNext( &aIdx );
                    if( pNxt && pNxt->FindSectionNode() == pTOXSectNd )
                        pTOXSect = nullptr;
                }
            }
        }
 
        if ( aAttrIter.RequiresImplicitBookmark() )
        {
            OUString sBkmkName =  "_toc" + OUString::number( rNode.GetIndex() );
            // Add a bookmark converted to a Word name.
            AppendBookmark( BookmarkToWord( sBkmkName ) );
        }
 
        // Call this before write out fields and runs
        AttrOutput().GenerateBookmarksForSequenceField(rNode, aAttrIter);
 
        const OUString& aStr( rNode.GetText() );
 
        sal_Int32 const nEnd = bNeedParaSplit ? *aBreakIt : aStr.getLength();
        bool bIncludeEndOfParaCRInRedlineProperties = false;
        sal_Int32 nOpenAttrWithRange = 0;
 
        ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner;
        if ( pTextNodeInfo.get() != nullptr )
        {
            pTextNodeInfoInner = pTextNodeInfo->getFirstInner();
        }
 
        do {
 
            const SwRedlineData* pRedlineData = aAttrIter.GetRunLevelRedline( nCurrentPos );
            FlyProcessingState nStateOfFlyFrame = FLY_PROCESSED;
            bool bPostponeWritingText    = false ;
            OUString aSavedSnippet ;
 
            sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nCurrentPos );
 
            // Skip un-exportable attributes.
            if (!aAttrIter.IsExportableAttr(nCurrentPos))
            {
                nCurrentPos = nNextAttr;
                UpdatePosition(&aAttrIter, nCurrentPos);
                eChrSet = aAttrIter.GetCharSet();
                continue;
            }
 
            // Is this the only run in this paragraph and it's empty?
            bool bSingleEmptyRun = nCurrentPos == 0 && nNextAttr == 0;
            AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
 
            if( nNextAttr > nEnd )
                nNextAttr = nEnd;
 
            if( m_nTextTyp == TXT_FTN || m_nTextTyp == TXT_EDN )
            {
                if( AttrOutput().FootnoteEndnoteRefTag() )
                {
                    AttrOutput().EndRun( &rNode, nCurrentPos, nNextAttr == nEnd );
                    AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
                }
            }
 
            /*
               1) If there is a text node and an overlapping anchor, then write them in two different
               runs and not as part of the same run.
               2) Ensure that it is a text node and not in a fly.
               3) If the anchor is associated with a text node with empty text then we ignore.
               */
            if( rNode.IsTextNode()
                    && aStr != "\001" && !aStr.isEmpty()
                    && !rNode.GetFlyFormat()
                    && !(IsInTable() && !AllowPostponedTextInTable())
                    && aAttrIter.IsAnchorLinkedToThisNode(rNode.GetIndex()) )
            {
                bPostponeWritingText = true ;
            }
 
            nStateOfFlyFrame = aAttrIter.OutFlys( nCurrentPos );
            AttrOutput().SetStateOfFlyFrame( nStateOfFlyFrame );
            AttrOutput().SetAnchorIsLinkedToNode( bPostponeWritingText && (FLY_POSTPONED != nStateOfFlyFrame) );
            // Append bookmarks in this range after flys, exclusive of final
            // position of this range
            AppendBookmarks( rNode, nCurrentPos, nNextAttr - nCurrentPos );
            AppendAnnotationMarks( rNode, nCurrentPos, nNextAttr - nCurrentPos );
 
            // At the moment smarttags are only written for paragraphs, at the
            // beginning of the paragraph.
            if (nCurrentPos == 0)
                AppendSmartTags(rNode);
 
            bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
            nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nCurrentPos );
 
            sal_Int32 nLen = nNextAttr - nCurrentPos;
            if ( !bTextAtr && nLen )
            {
                sal_Unicode ch = aStr[nCurrentPos];
                const sal_Int32 ofs = ( ch == CH_TXT_ATR_FIELDSTART || ch == CH_TXT_ATR_FIELDEND || ch == CH_TXT_ATR_FORMELEMENT? 1 : 0 );
 
                IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
                if ( ch == CH_TXT_ATR_FIELDSTART )
                {
                    SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
                    ::sw::mark::IFieldmark const * const pFieldmark = pMarkAccess->getFieldmarkFor( aPosition );
                    OSL_ENSURE( pFieldmark, "Looks like this doc is broken...; where is the Fieldmark for the FIELDSTART??" );
 
                    if ( pFieldmark && pFieldmark->GetFieldname() == ODF_FORMTEXT )
                        AppendBookmark( pFieldmark->GetName() );
                    ww::eField eFieldId = lcl_getFieldId( pFieldmark );
                    OUString sCode = lcl_getFieldCode( pFieldmark );
                    if ( pFieldmark && pFieldmark->GetFieldname() == ODF_UNHANDLED )
                    {
                        IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
                        if ( it != pFieldmark->GetParameters()->end() )
                        {
                            OUString sFieldId;
                            it->second >>= sFieldId;
                            eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
                        }
 
                        it = pFieldmark->GetParameters()->find( ODF_CODE_PARAM );
                        if ( it != pFieldmark->GetParameters()->end() )
                        {
                            it->second >>= sCode;
                        }
                    }
 
                    OutputField( nullptr, eFieldId, sCode, FieldFlags::Start | FieldFlags::CmdStart );
 
                    if ( pFieldmark && pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
                        WriteFormData( *pFieldmark );
                    else if ( pFieldmark && pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
                        WriteHyperlinkData( *pFieldmark );
                    OutputField( nullptr, lcl_getFieldId( pFieldmark ), OUString(), FieldFlags::CmdEnd );
 
                    if ( pFieldmark && pFieldmark->GetFieldname() == ODF_UNHANDLED )
                    {
                        // Check for the presence of a linked OLE object
                        IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_OLE_PARAM );
                        if ( it != pFieldmark->GetParameters()->end() )
                        {
                            OUString sOleId;
                            uno::Any aValue = it->second;
                            aValue >>= sOleId;
                            if ( !sOleId.isEmpty() )
                                OutputLinkedOLE( sOleId );
                        }
                    }
                }
                else if ( ch == CH_TXT_ATR_FIELDEND )
                {
                    SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
                    ::sw::mark::IFieldmark const * const pFieldmark = pMarkAccess->getFieldmarkFor( aPosition );
 
                    OSL_ENSURE( pFieldmark, "Looks like this doc is broken...; where is the Fieldmark for the FIELDEND??" );
 
                    ww::eField eFieldId = lcl_getFieldId( pFieldmark );
                    if ( pFieldmark && pFieldmark->GetFieldname() == ODF_UNHANDLED )
                    {
                        IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
                        if ( it != pFieldmark->GetParameters()->end() )
                        {
                            OUString sFieldId;
                            it->second >>= sFieldId;
                            eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
                        }
                    }
 
                    OutputField( nullptr, eFieldId, OUString(), FieldFlags::Close );
 
                    if ( pFieldmark && pFieldmark->GetFieldname() == ODF_FORMTEXT )
                        AppendBookmark( pFieldmark->GetName() );
                }
                else if ( ch == CH_TXT_ATR_FORMELEMENT )
                {
                    SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
                    ::sw::mark::IFieldmark const * const pFieldmark = pMarkAccess->getFieldmarkFor( aPosition );
                    OSL_ENSURE( pFieldmark, "Looks like this doc is broken...; where is the Fieldmark for the FIELDSTART??" );
 
                    bool isDropdownOrCheckbox = pFieldmark && (pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN ||
                            pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX );
 
                    if ( isDropdownOrCheckbox )
                        AppendBookmark( pFieldmark->GetName() );
                    OutputField( nullptr, lcl_getFieldId( pFieldmark ),
                            lcl_getFieldCode( pFieldmark ),
                            FieldFlags::Start | FieldFlags::CmdStart );
                    if ( isDropdownOrCheckbox )
                        WriteFormData( *pFieldmark );
                    OutputField( nullptr, lcl_getFieldId( pFieldmark ), OUString(), FieldFlags::Close );
                    if ( isDropdownOrCheckbox )
                        AppendBookmark( pFieldmark->GetName() );
                }
                nLen -= ofs;
 
                // if paragraph needs to be split, write only until split position
                assert(!bNeedParaSplit || nCurrentPos <= *aBreakIt);
                if( bNeedParaSplit && nCurrentPos + ofs + nLen > *aBreakIt)
                    nLen = *aBreakIt - nCurrentPos - ofs;
                assert(0 <= nLen);
 
                OUString aSnippet( aAttrIter.GetSnippet( aStr, nCurrentPos + ofs, nLen ) );
                if ( ( m_nTextTyp == TXT_EDN || m_nTextTyp == TXT_FTN ) && nCurrentPos == 0 && nLen > 0 )
                {
                    // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent
                    sal_Int32 nFirstLineIndent=0;
                    SfxItemSet aSet( m_pDoc->GetAttrPool(), svl::Items<RES_LR_SPACE, RES_LR_SPACE>{} );
                    const SwTextNode* pTextNode( rNode.GetTextNode() );
                    if ( pTextNode && pTextNode->GetAttr(aSet) )
                    {
                        const SvxLRSpaceItem* pLRSpace = aSet.GetItem<SvxLRSpaceItem>(RES_LR_SPACE);
                        if ( pLRSpace )
                            nFirstLineIndent = pLRSpace->GetTextFirstLineOfst();
                    }
 
                    // Insert tab for aesthetic purposes #i24762#
                    if ( m_bAddFootnoteTab && nFirstLineIndent < 0 && aSnippet[0] != 0x09 )
                        aSnippet = "\x09" + aSnippet;
                    m_bAddFootnoteTab = false;
                }
 
                if ( bPostponeWritingText && ( FLY_POSTPONED != nStateOfFlyFrame ) )
                {
                    bPostponeWritingText = true ;
                    aSavedSnippet = aSnippet ;
                }
                else
                {
                    bPostponeWritingText = false ;
                    AttrOutput().RunText( aSnippet, eChrSet );
                }
            }
 
            if ( aAttrIter.IsDropCap( nNextAttr ) )
                AttrOutput().FormatDrop( rNode, aAttrIter.GetSwFormatDrop(), nStyle, pTextNodeInfo, pTextNodeInfoInner );
 
            // Only output character attributes if this is not a postponed text run.
            if (0 != nEnd && !(bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame))
            {
                // Output the character attributes
                // #i51277# do this before writing flys at end of paragraph
                AttrOutput().StartRunProperties();
                aAttrIter.OutAttr( nCurrentPos, false );
                AttrOutput().EndRunProperties( pRedlineData );
            }
 
            // At the end of line, output the attributes until the CR.
            // Exception: footnotes at the end of line
            if ( nNextAttr == nEnd )
            {
                OSL_ENSURE( nOpenAttrWithRange >= 0, "odd to see this happening, expected >= 0" );
                if ( !bTextAtr && nOpenAttrWithRange <= 0 )
                {
                    if ( aAttrIter.IncludeEndOfParaCRInRedlineProperties( nEnd ) )
                        bIncludeEndOfParaCRInRedlineProperties = true;
                    else
                    {
                        // insert final graphic anchors if any before CR
                        nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
                        // insert final bookmarks if any before CR and after flys
                        AppendBookmarks( rNode, nEnd, 1 );
                        AppendAnnotationMarks( rNode, nEnd, 1 );
                        if ( pTOXSect )
                        {
                            m_aCurrentCharPropStarts.pop();
                            AttrOutput().EndTOX( *pTOXSect ,false);
                        }
                        //For i120928,the position of the bullet's graphic is at end of doc
                        if (bLastCR && (!bExported))
                        {
                            ExportGrfBullet(rNode);
                            bExported = true;
                        }
 
                        WriteCR( pTextNodeInfoInner );
                    }
                }
            }
 
            if (0 == nEnd)
            {
                // Output the character attributes
                // do it after WriteCR for an empty paragraph (otherwise
                // WW8_WrFkp::Append throws SPRMs away...)
                AttrOutput().StartRunProperties();
                aAttrIter.OutAttr( nCurrentPos, false );
                AttrOutput().EndRunProperties( pRedlineData );
            }
 
            // Exception: footnotes at the end of line
            if ( nNextAttr == nEnd )
            {
                OSL_ENSURE(nOpenAttrWithRange >= 0,
                        "odd to see this happening, expected >= 0");
                bool bAttrWithRange = (nOpenAttrWithRange > 0);
                if ( nCurrentPos != nEnd )
                {
                    nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nEnd );
                    OSL_ENSURE(nOpenAttrWithRange == 0,
                            "odd to see this happening, expected 0");
                }
 
                // !bIncludeEndOfParaCRInRedlineProperties implies we have just
                // emitted a CR, in which case we want to pass force=true to
                // OutputFKP to ensure that an FKP entry for direct character
                // formatting is written even if empty, so that the next one will
                // start after the CR.
                AttrOutput().OutputFKP(!bIncludeEndOfParaCRInRedlineProperties);
 
                if (bTextAtr || bAttrWithRange || bIncludeEndOfParaCRInRedlineProperties)
                {
                    // insert final graphic anchors if any before CR
                    nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
                    // insert final bookmarks if any before CR and after flys
                    AppendBookmarks( rNode, nEnd, 1 );
                    AppendAnnotationMarks( rNode, nEnd, 1 );
                    WriteCR( pTextNodeInfoInner );
                    // #i120928 - position of the bullet's graphic is at end of doc
                    if (bLastCR && (!bExported))
                    {
                        ExportGrfBullet(rNode);
                        bExported = true;
                    }
 
                    if ( pTOXSect )
                    {
                        m_aCurrentCharPropStarts.pop();
                        AttrOutput().EndTOX( *pTOXSect );
                    }
 
                    if (bIncludeEndOfParaCRInRedlineProperties)
                    {
                        AttrOutput().Redline( aAttrIter.GetRunLevelRedline( nEnd ) );
                        //If there was no redline property emitted, force adding
                        //another entry for the CR so that in the case that this
                        //has no redline, but the next para does, then this one is
                        //not merged with the next
                        AttrOutput().OutputFKP(true);
                    }
                }
            }
 
            AttrOutput().WritePostitFieldReference();
 
            if( bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame )
            {
                AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
                //write the postponed text run
                AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
                AttrOutput().SetAnchorIsLinkedToNode( false );
                AttrOutput().ResetFlyProcessingFlag();
                if (0 != nEnd)
                {
                    AttrOutput().StartRunProperties();
                    aAttrIter.OutAttr( nCurrentPos, false );
                    AttrOutput().EndRunProperties( pRedlineData );
                }
                AttrOutput().RunText( aSavedSnippet, eChrSet );
                AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
            }
            else if( bPostponeWritingText && !aSavedSnippet.isEmpty() )
            {
                //write the postponed text run
                AttrOutput().RunText( aSavedSnippet, eChrSet );
                AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
            }
            else
                AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
 
            nCurrentPos = nNextAttr;
            UpdatePosition( &aAttrIter, nCurrentPos );
            eChrSet = aAttrIter.GetCharSet();
        }
        while ( nCurrentPos < nEnd );
 
        // if paragraph is split, put the section break between the parts
        if( bNeedParaSplit && *aBreakIt != rNode.GetText().getLength() )
        {
            SwNodeIndex aNextIndex( rNode, 1 );
            const SwNode& pNextNode = aNextIndex.GetNode();
            // if there is a next node, use its attributes to create the new
            // section
            if( pNextNode.IsTextNode() )
            {
                const SwTextNode& rNextNode = *static_cast<SwTextNode*>(
                        &aNextIndex.GetNode() );
                OutputSectionBreaks(rNextNode.GetpSwAttrSet(), rNextNode);
            }
            else if (pNextNode.IsEndNode() )
            {
                // In this case the same paragraph holds the next page style
                // too.
                const SwPageDesc* pNextPageDesc = m_pCurrentPageDesc->GetFollow();
                assert(pNextPageDesc);
                PrepareNewPageDesc( rNode.GetpSwAttrSet(), rNode, nullptr , pNextPageDesc);
            }
        }
        else if (!bNeedParaSplit)
        {
            // else check if section break needed after the paragraph
            AttrOutput().SectionBreaks(rNode);
        }
 
        AttrOutput().StartParagraphProperties();
 
        AttrOutput().ParagraphStyle( nStyle );
 
        if ( m_pParentFrame && IsInTable() )    // Fly-Attrs
            OutputFormat( m_pParentFrame->GetFrameFormat(), false, false, true );
 
        if ( pTextNodeInfo.get() != nullptr )
        {
#ifdef DBG_UTIL
            SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
#endif
 
            AttrOutput().TableInfoCell( pTextNodeInfoInner );
            if (pTextNodeInfoInner->isFirstInTable())
            {
                const SwTable * pTable = pTextNodeInfoInner->getTable();
 
                const SwTableFormat* pTabFormat = pTable->GetFrameFormat();
                if (pTabFormat != nullptr)
                {
                    if (pTabFormat->GetBreak().GetBreak() == SvxBreak::PageBefore)
                        AttrOutput().PageBreakBefore(true);
                }
            }
        }
 
        if ( !bFlyInTable )
        {
            SfxItemSet* pTmpSet = nullptr;
            const sal_uInt8 nPrvNxtNd = rNode.HasPrevNextLayNode();
 
            if( (ND_HAS_PREV_LAYNODE|ND_HAS_NEXT_LAYNODE ) != nPrvNxtNd )
            {
                const SfxPoolItem* pItem;
                if( SfxItemState::SET == rNode.GetSwAttrSet().GetItemState(
                        RES_UL_SPACE, true, &pItem ) &&
                    ( ( !( ND_HAS_PREV_LAYNODE & nPrvNxtNd ) &&
                       static_cast<const SvxULSpaceItem*>(pItem)->GetUpper()) ||
                      ( !( ND_HAS_NEXT_LAYNODE & nPrvNxtNd ) &&
                       static_cast<const SvxULSpaceItem*>(pItem)->GetLower()) ))
                {
                    pTmpSet = new SfxItemSet( rNode.GetSwAttrSet() );
                    SvxULSpaceItem aUL( *static_cast<const SvxULSpaceItem*>(pItem) );
                    // #i25901#- consider compatibility option
                    if (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES))
                    {
                        if( !(ND_HAS_PREV_LAYNODE & nPrvNxtNd ))
                            aUL.SetUpper( 0 );
                    }
                    // #i25901# - consider compatibility option
                    if (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS))
                    {
                        if( !(ND_HAS_NEXT_LAYNODE & nPrvNxtNd ))
                            aUL.SetLower( 0 );
                    }
                    pTmpSet->Put( aUL );
                }
            }
 
            bool bParaRTL = aAttrIter.IsParaRTL();
 
            int nNumberLevel = -1;
            if (rNode.IsNumbered())
                nNumberLevel = rNode.GetActualListLevel();
            if (nNumberLevel >= 0 && nNumberLevel < MAXLEVEL)
            {
                const SwNumRule* pRule = rNode.GetNumRule();
                sal_uInt8 nLvl = static_cast< sal_uInt8 >(nNumberLevel);
                const SwNumFormat* pFormat = pRule->GetNumFormat( nLvl );
                if( !pFormat )
                    pFormat = &pRule->Get( nLvl );
 
                if( !pTmpSet )
                    pTmpSet = new SfxItemSet( rNode.GetSwAttrSet() );
 
                SvxLRSpaceItem aLR(ItemGet<SvxLRSpaceItem>(*pTmpSet, RES_LR_SPACE));
                // #i86652#
                if ( pFormat->GetPositionAndSpaceMode() ==
                                        SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
                {
                    aLR.SetTextLeft( aLR.GetTextLeft() + pFormat->GetAbsLSpace() );
                }
 
                if( rNode.IsNumbered() && rNode.IsCountedInList() )
                {
                    // #i86652#
                    if ( pFormat->GetPositionAndSpaceMode() ==
                                            SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
                    {
                        if (bParaRTL)
                            aLR.SetTextFirstLineOfstValue(pFormat->GetAbsLSpace() - pFormat->GetFirstLineOffset()); //TODO: overflow
                        else
                            aLR.SetTextFirstLineOfst(GetWordFirstLineOffset(*pFormat));
                    }
 
                    // correct fix for issue i94187
                    if (SfxItemState::SET !=
                        pTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
                    {
                        // List style set via paragraph style - then put it into the itemset.
                        // This is needed to get list level and list id exported for
                        // the paragraph.
                        pTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
 
                        // Put indent values into the itemset in case that the list
                        // style is applied via paragraph style and the list level
                        // indent values are not applicable.
                        if ( pFormat->GetPositionAndSpaceMode() ==
                                                SvxNumberFormat::LABEL_ALIGNMENT &&
                             !rNode.AreListLevelIndentsApplicable() )
                        {
                            pTmpSet->Put( aLR );
                        }
                    }
                }
                else
                    pTmpSet->ClearItem(RES_PARATR_NUMRULE);
 
                // #i86652#
                if ( pFormat->GetPositionAndSpaceMode() ==
                                        SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
                {
                    pTmpSet->Put(aLR);
 
                    //#i21847#
                    SvxTabStopItem aItem(
                        ItemGet<SvxTabStopItem>(*pTmpSet, RES_PARATR_TABSTOP));
                    SvxTabStop aTabStop(pFormat->GetAbsLSpace());
                    aItem.Insert(aTabStop);
                    pTmpSet->Put(aItem);
 
                    MSWordExportBase::CorrectTabStopInSet(*pTmpSet, pFormat->GetAbsLSpace());
                }
            }
 
            /*
            If a given para is using the SvxFrameDirection::Environment direction we
            cannot export that, if it's ltr then that's ok as that is word's
            default. Otherwise we must add a RTL attribute to our export list
            */
            const SvxFrameDirectionItem* pItem =
                rNode.GetSwAttrSet().GetItem(RES_FRAMEDIR);
            if (
                (!pItem || pItem->GetValue() == SvxFrameDirection::Environment) &&
                aAttrIter.IsParaRTL()
               )
            {
                if ( !pTmpSet )
                    pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
 
                pTmpSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_RL_TB, RES_FRAMEDIR));
            }
            // move code for handling of numbered,
            // but not counted paragraphs to this place. Otherwise, the paragraph
            // isn't exported as numbered, but not counted, if no other attribute
            // is found in <pTmpSet>
            // #i44815# adjust numbering/indents for numbered paragraphs
            //          without number (NO_NUMLEVEL)
            // #i47013# need to check rNode.GetNumRule()!=NULL as well.
            if ( ! rNode.IsCountedInList() && rNode.GetNumRule()!=nullptr )
            {
                // WW8 does not know numbered paragraphs without number
                // (NO_NUMLEVEL). In WW8AttributeOutput::ParaNumRule(), we will export
                // the RES_PARATR_NUMRULE as list-id 0, which in WW8 means
                // no numbering. Here, we will adjust the indents to match
                // visually.
 
                if ( !pTmpSet )
                    pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
 
                // create new LRSpace item, based on the current (if present)
                const SfxPoolItem* pPoolItem = nullptr;
                pTmpSet->GetItemState(RES_LR_SPACE, true, &pPoolItem);
                SvxLRSpaceItem aLRSpace(
                    ( pPoolItem == nullptr )
                        ? SvxLRSpaceItem(0, 0, 0, 0, RES_LR_SPACE)
                        : *static_cast<const SvxLRSpaceItem*>( pPoolItem ) );
 
                // new left margin = old left + label space
                const SwNumRule* pRule = rNode.GetNumRule();
                int nLevel = rNode.GetActualListLevel();
 
                if (nLevel < 0)
                    nLevel = 0;
 
                if (nLevel >= MAXLEVEL)
                    nLevel = MAXLEVEL - 1;
 
                const SwNumFormat& rNumFormat = pRule->Get( static_cast< sal_uInt16 >(nLevel) );
 
                // #i86652#
                if ( rNumFormat.GetPositionAndSpaceMode() ==
                                        SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
                {
                    aLRSpace.SetTextLeft( aLRSpace.GetLeft() + rNumFormat.GetAbsLSpace() );
                }
                else
                {
                    aLRSpace.SetTextLeft( aLRSpace.GetLeft() + rNumFormat.GetIndentAt() );
                }
 
                // new first line indent = 0
                // (first line indent is ignored for NO_NUMLEVEL)
                if (!bParaRTL)
                    aLRSpace.SetTextFirstLineOfst( 0 );
 
                // put back the new item
                pTmpSet->Put( aLRSpace );
 
                // assure that numbering rule is in <pTmpSet>
                if (SfxItemState::SET != pTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
                {
                    pTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
                }
            }
 
            // #i75457#
            // Export page break after attribute from paragraph style.
            // If page break attribute at the text node exist, an existing page
            // break after at the paragraph style hasn't got to be considered.
            if ( !rNode.GetpSwAttrSet() ||
                 SfxItemState::SET != rNode.GetpSwAttrSet()->GetItemState(RES_BREAK, false) )
            {
                const SvxFormatBreakItem* pBreakAtParaStyle =
                    &(ItemGet<SvxFormatBreakItem>(rNode.GetSwAttrSet(), RES_BREAK));
                if ( pBreakAtParaStyle &&
                     pBreakAtParaStyle->GetBreak() == SvxBreak::PageAfter )
                {
                    if ( !pTmpSet )
                    {
                        pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
                    }
                    pTmpSet->Put( *pBreakAtParaStyle );
                }
                else if( pTmpSet )
                {   // Even a pagedesc item is set, the break item can be set 'NONE',
                    // this has to be overruled.
                    const SwFormatPageDesc& rPageDescAtParaStyle =
                        ItemGet<SwFormatPageDesc>( rNode, RES_PAGEDESC );
                    if( rPageDescAtParaStyle.KnowsPageDesc() )
                        pTmpSet->ClearItem( RES_BREAK );
                }
            }
 
            // #i76520# Emulate non-splitting tables
            if ( IsInTable() )
            {
                const SwTableNode* pTableNode = rNode.FindTableNode();
 
                if ( pTableNode )
                {
                    const SwTable& rTable = pTableNode->GetTable();
                    const SvxFormatKeepItem& rKeep = rTable.GetFrameFormat()->GetKeep();
                    const bool bKeep = rKeep.GetValue();
                    const bool bDontSplit = !(bKeep ||
                                              rTable.GetFrameFormat()->GetLayoutSplit().GetValue());
 
                    if ( bKeep || bDontSplit )
                    {
                        // bKeep: set keep at first paragraphs in all lines
                        // bDontSplit : set keep at first paragraphs in all lines except from last line
                        // but only for non-complex tables
                        const SwTableBox* pBox = rNode.GetTableBox();
                        const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
 
                        if ( pLine && !pLine->GetUpper() )
                        {
                            // check if box is first in that line:
                            if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
                            {
                                // check if paragraph is first in that line:
                                if ( 1 == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
                                {
                                    bool bSetAtPara = false;
                                    if ( bKeep )
                                        bSetAtPara = true;
                                    else if ( bDontSplit )
                                    {
                                        // check if pLine isn't last line in table
                                        if ( rTable.GetTabLines().size() - rTable.GetTabLines().GetPos( pLine ) != 1 )
                                            bSetAtPara = true;
                                    }
 
                                    if ( bSetAtPara )
                                    {
                                        if ( !pTmpSet )
                                            pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
 
                                        const SvxFormatKeepItem aKeepItem( true, RES_KEEP );
                                        pTmpSet->Put( aKeepItem );
                                    }
                                }
                            }
                        }
                    }
                }
            }
 
            const SfxItemSet* pNewSet = pTmpSet ? pTmpSet : rNode.GetpSwAttrSet();
            if( pNewSet )
            {                                               // Para-Attrs
                m_pStyAttr = &rNode.GetAnyFormatColl().GetAttrSet();
 
                const SwModify* pOldMod = m_pOutFormatNode;
                m_pOutFormatNode = &rNode;
 
                // Pap-Attrs, so script is not necessary
                OutputItemSet( *pNewSet, true, false, i18n::ScriptType::LATIN, false);
 
                m_pStyAttr = nullptr;
                m_pOutFormatNode = pOldMod;
 
                if( pNewSet != rNode.GetpSwAttrSet() )
                    delete pNewSet;
            }
        }
 
        // The formatting of the paragraph marker has two sources:
        // 1) If there are hints at the end of the paragraph, then use that.
        // 2) Else use the RES_CHRATR_BEGIN..RES_TXTATR_END range of the paragraph
        // properties.
        //
        // Exception: if there is a character style hint at the end of the
        // paragraph only, then still go with 2), as RES_TXTATR_CHARFMT is always
        // set as a hint.
        SfxItemSet aParagraphMarkerProperties(m_pDoc->GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END>{});
        bool bCharFormatOnly = true;
        if(const SwpHints* pTextAttrs = rNode.GetpSwpHints())
        {
            for( size_t i = 0; i < pTextAttrs->Count(); ++i )
            {
                const SwTextAttr* pHt = pTextAttrs->Get(i);
                const sal_Int32 startPos = pHt->GetStart();    // first Attr characters
                const sal_Int32* endPos = pHt->End();    // end Attr characters
                // Check if these attributes are for the last character in the paragraph
                // - which means the paragraph marker. If a paragraph has 7 characters,
                // then properties on character 8 are for the paragraph marker
                if( endPos && (startPos == *endPos ) && (*endPos == rNode.GetText().getLength()) )
                {
                    SAL_INFO( "sw.ww8", startPos << "startPos == endPos" << *endPos);
                    sal_uInt16 nWhich = pHt->GetAttr().Which();
                    SAL_INFO( "sw.ww8", "nWhich" << nWhich);
                    if (nWhich == RES_TXTATR_AUTOFMT || nWhich == RES_TXTATR_CHARFMT)
                        aParagraphMarkerProperties.Put(pHt->GetAttr());
                    if (nWhich != RES_TXTATR_CHARFMT)
                        bCharFormatOnly = false;
                }
            }
        }
        if (rNode.GetpSwAttrSet() && bCharFormatOnly)
        {
            aParagraphMarkerProperties.Put(*rNode.GetpSwAttrSet());
        }
        const SwRedlineData* pRedlineParagraphMarkerDelete = AttrOutput().GetParagraphMarkerRedline( rNode, nsRedlineType_t::REDLINE_DELETE );
        const SwRedlineData* pRedlineParagraphMarkerInsert = AttrOutput().GetParagraphMarkerRedline( rNode, nsRedlineType_t::REDLINE_INSERT );
        const SwRedlineData* pParagraphRedlineData = aAttrIter.GetParagraphLevelRedline( );
        AttrOutput().EndParagraphProperties(aParagraphMarkerProperties, pParagraphRedlineData, pRedlineParagraphMarkerDelete, pRedlineParagraphMarkerInsert);
 
        AttrOutput().EndParagraph( pTextNodeInfoInner );
    }while(*aBreakIt != rNode.GetText().getLength() && bNeedParaSplit );
 
    SAL_INFO( "sw.ww8", "</OutWW8_SwTextNode>" );
}
 
// Tables
 
void WW8AttributeOutput::EmptyParagraph()
{
    m_rWW8Export.WriteStringAsPara( OUString() );
}
 
bool MSWordExportBase::NoPageBreakSection( const SfxItemSet* pSet )
{
    bool bRet = false;
    const SfxPoolItem* pI;
    if( pSet)
    {
        bool bNoPageBreak = false;
        if ( SfxItemState::SET != pSet->GetItemState(RES_PAGEDESC, true, &pI)
            || nullptr == static_cast<const SwFormatPageDesc*>(pI)->GetPageDesc() )
        {
            bNoPageBreak = true;
        }
 
        if (bNoPageBreak)
        {
            if (SfxItemState::SET != pSet->GetItemState(RES_BREAK, true, &pI))
                bNoPageBreak = true;
            else
            {
                SvxBreak eBreak = static_cast<const SvxFormatBreakItem*>(pI)->GetBreak();
                switch (eBreak)
                {
                    case SvxBreak::PageBefore:
                    case SvxBreak::PageAfter:
                        bNoPageBreak = false;
                        break;
                    default:
                        break;
                }
            }
        }
        bRet = bNoPageBreak;
    }
    return bRet;
}
 
void MSWordExportBase::OutputSectionNode( const SwSectionNode& rSectionNode )
{
    const SwSection& rSection = rSectionNode.GetSection();
 
    SwNodeIndex aIdx( rSectionNode, 1 );
    const SwNode& rNd = aIdx.GetNode();
    if ( !rNd.IsSectionNode() && !IsInTable()
        && rSection.GetType() != TOX_CONTENT_SECTION && rSection.GetType() != TOX_HEADER_SECTION) //No sections in table
    {
        // if the first Node inside the section has an own
        // PageDesc or PageBreak attribute, then don't write
        // here the section break
        sal_uLong nRstLnNum = 0;
        const SfxItemSet* pSet;
        if ( rNd.IsContentNode() )
        {
            pSet = &rNd.GetContentNode()->GetSwAttrSet();
            nRstLnNum = pSet->Get( RES_LINENUMBER ).GetStartValue();
        }
        else
            pSet = nullptr;
 
        if ( pSet && NoPageBreakSection( pSet ) )
            pSet = nullptr;
 
        if ( !pSet )
        {
            // new Section with no own PageDesc/-Break
            //  -> write follow section break;
            const SwSectionFormat& rFormat = *rSection.GetFormat();
            ReplaceCr( msword::PageBreak ); // Indicator for Page/Section-Break
 
            // Get the page in use at the top of this section
            SwNodeIndex aIdxTmp(rSectionNode, 1);
            const SwPageDesc *pCurrent =
                SwPageDesc::GetPageDescOfNode(aIdxTmp.GetNode());
            if (!pCurrent)
                pCurrent = m_pCurrentPageDesc;
 
            AppendSection( pCurrent, &rFormat, nRstLnNum );
        }
    }
    if ( TOX_CONTENT_SECTION == rSection.GetType() )
        m_bStartTOX = true;
}
 
void WW8Export::AppendSection( const SwPageDesc *pPageDesc, const SwSectionFormat* pFormat, sal_uLong nLnNum )
{
    pSepx->AppendSep(Fc2Cp(Strm().Tell()), pPageDesc, pFormat, nLnNum);
}
 
// Flys
 
void WW8AttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFormat, const Point& rNdTopLeft )
{
    const SwFrameFormat &rFrameFormat = rFormat.GetFrameFormat();
    const SwFormatAnchor& rAnch = rFrameFormat.GetAnchor();
 
    bool bUseEscher = true;
 
    if (rFormat.IsInline())
    {
        ww8::Frame::WriterSource eType = rFormat.GetWriterType();
        bUseEscher = eType != ww8::Frame::eGraphic && eType != ww8::Frame::eOle;
 
        /*
         A special case for converting some inline form controls to form fields
         when in winword 8+ mode
        */
        if (bUseEscher && (eType == ww8::Frame::eFormControl))
        {
            if ( m_rWW8Export.MiserableFormFieldExportHack( rFrameFormat ) )
                return ;
        }
    }
 
    if (bUseEscher)
    {
        // write as escher
        m_rWW8Export.AppendFlyInFlys(rFormat, rNdTopLeft);
    }
    else
    {
        bool bDone = false;
 
        // Fetch from node and last node the position in the section
        const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
 
        sal_uLong nStt = pNodeIndex ? pNodeIndex->GetIndex()+1                  : 0;
        sal_uLong nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : 0;
 
        if( nStt >= nEnd )      // no range, hence no valid node
            return;
 
        if ( !m_rWW8Export.IsInTable() && rFormat.IsInline() )
        {
            //Test to see if this textbox contains only a single graphic/ole
            SwTextNode* pParTextNode = rAnch.GetContentAnchor()->nNode.GetNode().GetTextNode();
            if ( pParTextNode && !m_rWW8Export.m_pDoc->GetNodes()[ nStt ]->IsNoTextNode() )
                bDone = true;
        }
        if( !bDone )
        {
 
            m_rWW8Export.SaveData( nStt, nEnd );
 
            Point aOffset;
            if ( m_rWW8Export.m_pParentFrame )
            {
                /* Munge flys in fly into absolutely positioned elements for word 6 */
                const SwTextNode* pParTextNode = rAnch.GetContentAnchor()->nNode.GetNode().GetTextNode();
                const SwRect aPageRect = pParTextNode->FindPageFrameRect();
 
                aOffset = rFrameFormat.FindLayoutRect().Pos();
                aOffset -= aPageRect.Pos();
 
                m_rWW8Export.m_pFlyOffset = &aOffset;
                m_rWW8Export.m_eNewAnchorType = RndStdIds::FLY_AT_PAGE;
            }
 
            m_rWW8Export.m_pParentFrame = &rFormat;
            if (
                m_rWW8Export.IsInTable() &&
                 (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) &&
                 !m_rWW8Export.m_pDoc->GetNodes()[ nStt ]->IsNoTextNode()
               )
            {
                // note: set Flag  bOutTable again,
                // because we deliver the normal content of the table cell, and no border
                // ( Flag was deleted above in aSaveData() )
                m_rWW8Export.m_bOutTable = true;
                const OUString& aName = rFrameFormat.GetName();
                m_rWW8Export.StartCommentOutput(aName);
                m_rWW8Export.WriteText();
                m_rWW8Export.EndCommentOutput(aName);
            }
            else
                m_rWW8Export.WriteText();
 
            m_rWW8Export.RestoreData();
        }
    }
}
 
void AttributeOutputBase::OutputFlyFrame( const ww8::Frame& rFormat )
{
    if ( !rFormat.GetContentNode() )
        return;
 
    const SwContentNode &rNode = *rFormat.GetContentNode();
    Point aLayPos;
 
    // get the Layout Node-Position
    if (RndStdIds::FLY_AT_PAGE == rFormat.GetFrameFormat().GetAnchor().GetAnchorId())
        aLayPos = rNode.FindPageFrameRect().Pos();
    else
        aLayPos = rNode.FindLayoutRect().Pos();
 
    OutputFlyFrame_Impl( rFormat, aLayPos );
}
 
// write data of any redline
void WW8AttributeOutput::Redline( const SwRedlineData* pRedline )
{
    if ( !pRedline )
        return;
 
    if ( pRedline->Next() )
        Redline( pRedline->Next() );
 
    static const sal_uInt16 insSprmIds[ 3 ] =
    {
        // Ids for insert // for WW8
        NS_sprm::sprmCFRMarkIns, NS_sprm::sprmCIbstRMark, NS_sprm::sprmCDttmRMark,
    };
    static const sal_uInt16 delSprmIds[ 3 ] =
    {
        // Ids for delete // for WW8
        NS_sprm::sprmCFRMarkDel, NS_sprm::sprmCIbstRMarkDel, NS_sprm::sprmCDttmRMarkDel,
    };
 
    const sal_uInt16* pSprmIds = nullptr;
    switch( pRedline->GetType() )
    {
    case nsRedlineType_t::REDLINE_INSERT:
        pSprmIds = insSprmIds;
        break;
 
    case nsRedlineType_t::REDLINE_DELETE:
        pSprmIds = delSprmIds;
        break;
 
    case nsRedlineType_t::REDLINE_FORMAT:
        m_rWW8Export.InsUInt16( NS_sprm::sprmCPropRMark90 );
        m_rWW8Export.pO->push_back( 7 );       // len
        m_rWW8Export.pO->push_back( 1 );
        m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
        m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
        break;
    default:
        OSL_ENSURE(false, "Unhandled redline type for export");
        break;
    }
 
    if ( pSprmIds )
    {
        m_rWW8Export.InsUInt16( pSprmIds[0] );
        m_rWW8Export.pO->push_back( 1 );
 
        m_rWW8Export.InsUInt16( pSprmIds[1] );
        m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
 
        m_rWW8Export.InsUInt16( pSprmIds[2] );
        m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
    }
}
 
void MSWordExportBase::OutputContentNode( SwContentNode& rNode )
{
    switch ( rNode.GetNodeType() )
    {
        case SwNodeType::Text:
            OutputTextNode( *rNode.GetTextNode() );
            break;
        case SwNodeType::Grf:
            OutputGrfNode( *rNode.GetGrfNode() );
            break;
        case SwNodeType::Ole:
            OutputOLENode( *rNode.GetOLENode() );
            break;
        default:
            SAL_WARN("sw.ww8", "Unhandled node, type == " << static_cast<int>(rNode.GetNodeType()) );
            break;
    }
}
 
 
WW8Ruby::WW8Ruby(const SwTextNode& rNode, const SwFormatRuby& rRuby, const MSWordExportBase& rExport ):
    m_nJC(0),
    m_cDirective(0),
    m_nRubyHeight(0),
    m_nBaseHeight(0)
{
    switch ( rRuby.GetAdjustment() )
    {
        case css::text::RubyAdjust_LEFT:
            m_nJC = 3;
            m_cDirective = 'l';
            break;
        case css::text::RubyAdjust_CENTER:
            //defaults to 0
            break;
        case css::text::RubyAdjust_RIGHT:
            m_nJC = 4;
            m_cDirective = 'r';
            break;
        case css::text::RubyAdjust_BLOCK:
            m_nJC = 1;
            m_cDirective = 'd';
            break;
        case css::text::RubyAdjust_INDENT_BLOCK:
            m_nJC = 2;
            m_cDirective = 'd';
            break;
        default:
            OSL_ENSURE( false,"Unhandled Ruby justification code" );
            break;
    }
 
    if ( rRuby.GetPosition() == css::text::RubyPosition::INTER_CHARACTER )
    {
        m_nJC = 5;
        m_cDirective = 0;
    }
 
    /*
     MS needs to know the name and size of the font used in the ruby item,
     but we could have written it in a mixture of asian and western
     scripts, and each of these can be a different font and size than the
     other, so we make a guess based upon the first character of the text,
     defaulting to asian.
     */
    assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
    sal_uInt16 nRubyScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
 
    const SwTextRuby* pRubyText = rRuby.GetTextRuby();
    const SwCharFormat* pFormat = pRubyText ? pRubyText->GetCharFormat() : nullptr;
 
    if (pFormat)
    {
        const auto& rFont
            = ItemGet<SvxFontItem>(*pFormat, GetWhichOfScript(RES_CHRATR_FONT, nRubyScript));
        m_sFontFamily = rFont.GetFamilyName();
 
        const auto& rHeight = ItemGet<SvxFontHeightItem>(
            *pFormat, GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
        m_nRubyHeight = rHeight.GetHeight();
    }
    else
    {
        /*Get defaults if no formatting on ruby text*/
 
        const SfxItemPool* pPool = rNode.GetSwAttrSet().GetPool();
        pPool = pPool ? pPool : &rExport.m_pDoc->GetAttrPool();
 
 
        const auto& rFont
            = DefaultItemGet<SvxFontItem>(*pPool, GetWhichOfScript(RES_CHRATR_FONT, nRubyScript));
        m_sFontFamily = rFont.GetFamilyName();
 
        const auto& rHeight = DefaultItemGet<SvxFontHeightItem>(
            *pPool, GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
        m_nRubyHeight = rHeight.GetHeight();
    }
 
    if (pRubyText)
        nRubyScript
            = g_pBreakIt->GetBreakIter()->getScriptType(rNode.GetText(), pRubyText->GetStart());
    else
        nRubyScript = i18n::ScriptType::ASIAN;
 
    const OUString &rText = rNode.GetText();
    sal_uInt16 nScript = i18n::ScriptType::LATIN;
 
    if (!rText.isEmpty())
        nScript = g_pBreakIt->GetBreakIter()->getScriptType(rText, 0);
 
    sal_uInt16 nWhich = GetWhichOfScript(RES_CHRATR_FONTSIZE, nScript);
    auto& rHeightItem = static_cast<const SvxFontHeightItem&>(rExport.GetItem(nWhich));
    m_nBaseHeight = rHeightItem.GetHeight();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V595 The 'pFont' pointer was utilized before it was verified against nullptr. Check lines: 504, 517.

V560 A part of conditional expression is always true: pRet.

V547 Expression '!pCurRedline' is always true.

V1029 Numeric Truncation Error. Result of the 'size' function is written to the 16-bit variable.

V522 There might be dereferencing of a potential null pointer 'pNewCharFmtGrabBag'.

V506 Pointer to local variable 'aOffset' is stored outside the scope of this variable. Such a pointer will become invalid.

V560 A part of conditional expression is always true: pBreakAtParaStyle.