/* -*- 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 <sal/config.h>
#include <sal/log.hxx>

#include <bookmark.hxx>
#include <DocumentContentOperationsManager.hxx>
#include <hintids.hxx>
#include <editeng/rsiditem.hxx>
#include <editeng/nhypitem.hxx>
#include <osl/diagnose.h>
#include <svl/whiter.hxx>
#include <svl/itemiter.hxx>
#include <editeng/charhiddenitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/lrspitem.hxx>
#include <txtinet.hxx>
#include <txtflcnt.hxx>
#include <fmtfld.hxx>
#include <fmtrfmrk.hxx>
#include <fmtanchr.hxx>
#include <fmtinfmt.hxx>
#include <txtatr.hxx>
#include <fchrfmt.hxx>
#include <fmtautofmt.hxx>
#include <fmtflcnt.hxx>
#include <fmtftn.hxx>
#include <txttxmrk.hxx>
#include <txtrfmrk.hxx>
#include <txtftn.hxx>
#include <textlinebreak.hxx>
#include <txtfld.hxx>
#include <txtannotationfld.hxx>
#include <charfmt.hxx>
#include <frmfmt.hxx>
#include <ftnidx.hxx>
#include <fmtruby.hxx>
#include <fmtmeta.hxx>
#include <formatcontentcontrol.hxx>
#include <formatflysplit.hxx>
#include <textcontentcontrol.hxx>
#include <breakit.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <fldbas.hxx>
#include <pam.hxx>
#include <ndtxt.hxx>
#include <txtfrm.hxx>
#include <rootfrm.hxx>
#include <rolbck.hxx>
#include <ddefld.hxx>
#include <docufld.hxx>
#include <expfld.hxx>
#include <usrfld.hxx>
#include <poolfmt.hxx>
#include <istyleaccess.hxx>
#include <docsh.hxx>
#include <algorithm>
#include <map>
#include <memory>

#include <rdfhelper.hxx>
#include <hints.hxx>
#include <unotxdoc.hxx>

#include <officecfg/Office/Common.hxx>

#ifdef DBG_UTIL
#define CHECK           Check(true);
#define CHECK_NOTMERGED Check(false);
#else
#define CHECK_NOTMERGED
#endif

SwpHints::SwpHints(const SwTextNode& rParent)
    : m_rParent(rParent)
    , m_pHistory(nullptr)
    , m_bInSplitNode(false)
    , m_bCalcHiddenParaField(false)
    , m_bHiddenByParaField(false)
    , m_bFootnote(false)
    , m_bDDEFields(false)
{
}

static void TextAttrDelete( SwTextAttr * const pAttr )
{
    if (RES_TXTATR_META == pAttr->Which() ||
        RES_TXTATR_METAFIELD == pAttr->Which())
    {
        static_txtattr_cast<SwTextMeta *>(pAttr)->ChgTextNode(nullptr); // prevents ASSERT
    }
    else if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
    {
        static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr);
    }
    SwTextAttr::Destroy( pAttr );
}

static bool TextAttrContains(const sal_Int32 nPos, const SwTextAttrEnd * const pAttr)
{
    return (pAttr->GetStart() < nPos) && (nPos < *pAttr->End());
}

// a:       |-----|
// b:
//    |---|               => valid: b before a
//    |-----|             => valid: start == end; b before a
//    |---------|         => invalid: overlap (1)
//    |-----------|       => valid: same end; b around a
//    |-----------------| => valid: b around a
//          |---|         => valid; same start; b within a
//          |-----|       => valid; same start and end; b around or within a?
//          |-----------| => valid: same start: b around a
//            |-|         => valid: b within a
//            |---|       => valid: same end; b within a
//            |---------| => invalid: overlap (2)
//                |-----| => valid: end == start; b after a
//                  |---| => valid: b after a
// ===> 2 invalid overlap cases
static
bool isOverlap(const sal_Int32 nStart1, const sal_Int32 nEnd1,
               const sal_Int32 nStart2, const sal_Int32 nEnd2)
{
    return
        ((nStart1 > nStart2) && (nStart1 < nEnd2) && (nEnd1 > nEnd2))  // (1)
     || ((nStart1 < nStart2) && (nStart2 < nEnd1) && (nEnd1 < nEnd2)); // (2)
}

/// #i106930#: now asymmetric: empty hint1 is _not_ nested, but empty hint2 is
static
bool isNestedAny(const sal_Int32 nStart1, const sal_Int32 nEnd1,
                 const sal_Int32 nStart2, const sal_Int32 nEnd2)
{
    return ((nStart1 == nStart2) || (nEnd1 == nEnd2))
        // same start/end: nested except if hint1 empty and hint2 not empty
        ? (nStart1 != nEnd1) || (nStart2 == nEnd2)
        : ((nStart1 < nStart2) ? (nEnd1 >= nEnd2) : (nEnd1 <= nEnd2));
}

static
bool isSelfNestable(const sal_uInt16 nWhich)
{
    if ((RES_TXTATR_INETFMT  == nWhich) ||
        (RES_TXTATR_CJK_RUBY == nWhich) ||
        (RES_TXTATR_INPUTFIELD == nWhich))
        return false;
    assert((RES_TXTATR_META  == nWhich) ||
           (RES_TXTATR_METAFIELD  == nWhich) ||
           (RES_TXTATR_CONTENTCONTROL  == nWhich));
    return true;
}

static
bool isSplittable(const sal_uInt16 nWhich)
{
    if ((RES_TXTATR_INETFMT  == nWhich) ||
        (RES_TXTATR_CJK_RUBY == nWhich))
        return true;
    assert((RES_TXTATR_META  == nWhich) ||
           (RES_TXTATR_METAFIELD  == nWhich) ||
           (RES_TXTATR_INPUTFIELD  == nWhich) ||
           (RES_TXTATR_CONTENTCONTROL  == nWhich));
    return false;
}

namespace {

enum Split_t { FAIL, SPLIT_NEW, SPLIT_OTHER };

}

/**
  Calculate splitting policy for overlapping hints, based on what kind of
  hint is inserted, and what kind of existing hint overlaps.
  */
static Split_t
splitPolicy(const sal_uInt16 nWhichNew, const sal_uInt16 nWhichOther)
{
    if (!isSplittable(nWhichOther))
    {
        if (!isSplittable(nWhichNew))
            return FAIL;
        else
            return SPLIT_NEW;
    }
    else
    {
        if ( RES_TXTATR_INPUTFIELD == nWhichNew )
            return FAIL;
        else if ( (RES_TXTATR_INETFMT  == nWhichNew) &&
                  (RES_TXTATR_CJK_RUBY == nWhichOther) )
            return SPLIT_NEW;
        else
            return SPLIT_OTHER;
    }
}

void SwTextINetFormat::InitINetFormat(SwTextNode & rNode)
{
    ChgTextNode(&rNode);
    SwCharFormat * const pFormat(
         rNode.GetDoc().getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_INET_NORMAL) );
    pFormat->Add(*this);
}

void SwTextRuby::InitRuby(SwTextNode & rNode)
{
    ChgTextNode(&rNode);
    SwCharFormat * const pFormat(
        rNode.GetDoc().getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_RUBYTEXT) );
    EndListeningAll();
    StartListening(pFormat->GetNotifier());
}

/**
  Create a new nesting text hint.
 */
static SwTextAttrNesting *
MakeTextAttrNesting(SwTextNode & rNode, SwTextAttrNesting & rNesting,
        const sal_Int32 nStart, const sal_Int32 nEnd)
{
    SwTextAttr * const pNew( MakeTextAttr(
            rNode.GetDoc(), rNesting.GetAttr(), nStart, nEnd ) );
    switch (pNew->Which())
    {
        case RES_TXTATR_INETFMT:
        {
            static_txtattr_cast<SwTextINetFormat*>(pNew)->InitINetFormat(rNode);
            break;
        }
        case RES_TXTATR_CJK_RUBY:
        {
            static_txtattr_cast<SwTextRuby*>(pNew)->InitRuby(rNode);
            break;
        }
        default:
            assert(!"MakeTextAttrNesting: what the hell is that?");
            break;
    }
    return static_txtattr_cast<SwTextAttrNesting*>(pNew);
}

typedef std::vector<SwTextAttrNesting *> NestList_t;

static NestList_t::iterator
lcl_DoSplitImpl(NestList_t & rSplits, SwTextNode & rNode,
    NestList_t::iterator const iter, sal_Int32 const nSplitPos,
    bool const bSplitAtStart, bool const bOtherDummy)
{
    const sal_Int32 nStartPos( // skip other's dummy character!
        (bSplitAtStart && bOtherDummy) ? nSplitPos + 1 : nSplitPos );
    SwTextAttrNesting * const pNew( MakeTextAttrNesting(
            rNode, **iter, nStartPos, *(*iter)->GetEnd() ) );
    (*iter)->SetEnd(nSplitPos);
    return rSplits.insert(iter + 1, pNew);
}

static void
lcl_DoSplitNew(NestList_t & rSplits, SwTextNode & rNode,
    const sal_Int32 nNewStart,
    const sal_Int32 nOtherStart, const sal_Int32 nOtherEnd, bool bOtherDummy)
{
    const bool bSplitAtStart(nNewStart < nOtherStart);
    const sal_Int32 nSplitPos( bSplitAtStart ? nOtherStart : nOtherEnd );
    // first find the portion that is split (not necessarily the last one!)
    NestList_t::iterator const iter(
        std::find_if( rSplits.begin(), rSplits.end(),
            [nSplitPos](SwTextAttrEnd * const pAttr) {
                return TextAttrContains(nSplitPos, pAttr);
            } ) );
    if (iter != rSplits.end()) // already split here?
    {
        lcl_DoSplitImpl(rSplits, rNode, iter, nSplitPos, bSplitAtStart, bOtherDummy);
    }
}

/**
  Insert nesting hint into the hints array. Also calls NoteInHistory.
  @param    rNewHint    the hint to be inserted (must not overlap existing!)
 */
void SwpHints::InsertNesting(SwTextAttrNesting & rNewHint)
{
    Insert(& rNewHint);
    NoteInHistory( & rNewHint, true );
}

/**

The following hints correspond to well-formed XML elements in ODF:
RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD,
RES_TXTATR_CONTENTCONTROL

The writer core must ensure that these do not overlap; if they did,
the document would not be storable as ODF.

Also, a Hyperlink must not be nested within another Hyperlink,
and a Ruby must not be nested within another Ruby.

The ODF export in xmloff will only put a hyperlink into a ruby, never a ruby
into a hyperlink.

Unfortunately the UNO API for Hyperlink and Ruby consists of the properties
Hyperlink* and Ruby* of the css.text.CharacterProperties service.  In other
words, they are treated as formatting attributes, not as content entities.
Furthermore, for API users it is not possible to easily test whether a certain
range would be overlapping with other nested attributes, and most importantly,
<em>which ones</em>, so we can hardly refuse to insert these in cases of
overlap.

It is possible to split Hyperlink and Ruby into multiple portions, such that
the result is properly nested.

meta and meta-field must not be split, because they have xml:id.

content controls should not split, either.

These constraints result in the following design:

RES_TXTATR_INETFMT:
    always succeeds
    inserts n attributes split at RES_TXTATR_CJK_RUBY, RES_TXTATR_META,
        RES_TXTATR_METAFIELD
    may replace existing RES_TXTATR_INETFMT at overlap
RES_TXTATR_CJK_RUBY:
    always succeeds
    inserts n attributes split at RES_TXTATR_META, RES_TXTATR_METAFIELD
    may replace existing RES_TXTATR_CJK_RUBY at overlap
    may split existing overlapping RES_TXTATR_INETFMT
RES_TXTATR_META:
    may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD
    may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY
    inserts 1 attribute
RES_TXTATR_METAFIELD:
    may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD
    may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY
    inserts 1 attribute

The nesting is expressed by the position of the hints.
RES_TXTATR_META and RES_TXTATR_METAFIELD have a CH_TXTATR, and there can
only be one such hint starting and ending at a given position.
Only RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY lack a CH_TXTATR.
The interpretation given is that RES_TXTATR_CJK_RUBY is always around
a RES_TXTATR_INETFMT at the same start and end position (which corresponds
with the UNO API).
Both of these are always around a nesting hint with CH_TXTATR at the same
start and end position (if they should be inside, then the start should be
after the CH_TXTATR).
It would probably be a bad idea to add another nesting hint without
CH_TXTATR; on the other hand, it would be difficult adding a CH_TXTATR to
RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY, due to the overwriting and
splitting of existing hints that is necessary for backward compatibility.

    @param rNode    the text node
    @param rHint    the hint to be inserted
    @returns        true iff hint was successfully inserted
*/
bool
SwpHints::TryInsertNesting( SwTextNode & rNode, SwTextAttrNesting & rNewHint )
{
//    INVARIANT:  the nestable hints in the array are properly nested
    const sal_uInt16 nNewWhich( rNewHint.Which() );
    const sal_Int32 nNewStart( rNewHint.GetStart() );
    const sal_Int32 nNewEnd  ( *rNewHint.GetEnd()   );
    const bool bNewSelfNestable( isSelfNestable(nNewWhich) );

    assert( (RES_TXTATR_INETFMT   == nNewWhich) ||
            (RES_TXTATR_CJK_RUBY  == nNewWhich) ||
            (RES_TXTATR_META      == nNewWhich) ||
            (RES_TXTATR_METAFIELD == nNewWhich) ||
            (RES_TXTATR_CONTENTCONTROL == nNewWhich) ||
            (RES_TXTATR_INPUTFIELD == nNewWhich));

    NestList_t OverlappingExisting; // existing hints to be split
    NestList_t OverwrittenExisting; // existing hints to be replaced
    NestList_t SplitNew;            // new hints to be inserted

    SplitNew.push_back(& rNewHint);

    // pass 1: split the inserted hint into fragments if necessary
    for ( size_t i = 0; i < Count(); ++i )
    {
        SwTextAttr * const pOther = GetSortedByEnd(i);

        if (pOther->IsNesting())
        {
            const sal_uInt16 nOtherWhich( pOther->Which() );
            const sal_Int32 nOtherStart( pOther->GetStart() );
            const sal_Int32 nOtherEnd  ( *pOther->GetEnd()   );
            if (isOverlap(nNewStart, nNewEnd, nOtherStart, nOtherEnd ))
            {
                switch (splitPolicy(nNewWhich, nOtherWhich))
                {
                    case FAIL:
                        SAL_INFO("sw.core", "cannot insert hint: overlap");
                        for (const auto& aSplit : SplitNew)
                            TextAttrDelete(aSplit);
                        return false;
                    case SPLIT_NEW:
                        lcl_DoSplitNew(SplitNew, rNode, nNewStart,
                            nOtherStart, nOtherEnd, pOther->HasDummyChar());
                        break;
                    case SPLIT_OTHER:
                        OverlappingExisting.push_back(
                            static_txtattr_cast<SwTextAttrNesting*>(pOther));
                        break;
                    default:
                        assert(!"bad code monkey");
                        break;
                }
            }
            else if (isNestedAny(nNewStart, nNewEnd, nOtherStart, nOtherEnd))
            {
                if (!bNewSelfNestable && (nNewWhich == nOtherWhich))
                {
                    // ruby and hyperlink: if there is nesting, _overwrite_
                    OverwrittenExisting.push_back(
                        static_txtattr_cast<SwTextAttrNesting*>(pOther));
                }
                else if ((nNewStart == nOtherStart) && pOther->HasDummyChar())
                {
                    if (rNewHint.HasDummyChar())
                    {
                        assert(!"ERROR: inserting duplicate CH_TXTATR hint");
                        return false;
                    } else if (nNewEnd < nOtherEnd) {
                        // other has dummy char, new is inside other, but
                        // new contains the other's dummy char?
                        // should be corrected because it may lead to problems
                        // in SwXMeta::createEnumeration
                        // SplitNew is sorted, so this is the first split
                        assert(SplitNew.front()->GetStart() == nNewStart);
                        SplitNew.front()->SetStart(nNewStart + 1);
                    }
                }
            }
        }
    }

    // pass 1b: tragically need to check for fieldmarks here too
    for (auto iter = SplitNew.begin(); iter != SplitNew.end(); ++iter)
    {
        SwPaM const temp(rNode, (*iter)->GetStart(), rNode, *(*iter)->GetEnd());
        std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
        sw::CalcBreaks(Breaks, temp, true);
        if (!Breaks.empty())
        {
            if (!isSplittable(nNewWhich))
            {
                SAL_INFO("sw.core", "cannot insert hint: fieldmark overlap");
                assert(SplitNew.size() == 1);
                TextAttrDelete(&rNewHint);
                return false;
            }
            else
            {
                for (auto const& rPos : Breaks)
                {
                    assert(rPos.first == rNode.GetIndex());
                    iter = lcl_DoSplitImpl(SplitNew, rNode, iter,
                        rPos.second, true, true);
                }
            }
        }
    }

    assert((isSplittable(nNewWhich) || SplitNew.size() == 1) &&
            "splitting the unsplittable ???");

    // pass 2: split existing hints that overlap/nest with new hint
    // do not iterate over hints array, but over remembered set of overlapping
    // hints, to keep things simple w.r.t. insertion/removal
    // N.B: if there is a hint that splits the inserted hint, then
    // that hint would also have already split any hint in OverlappingExisting
    // so any hint in OverlappingExisting can be split at most by one hint
    // in SplitNew, or even not at all (this is not true for existing hints
    // that go _around_ new hint, which is the reason d'^etre for pass 4)
    for (auto& rpOther : OverlappingExisting)
    {
        const sal_Int32 nOtherStart( rpOther->GetStart() );
        const sal_Int32 nOtherEnd  ( *rpOther->GetEnd()   );

        for (const auto& rpNew : SplitNew)
        {
            const sal_Int32 nSplitNewStart( rpNew->GetStart() );
            const sal_Int32 nSplitNewEnd  ( *rpNew->GetEnd()   );
            // 4 cases: within, around, overlap l, overlap r, (OTHER: no action)
            const bool bRemoveOverlap(
                !bNewSelfNestable && (nNewWhich == rpOther->Which()) );

            switch (ComparePosition(nSplitNewStart, nSplitNewEnd,
                                    nOtherStart,    nOtherEnd))
            {
                case SwComparePosition::Inside:
                    {
                        assert(!bRemoveOverlap &&
                            "this one should be in OverwrittenExisting?");
                    }
                    break;
                case SwComparePosition::Outside:
                case SwComparePosition::Equal:
                    {
                        assert(!"existing hint inside new hint: why?");
                    }
                    break;
                case SwComparePosition::OverlapBefore:
                    {
                        Delete( rpOther ); // this also does NoteInHistory!
                        rpOther->SetStart(nSplitNewEnd);
                        InsertNesting( *rpOther );
                        if (!bRemoveOverlap)
                        {
                            if ( MAX_HINTS <= Count() )
                            {
                                SAL_INFO("sw.core", "hints array full :-(");
                                return false;
                            }
                            SwTextAttrNesting * const pOtherLeft(
                                MakeTextAttrNesting( rNode, *rpOther,
                                    nOtherStart, nSplitNewEnd ) );
                            InsertNesting( *pOtherLeft );
                        }
                    }
                    break;
                case SwComparePosition::OverlapBehind:
                    {
                        Delete( rpOther ); // this also does NoteInHistory!
                        rpOther->SetEnd(nSplitNewStart);
                        InsertNesting( *rpOther );
                        if (!bRemoveOverlap)
                        {
                            if ( MAX_HINTS <= Count() )
                            {
                                SAL_INFO("sw.core", "hints array full :-(");
                                return false;
                            }
                            SwTextAttrNesting * const pOtherRight(
                                MakeTextAttrNesting( rNode, *rpOther,
                                    nSplitNewStart, nOtherEnd ) );
                            InsertNesting( *pOtherRight );
                        }
                    }
                    break;
                default:
                    break; // overlap resolved by splitting new: nothing to do
            }
        }
    }

    if ( MAX_HINTS <= Count() || MAX_HINTS - Count() <= SplitNew.size() )
    {
        SAL_INFO("sw.core", "hints array full :-(");
        return false;
    }

    // pass 3: insert new hints
    for (const auto& rpHint : SplitNew)
    {
        InsertNesting(*rpHint);
    }

    // pass 4: handle overwritten hints
    // RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY should displace attributes
    // of the same kind.
    for (auto& rpOther : OverwrittenExisting)
    {
        const sal_Int32 nOtherStart( rpOther->GetStart() );
        const sal_Int32 nOtherEnd  ( *rpOther->GetEnd()   );

        // overwritten portion is given by start/end of inserted hint
        if ((nNewStart <= nOtherStart) && (nOtherEnd <= nNewEnd))
        {
            Delete(rpOther);
            rNode.DestroyAttr( rpOther );
        }
        else
        {
            assert((nOtherStart < nNewStart) || (nNewEnd < nOtherEnd));
        // scenario: there is a RUBY, and contained within that a META;
        // now a RUBY is inserted within the META => the existing RUBY is split:
        // here it is not possible to simply insert the left/right fragment
        // of the existing RUBY because they <em>overlap</em> with the META!
            Delete( rpOther ); // this also does NoteInHistory!
            if (nNewEnd < nOtherEnd)
            {
                SwTextAttrNesting * const pOtherRight(
                    MakeTextAttrNesting(
                        rNode, *rpOther, nNewEnd, nOtherEnd ) );
                bool const bSuccess( TryInsertNesting(rNode, *pOtherRight) );
                SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 1 failed?");
            }
            if (nOtherStart < nNewStart)
            {
                rpOther->SetEnd(nNewStart);
                bool const bSuccess( TryInsertNesting(rNode, *rpOther) );
                SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 2 failed?");
            }
            else
            {
                rNode.DestroyAttr(rpOther);
            }
        }
    }

    return true;
}

// This function takes care for the following text attribute:
// RES_TXTATR_CHARFMT, RES_TXTATR_AUTOFMT
// These attributes have to be handled in a special way (Portion building).

// The new attribute will be split by any existing RES_TXTATR_AUTOFMT or
// RES_TXTATR_CHARFMT. The new attribute itself will
// split any existing RES_TXTATR_AUTOFMT or RES_TXTATR_CHARFMT.

void SwpHints::BuildPortions( SwTextNode& rNode, SwTextAttr& rNewHint,
        const SetAttrMode nMode )
{
    const sal_uInt16 nWhich = rNewHint.Which();

    const sal_Int32 nThisStart = rNewHint.GetStart();
    const sal_Int32 nThisEnd =   *rNewHint.GetEnd();
    const bool bNoLengthAttribute = nThisStart == nThisEnd;

    std::vector<SwTextAttr*> aInsDelHints;

    assert( RES_TXTATR_CHARFMT == rNewHint.Which() ||
            RES_TXTATR_AUTOFMT == rNewHint.Which() );

    // 2. Find the hints which cover the start and end position
    // of the new hint. These hints have to be split into two portions:

    if ( !bNoLengthAttribute ) // nothing to do for no length attributes
    {
        for ( size_t i = 0, nCnt = m_HintsByStart.size(); i < nCnt; ++i )
        {
            // we're modifying stuff here which affects the sorting, and we
            // don't want it changing underneath us
            SwTextAttr* pOther = m_HintsByStart[i];

            if ( RES_TXTATR_CHARFMT != pOther->Which() &&
                 RES_TXTATR_AUTOFMT != pOther->Which() )
                continue;

            sal_Int32 nOtherStart = pOther->GetStart();
            if (nThisEnd < nOtherStart)
                break;
            const sal_Int32 nOtherEnd = *pOther->GetEnd();

            // Check if start of new attribute overlaps with pOther:
            // Split pOther if necessary:
            if ( nOtherStart < nThisStart && nThisStart < nOtherEnd )
            {
                SwTextAttr* pNewAttr = MakeTextAttr( rNode.GetDoc(),
                        pOther->GetAttr(), nOtherStart, nThisStart );
                if ( RES_TXTATR_CHARFMT == pOther->Which() )
                {
                    static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(
                        static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber() );
                }
                aInsDelHints.push_back( pNewAttr );

                NoteInHistory( pOther );
                pOther->SetStart(nThisStart);
                NoteInHistory( pOther, true );

                nOtherStart = nThisStart;
            }

            // Check if end of new attribute overlaps with pOther:
            // Split pOther if necessary:
            if ( nOtherStart < nThisEnd && nThisEnd < nOtherEnd )
            {
                SwTextAttr* pNewAttr = MakeTextAttr( rNode.GetDoc(),
                        pOther->GetAttr(), nOtherStart, nThisEnd );
                if ( RES_TXTATR_CHARFMT == pOther->Which() )
                {
                    static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(
                        static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber());
                }
                aInsDelHints.push_back( pNewAttr );

                NoteInHistory( pOther );
                pOther->SetStart(nThisEnd);
                NoteInHistory( pOther, true );
            }
        }

        // Insert the newly created attributes:
        for ( const auto& rpHint : aInsDelHints )
        {
            Insert( rpHint );
            NoteInHistory( rpHint, true );
        }
    }

#ifdef DBG_UTIL
    if( !rNode.GetDoc().IsInReading() )
        CHECK_NOTMERGED; // ignore flags not set properly yet, don't check them
#endif

    // 4. Split rNewHint into 1 ... n new hints:

    o3tl::sorted_vector<sal_Int32> aBounds;
    aBounds.insert( nThisStart );
    aBounds.insert( nThisEnd );

    if ( !bNoLengthAttribute ) // nothing to do for no length attributes
    {
        for ( size_t i = 0, nCnt = m_HintsByStart.size(); i < nCnt; ++i )
        {
            const SwTextAttr* pOther = m_HintsByStart[i];

            if ( RES_TXTATR_CHARFMT != pOther->Which() &&
                 RES_TXTATR_AUTOFMT != pOther->Which() )
                continue;

            const sal_Int32 nOtherStart = pOther->GetStart();
            if (nThisEnd < nOtherStart)
                break;
            const sal_Int32 nOtherEnd = *pOther->End();

            if (nThisStart <= nOtherStart && nOtherStart <= nThisEnd)
                aBounds.insert( nOtherStart );
            if (nThisStart <= nOtherEnd && nOtherEnd <= nThisEnd)
                aBounds.insert( nOtherEnd );
        }
    }

    auto aStartIter = aBounds.lower_bound( nThisStart );
    auto aEndIter = aBounds.upper_bound( nThisEnd );
    sal_Int32 nPorStart = *aStartIter;
    ++aStartIter;
    bool bDestroyHint = true;

    // Insert the 1...n new parts of the new attribute:

    while ( aStartIter != aEndIter || bNoLengthAttribute )
    {
        OSL_ENSURE( bNoLengthAttribute || nPorStart < *aStartIter, "AUTOSTYLES: BuildPortion trouble" );

        const sal_Int32 nPorEnd = bNoLengthAttribute ? nPorStart : *aStartIter;
        aInsDelHints.clear();

        // Get all hints that are in [nPorStart, nPorEnd[:
        for ( size_t i = 0, nCnt = m_HintsByStart.size(); i < nCnt; ++i )
        {
            // we get called from TryInsertHint, which changes ordering
            SwTextAttr *pOther = m_HintsByStart[i];

            if ( RES_TXTATR_CHARFMT != pOther->Which() &&
                 RES_TXTATR_AUTOFMT != pOther->Which() )
                continue;

            const sal_Int32 nOtherStart = pOther->GetStart();

            if ( nOtherStart > nPorStart )
                break;

            if ( pOther->GetEnd() && *pOther->GetEnd() == nPorEnd && nOtherStart == nPorStart )
            {
                OSL_ENSURE( *pOther->GetEnd() == nPorEnd, "AUTOSTYLES: BuildPortion trouble" );
                aInsDelHints.push_back( pOther );
            }
        }

        SwTextAttr* pNewAttr = nullptr;
        if ( RES_TXTATR_CHARFMT == nWhich )
        {
            // pNewHint can be inserted after calculating the sort value.
            // This should ensure, that pNewHint comes behind the already present
            // character style
            sal_uInt16 nCharStyleCount = 0;
            for ( const auto& rpHint : aInsDelHints )
            {
                if ( RES_TXTATR_CHARFMT == rpHint->Which() )
                {
                    // #i74589#
                    const SwFormatCharFormat& rOtherCharFormat = rpHint->GetCharFormat();
                    const SwFormatCharFormat& rThisCharFormat = rNewHint.GetCharFormat();
                    const bool bSameCharFormat = rOtherCharFormat.GetCharFormat() == rThisCharFormat.GetCharFormat();

                    // #i90311#
                    // Do not remove existing character format hint during XML import
                    if ( !rNode.GetDoc().IsInXMLImport() &&
                         ( !( SetAttrMode::DONTREPLACE & nMode ) ||
                           bNoLengthAttribute ||
                           bSameCharFormat ) )
                    {
                        // Remove old hint
                        Delete( rpHint );
                        rNode.DestroyAttr( rpHint );
                    }
                    else
                        ++nCharStyleCount;
                }
                else
                {
                    // remove all attributes from auto styles, which are explicitly set in
                    // the new character format:
                    OSL_ENSURE( RES_TXTATR_AUTOFMT == rpHint->Which(), "AUTOSTYLES - Misc trouble" );
                    SwTextAttr* pOther = rpHint;
                    const std::shared_ptr<SfxItemSet> & pOldStyle = static_cast<const SwFormatAutoFormat&>(pOther->GetAttr()).GetStyleHandle();

                    // For each attribute in the automatic style check if it
                    // is also set the new character style:
                    SfxItemSet aNewSet( *pOldStyle->GetPool(),
                        aCharAutoFormatSetRange);
                    SfxItemIter aItemIter( *pOldStyle );
                    const SfxPoolItem* pItem = aItemIter.GetCurItem();
                    do
                    {
                        if ( !CharFormat::IsItemIncluded( pItem->Which(), &rNewHint ) )
                        {
                            aNewSet.Put( *pItem );
                        }

                        pItem = aItemIter.NextItem();
                    } while (pItem);

                    // Remove old hint
                    Delete( pOther );
                    rNode.DestroyAttr( pOther );

                    // Create new AutoStyle
                    if ( aNewSet.Count() )
                    {
                        pNewAttr = MakeTextAttr( rNode.GetDoc(),
                                aNewSet, nPorStart, nPorEnd );
                        Insert( pNewAttr );
                        NoteInHistory( pNewAttr, true );
                    }
                }
            }

            // If there is no current hint and start and end of rNewHint
            // is ok, we do not need to create a new txtattr.
            if ( nPorStart == nThisStart &&
                 nPorEnd == nThisEnd &&
                 !nCharStyleCount )
            {
                pNewAttr = &rNewHint;
                bDestroyHint = false;
            }
            else
            {
                pNewAttr = MakeTextAttr( rNode.GetDoc(), rNewHint.GetAttr(),
                        nPorStart, nPorEnd );
                static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(nCharStyleCount);
            }
        }
        else
        {
            // Find the current autostyle. Mix attributes if necessary.
            SwTextAttr* pCurrentAutoStyle = nullptr;
            SwTextAttr* pCurrentCharFormat = nullptr;
            for ( const auto& rpHint : aInsDelHints )
            {
                if ( RES_TXTATR_AUTOFMT == rpHint->Which() )
                    pCurrentAutoStyle = rpHint;
                else if ( RES_TXTATR_CHARFMT == rpHint->Which() )
                    pCurrentCharFormat = rpHint;
            }

            std::shared_ptr<SfxItemSet> pNewStyle = static_cast<const SwFormatAutoFormat&>(rNewHint.GetAttr()).GetStyleHandle();
            if ( pCurrentAutoStyle )
            {
                const std::shared_ptr<SfxItemSet> & pCurrentStyle = static_cast<const SwFormatAutoFormat&>(pCurrentAutoStyle->GetAttr()).GetStyleHandle();

                // Merge attributes
                SfxItemSet aNewSet( *pCurrentStyle );
                aNewSet.Put( *pNewStyle );

                // #i75750# Remove attributes already set at whole paragraph
                // #i81764# This should not be applied for no length attributes!!! <--
                if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && aNewSet.Count() )
                {
                    const SfxItemSet& rWholeParaAttrSet(rNode.GetSwAttrSet());
                    std::vector<sal_uInt16> aDeleteWhichIDs;

                    for (SfxItemIter aIter(aNewSet); !aIter.IsAtEnd(); aIter.NextItem())
                    {
                        const SfxPoolItem* pGet(nullptr);
                        if (SfxItemState::SET == rWholeParaAttrSet.GetItemState(aIter.GetCurWhich(), false, &pGet) &&
                             SfxPoolItem::areSame(pGet, aIter.GetCurItem()))
                        {
                            // Do not clear item if the attribute is set in a character format:
                            if (!pCurrentCharFormat || nullptr == CharFormat::GetItem(*pCurrentCharFormat, aIter.GetCurWhich()))
                                aDeleteWhichIDs.push_back(aIter.GetCurWhich());
                        }
                    }

                    for (auto nDelWhich : aDeleteWhichIDs)
                        aNewSet.ClearItem(nDelWhich);
                }

                // Remove old hint
                Delete( pCurrentAutoStyle );
                rNode.DestroyAttr( pCurrentAutoStyle );

                // Create new AutoStyle
                if ( aNewSet.Count() )
                    pNewAttr = MakeTextAttr( rNode.GetDoc(), aNewSet,
                            nPorStart, nPorEnd );
            }
            else
            {
                // Remove any attributes which are already set at the whole paragraph:
                bool bOptimizeAllowed = true;

                // #i75750# Remove attributes already set at whole paragraph
                // #i81764# This should not be applied for no length attributes!!! <--
                if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && pNewStyle->Count() )
                {
                    std::unique_ptr<SfxItemSet> pNewSet;

                    SfxItemIter aIter2( *pNewStyle );
                    const SfxPoolItem* pItem = aIter2.GetCurItem();
                    const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet();

                    do
                    {
                        const SfxPoolItem* pTmpItem = nullptr;
                        // here direct SfxPoolItem ptr comp was wrong, found using SfxPoolItem::areSame
                        if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) &&
                             SfxPoolItem::areSame(pTmpItem, pItem) )
                        {
                            // Do not clear item if the attribute is set in a character format:
                            if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) )
                            {
                                if ( !pNewSet )
                                    pNewSet = pNewStyle->Clone();
                                pNewSet->ClearItem( pItem->Which() );
                            }
                        }
                    }
                    while ((pItem = aIter2.NextItem()));

                    if ( pNewSet )
                    {
                        bOptimizeAllowed = false;
                        if ( pNewSet->Count() )
                            pNewStyle = rNode.getIDocumentStyleAccess().getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
                        else
                            pNewStyle.reset();
                    }
                }

                // Create new AutoStyle
                // If there is no current hint and start and end of rNewHint
                // is ok, we do not need to create a new txtattr.
                if ( bOptimizeAllowed &&
                     nPorStart == nThisStart &&
                     nPorEnd == nThisEnd )
                {
                    pNewAttr = &rNewHint;
                    bDestroyHint = false;
                }
                else if ( pNewStyle )
                {
                    pNewAttr = MakeTextAttr( rNode.GetDoc(), *pNewStyle,
                            nPorStart, nPorEnd );
                }
            }
        }

        if ( pNewAttr )
        {
            Insert( pNewAttr );
//            if ( bDestroyHint )
            NoteInHistory( pNewAttr, true );
        }

        if ( !bNoLengthAttribute )
        {
            nPorStart = *aStartIter;
            ++aStartIter;
        }
        else
            break;
    }

    if ( bDestroyHint )
        rNode.DestroyAttr( &rNewHint );
}

SwTextAttr* MakeRedlineTextAttr( SwDoc & rDoc, SfxPoolItem const & rAttr )
{
    // this is intended _only_ for special-purpose redline attributes!
    switch (rAttr.Which())
    {
        case RES_CHRATR_COLOR:
        case RES_CHRATR_WEIGHT:
        case RES_CHRATR_CJK_WEIGHT:
        case RES_CHRATR_CTL_WEIGHT:
        case RES_CHRATR_POSTURE:
        case RES_CHRATR_CJK_POSTURE:
        case RES_CHRATR_CTL_POSTURE:
        case RES_CHRATR_UNDERLINE:
        case RES_CHRATR_CROSSEDOUT:
        case RES_CHRATR_CASEMAP:
        case RES_CHRATR_BACKGROUND:
            break;
        default:
            assert(!"unsupported redline attribute");
            break;
    }

    // create a SfxPoolItemHolder and return it (will move Item to referenced mode)
    return new SwTextAttrEnd(SfxPoolItemHolder(rDoc.GetAttrPool(), &rAttr), 0, 0);
}

// create new text attribute
SwTextAttr* MakeTextAttr(
    SwDoc & rDoc,
    SfxPoolItem& rAttr,
    sal_Int32 const nStt,
    sal_Int32 const nEnd,
    CopyOrNewType const bIsCopy,
    SwTextNode *const pTextNode )
{
    if ( isCHRATR(rAttr.Which()) )
    {
        // Somebody wants to build a SwTextAttr for a character attribute.
        // Sorry, this is not allowed any longer.
        // You'll get a brand new autostyle attribute:
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( rDoc.GetAttrPool() );
        aItemSet.Put( rAttr );
        return MakeTextAttr( rDoc, aItemSet, nStt, nEnd );
    }
    else if ( RES_TXTATR_AUTOFMT == rAttr.Which() &&
              static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle()->
                GetPool() != &rDoc.GetAttrPool() )
    {
        // If the attribute is an auto-style which refers to a pool that is
        // different from rDoc's pool, we have to correct this:
        const std::shared_ptr<SfxItemSet> & pAutoStyle = static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle();
        std::unique_ptr<const SfxItemSet> pNewSet(
                pAutoStyle->SfxItemSet::Clone( true, &rDoc.GetAttrPool() ));
        SwTextAttr* pNew = MakeTextAttr( rDoc, *pNewSet, nStt, nEnd );
        return pNew;
    }

    // create a SfxPoolItemHolder and use it (will move Item to referenced mode)
    const SfxPoolItemHolder aHolder(rDoc.GetAttrPool(), &rAttr);
    SfxPoolItem& rNew(const_cast<SfxPoolItem&>(*aHolder.getItem()));

    SwTextAttr* pNew = nullptr;
    switch( aHolder.Which() )
    {
    case RES_TXTATR_CHARFMT:
        {
            SwFormatCharFormat &rFormatCharFormat = static_cast<SwFormatCharFormat&>(rNew);
            if( !rFormatCharFormat.GetCharFormat() )
            {
                rFormatCharFormat.SetCharFormat( rDoc.GetDfltCharFormat() );
            }

            pNew = new SwTextCharFormat( aHolder, nStt, nEnd );
        }
        break;
    case RES_TXTATR_INETFMT:
        pNew = new SwTextINetFormat( aHolder, nStt, nEnd );
        break;

    case RES_TXTATR_FIELD:
        pNew = new SwTextField( aHolder, nStt,
                    rDoc.IsClipBoard() );
        break;

    case RES_TXTATR_ANNOTATION:
        {
            pNew = new SwTextAnnotationField( aHolder, nStt, rDoc.IsClipBoard() );
            if (bIsCopy == CopyOrNewType::Copy)
            {
                // On copy of the annotation field do not keep the annotated text range by removing
                // the relation to its annotation mark (relation established via annotation field's name).
                // If the annotation mark is also copied, the relation and thus the annotated text range will be reestablished,
                // when the annotation mark is created and inserted into the document.
                auto& pField = const_cast<SwPostItField&>(dynamic_cast<const SwPostItField&>(*(pNew->GetFormatField().GetField())));

                if (!rDoc.IsInWriterfilterImport())
                {
                    // We set the name here to make the object referenceable.
                    pField.SetName(sw::mark::MarkBase::GenerateNewName(u"__Annotation__"));
                }
                else // Keep the previous behaviour while loading the file.
                    pField.SetName(SwMarkName());

                pField.SetPostItId();
            }
        }
        break;

    case RES_TXTATR_INPUTFIELD:
        pNew = new SwTextInputField( aHolder, nStt, nEnd,
                    rDoc.IsClipBoard() );
        break;

    case RES_TXTATR_FLYCNT:
        {
            // finally, copy the frame format (with content)
            pNew = new SwTextFlyCnt( aHolder, nStt );
            if ( static_cast<const SwFormatFlyCnt &>(rAttr).GetTextFlyCnt() )
            {
                // if it has an existing attr then the format must be copied
                static_cast<SwTextFlyCnt *>(pNew)->CopyFlyFormat( rDoc );
            }
        }
        break;
    case RES_TXTATR_FTN:
        pNew = new SwTextFootnote( aHolder, nStt );
        // copy note's SeqNo
        if( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote() )
            static_cast<SwTextFootnote*>(pNew)->SetSeqNo( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote()->GetSeqRefNo() );
        break;
    case RES_TXTATR_REFMARK:
        pNew = nStt == nEnd
                ? new SwTextRefMark( aHolder, nStt )
                : new SwTextRefMark( aHolder, nStt, &nEnd );
        break;
    case RES_TXTATR_TOXMARK:
    {
        SwTOXMark& rMark = static_cast<SwTOXMark&>(rNew);

        // tdf#98868 if the SwTOXType is from a different document that the
        // target, re-register the TOXMark against a matching SwTOXType from
        // the target document instead
        const SwTOXType* pTOXType = rMark.GetTOXType();
        if (pTOXType && &pTOXType->GetDoc() != &rDoc)
        {
            SwTOXType* pToxType = SwHistorySetTOXMark::GetSwTOXType(rDoc, pTOXType->GetType(),
                                                                    pTOXType->GetTypeName());
            rMark.RegisterToTOXType(*pToxType);
        }

        pNew = new SwTextTOXMark(aHolder, nStt, &nEnd);
        break;
    }
    case RES_TXTATR_CJK_RUBY:
        pNew = new SwTextRuby( aHolder, nStt, nEnd );
        break;
    case RES_TXTATR_META:
    case RES_TXTATR_METAFIELD:
        pNew = SwTextMeta::CreateTextMeta( rDoc.GetMetaFieldManager(), pTextNode,
                aHolder,
                nStt, nEnd, bIsCopy == CopyOrNewType::Copy );
        break;
    case RES_TXTATR_LINEBREAK:
        pNew = new SwTextLineBreak(aHolder, nStt);
        break;
    case RES_TXTATR_CONTENTCONTROL:
        pNew = SwTextContentControl::CreateTextContentControl(
            rDoc, pTextNode,
            aHolder,
            nStt, nEnd,
            bIsCopy == CopyOrNewType::Copy);
        break;
    default:
        assert(RES_TXTATR_AUTOFMT == rNew.Which());
        pNew = new SwTextAttrEnd( aHolder, nStt, nEnd );
        break;
    }

    return pNew;
}

SwTextAttr* MakeTextAttr( SwDoc & rDoc, const SfxItemSet& rSet,
                        sal_Int32 nStt, sal_Int32 nEnd )
{
    IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
    const std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle( rSet, IStyleAccess::AUTO_STYLE_CHAR );
    SwFormatAutoFormat aNewAutoFormat;
    aNewAutoFormat.SetStyleHandle( pAutoStyle );
    SwTextAttr* pNew = MakeTextAttr( rDoc, aNewAutoFormat, nStt, nEnd );
    return pNew;
}

// delete the text attribute and unregister its item at the pool
void SwTextNode::DestroyAttr( SwTextAttr* pAttr )
{
    if( !pAttr )
        return;

    // some things need to be done before deleting the formatting attribute
    SwDoc& rDoc = GetDoc();
    switch( pAttr->Which() )
    {
    case RES_TXTATR_FLYCNT:
        {
            SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat();
            if( pFormat )      // set to 0 by Undo?
                rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
        }
        break;

    case RES_CHRATR_HIDDEN:
        SetCalcHiddenCharFlags();
        break;

    case RES_TXTATR_FTN:
        static_cast<SwTextFootnote*>(pAttr)->SetStartNode( nullptr );
        static_cast<SwFormatFootnote&>(pAttr->GetAttr()).InvalidateFootnote();
        break;

    case RES_TXTATR_FIELD:
    case RES_TXTATR_ANNOTATION:
    case RES_TXTATR_INPUTFIELD:
        if( !rDoc.IsInDtor() )
        {
            SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pAttr));
            SwFieldType* pFieldType = pAttr->GetFormatField().GetField()->GetTyp();

            if (SwFieldIds::Dde != pFieldType->Which()
                && !pTextField->GetpTextNode())
            {
                break; // was not yet inserted
            }

            //JP 06-08-95: DDE-fields are an exception
            assert(SwFieldIds::Dde == pFieldType->Which() ||
                   this == pTextField->GetpTextNode());

            // certain fields must update the SwDoc's calculation flags

            // Certain fields (like HiddenParaField) must trigger recalculation of visible flag
            if (GetDoc().FieldCanHideParaWeight(pFieldType->Which()))
                SetCalcHiddenParaField();

            switch( pFieldType->Which() )
            {
            case SwFieldIds::HiddenPara:
            case SwFieldIds::DbSetNumber:
            case SwFieldIds::GetExp:
            case SwFieldIds::Database:
            case SwFieldIds::SetExp:
            case SwFieldIds::HiddenText:
            case SwFieldIds::DbNumSet:
            case SwFieldIds::DbNextSet:
                if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() && GetNodes().IsDocNodes() )
                    rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField);
                break;
            case SwFieldIds::Dde:
                if (GetNodes().IsDocNodes() && pTextField->GetpTextNode())
                    static_cast<SwDDEFieldType*>(pFieldType)->DecRefCnt();
                break;
            case SwFieldIds::Postit:
                {
                    const_cast<SwFormatField&>(pAttr->GetFormatField()).Broadcast(
                        SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED));
                    break;
                }
            default: break;
            }
        }
        static_cast<SwFormatField&>(pAttr->GetAttr()).InvalidateField();
        break;

    case RES_TXTATR_TOXMARK:
        static_cast<SwTOXMark&>(pAttr->GetAttr()).InvalidateTOXMark();
        break;

    case RES_TXTATR_REFMARK:
        static_cast<SwFormatRefMark&>(pAttr->GetAttr()).InvalidateRefMark();
        break;

    case RES_TXTATR_META:
    case RES_TXTATR_METAFIELD:
    {
        auto pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
        SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(pTextMeta->GetAttr()) );
        if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
        {
            if (SwDocShell* pDocSh = rDoc.GetDocShell())
            {
                static constexpr OUStringLiteral metaNS(u"urn:bails");
                const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
                rtl::Reference<SwXTextDocument> xModel = pDocSh->GetBaseModel();
                SwRDFHelper::clearStatements(xModel, metaNS, xSubject);
            }
        }

        static_txtattr_cast<SwTextMeta*>(pAttr)->ChgTextNode(nullptr);
    }
        break;
    case RES_TXTATR_CONTENTCONTROL:
    {
        static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr);
        break;
    }

    default:
        break;
    }

    SwTextAttr::Destroy( pAttr );
}

SwTextAttr* SwTextNode::InsertItem(
    SfxPoolItem& rAttr,
    const sal_Int32 nStart,
    const sal_Int32 nEnd,
    const SetAttrMode nMode )
{
    // character attributes will be inserted as automatic styles:
    assert( !isCHRATR(rAttr.Which()) && "AUTOSTYLES - "
        "SwTextNode::InsertItem should not be called with character attributes");

    SwTextAttr *const pNew =
        MakeTextAttr(
            GetDoc(),
            rAttr,
            nStart,
            nEnd,
            (nMode & SetAttrMode::IS_COPY) ? CopyOrNewType::Copy : CopyOrNewType::New,
            this );

    if ( pNew )
    {
        const bool bSuccess( InsertHint( pNew, nMode ) );
        // N.B.: also check that the hint is actually in the hints array,
        // because hints of certain types may be merged after successful
        // insertion, and thus destroyed!
        if (!bSuccess || !m_pSwpHints->Contains( pNew ))
        {
            return nullptr;
        }
    }

    return pNew;
}

// take ownership of pAttr; if insertion fails, delete pAttr
bool SwTextNode::InsertHint( SwTextAttr * const pAttr, const SetAttrMode nMode )
{
    bool bHiddenPara = false;

    assert(pAttr && pAttr->GetStart() <= Len());
    assert(!pAttr->GetEnd() || (*pAttr->GetEnd() <= Len()));

    // translate from SetAttrMode to InsertMode (for hints with CH_TXTATR)
    const SwInsertFlags nInsertFlags =
        (nMode & SetAttrMode::NOHINTEXPAND)
        ? SwInsertFlags::NOHINTEXPAND
        : (nMode & SetAttrMode::FORCEHINTEXPAND)
            ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND)
            : SwInsertFlags::EMPTYEXPAND;

    // need this after TryInsertHint, when pAttr may be deleted
    const sal_Int32 nStart( pAttr->GetStart() );
    const bool bDummyChar( pAttr->HasDummyChar() );
    if (bDummyChar)
    {
        SetAttrMode nInsMode = nMode;
        switch( pAttr->Which() )
        {
        case RES_TXTATR_FLYCNT:
            {
                SwTextFlyCnt *pFly = static_cast<SwTextFlyCnt *>(pAttr);
                SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat();
                if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
                {
                    // Need to insert char first, because SetAnchor() reads
                    // GetStart().
                    //JP 11.05.98: if the anchor is already set correctly,
                    // fix it after inserting the char, so that clients don't
                    // have to worry about it.
                    const SwFormatAnchor* pAnchor = pFormat->GetItemIfSet( RES_ANCHOR, false );

                    SwContentIndex aIdx( this, pAttr->GetStart() );
                    const OUString c(GetCharOfTextAttr(*pAttr));
                    OUString const ins( InsertText(c, aIdx, nInsertFlags) );
                    if (ins.isEmpty())
                    {
                        // do not record deletion of Format!
                        ::sw::UndoGuard const ug(
                                pFormat->GetDoc().GetIDocumentUndoRedo());
                        DestroyAttr(pAttr);
                        return false; // text node full :(
                    }
                    nInsMode |= SetAttrMode::NOTXTATRCHR;

                    if (pAnchor &&
                        (RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) &&
                        pAnchor->GetAnchorNode() &&
                        *pAnchor->GetAnchorNode() == *this &&
                        pAnchor->GetAnchorContentOffset() == aIdx.GetIndex() )
                    {
                        const_cast<SwPosition*>(pAnchor->GetContentAnchor())->AdjustContent(-1);
                    }
                }
                pFly->SetAnchor( this );

                // format pointer could have changed in SetAnchor,
                // when copying to other docs!
                pFormat = pAttr->GetFlyCnt().GetFrameFormat();
                SwDoc& rDoc = pFormat->GetDoc();

                // OD 26.06.2003 - allow drawing objects in header/footer.
                // But don't allow control objects in header/footer
                if( RES_DRAWFRMFMT == pFormat->Which() &&
                    rDoc.IsInHeaderFooter( *pFormat->GetAnchor().GetAnchorNode() ) )
                {
                    bool bCheckControlLayer = false;
                    pFormat->CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer));
                    if( bCheckControlLayer )
                    {
                        // This should not be allowed, prevent it here.
                        // The dtor of the SwTextAttr does not delete the
                        // char, so delete it explicitly here.
                        if( SetAttrMode::NOTXTATRCHR & nInsMode )
                        {
                            // delete the char from the string
                            assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
                                || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
                            m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u"");
                            // Update SwContentIndexes
                            SwContentIndex aTmpIdx( this, pAttr->GetStart() );
                            Update(aTmpIdx, 1, UpdateMode::Negative);
                        }
                        // do not record deletion of Format!
                        ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
                        DestroyAttr( pAttr );
                        return false;
                    }
                }
                break;
            }

        case RES_TXTATR_FTN :
            {
                // Footnotes: create text node and put it into Inserts-section
                SwDoc& rDoc = GetDoc();
                SwNodes &rNodes = rDoc.GetNodes();

                // check that footnote is inserted into body or redline section
                bool bSplitFly = false;
                if (StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex()
                    && StartOfSectionIndex() >= rNodes.GetEndOfInserts().GetIndex())
                {
                    // This is a frame, header or footer. Check if it's a split frame.
                    SwFrameFormat* pFlyFormat = StartOfSectionNode()->GetFlyFormat();
                    bSplitFly = pFlyFormat && pFlyFormat->GetFlySplit().GetValue();
                }

                if (StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex() && !bSplitFly)
                {
                    // This should not be allowed, prevent it here.
                    // The dtor of the SwTextAttr does not delete the
                    // char, so delete it explicitly here.
                    if( SetAttrMode::NOTXTATRCHR & nInsMode )
                    {
                        // delete the char from the string
                        assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
                            || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
                        m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u"");
                        // Update SwContentIndexes
                        SwContentIndex aTmpIdx( this, pAttr->GetStart() );
                        Update(aTmpIdx, 1, UpdateMode::Negative);
                    }
                    DestroyAttr( pAttr );
                    return false;
                }

                // is a new footnote being inserted?
                bool bNewFootnote = nullptr == static_cast<SwTextFootnote*>(pAttr)->GetStartNode();
                if( bNewFootnote )
                {
                    static_cast<SwTextFootnote*>(pAttr)->MakeNewTextSection( GetNodes() );
                    SwRegHistory* pHist = GetpSwpHints()
                        ? GetpSwpHints()->GetHistory() : nullptr;
                    if( pHist )
                        pHist->ChangeNodeIndex( GetIndex() );
                }
                else if ( !GetpSwpHints() || !GetpSwpHints()->IsInSplitNode() )
                {
                    // existing footnote: delete all layout frames of its
                    // footnote section
                    SwNodeOffset nSttIdx =
                        static_cast<SwTextFootnote*>(pAttr)->GetStartNode()->GetIndex();
                    SwNodeOffset nEndIdx = rNodes[ nSttIdx++ ]->EndOfSectionIndex();
                    for( ; nSttIdx < nEndIdx; ++nSttIdx )
                    {
                        SwContentNode* pCNd = rNodes[ nSttIdx ]->GetContentNode();
                        if( nullptr != pCNd )
                            pCNd->DelFrames(nullptr);
                        else if (SwTableNode *const pTable = rNodes[nSttIdx]->GetTableNode())
                        {
                            pTable->DelFrames();
                        }
                    }
                }

                if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
                {
                    // must insert first, to prevent identical indexes
                    // that could later prevent insertion into SwDoc's
                    // footnote array
                    SwContentIndex aNdIdx( this, pAttr->GetStart() );
                    const OUString c(GetCharOfTextAttr(*pAttr));
                    OUString const ins( InsertText(c, aNdIdx, nInsertFlags) );
                    if (ins.isEmpty())
                    {
                        DestroyAttr(pAttr);
                        return false; // text node full :(
                    }
                    nInsMode |= SetAttrMode::NOTXTATRCHR;
                }

                // insert into SwDoc's footnote index array
                SwTextFootnote* pTextFootnote = nullptr;
                if( !bNewFootnote )
                {
                    // moving an existing footnote (e.g. SplitNode)
                    for( size_t n = 0; n < rDoc.GetFootnoteIdxs().size(); ++n )
                        if( pAttr == rDoc.GetFootnoteIdxs()[n] )
                        {
                            // assign new index by removing and re-inserting
                            pTextFootnote = rDoc.GetFootnoteIdxs()[n];
                            rDoc.GetFootnoteIdxs().erase( rDoc.GetFootnoteIdxs().begin() + n );
                            break;
                        }
                        // if the Undo set the StartNode, the Index isn't
                        // in the doc's array yet!
                }
                if( !pTextFootnote )
                    pTextFootnote = static_cast<SwTextFootnote*>(pAttr);

                // to update the numbers and for sorting, the Node must be set
                static_cast<SwTextFootnote*>(pAttr)->ChgTextNode( this );

                // do not insert footnote in redline section into footnote array
                if (StartOfSectionIndex() > rNodes.GetEndOfRedlines().GetIndex() || bSplitFly)
                {
                    const bool bSuccess = rDoc.GetFootnoteIdxs().insert(pTextFootnote).second;
                    OSL_ENSURE( bSuccess, "FootnoteIdx not inserted." );
                }
                rDoc.GetFootnoteIdxs().UpdateFootnote( *this );
                static_cast<SwTextFootnote*>(pAttr)->SetSeqRefNo();
            }
            break;

        case RES_TXTATR_FIELD:
                {
                    // trigger notification for relevant fields, like HiddenParaFields
                    if (GetDoc().FieldCanHideParaWeight(
                            pAttr->GetFormatField().GetField()->GetTyp()->Which()))
                    {
                        bHiddenPara = true;
                    }
                }
                break;
        case RES_TXTATR_LINEBREAK :
                {
                    static_cast<SwTextLineBreak*>(pAttr)->SetTextNode(this);
                }
                break;

        }
        // CH_TXTATR_* are inserted for SwTextHints without EndIndex
        // If the caller is SwTextNode::Copy, the char has already been copied,
        // and SETATTR_NOTXTATRCHR prevents inserting it again here.
        if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
        {
            SwContentIndex aIdx( this, pAttr->GetStart() );
            OUString const ins( InsertText(OUString(GetCharOfTextAttr(*pAttr)),
                        aIdx, nInsertFlags) );
            if (ins.isEmpty())
            {
                DestroyAttr(pAttr);
                return false; // text node full :(
            }

            // adjust end of hint to account for inserted CH_TXTATR
            const sal_Int32* pEnd(pAttr->GetEnd());
            if (pEnd)
            {
                pAttr->SetEnd(*pEnd + 1);
            }

            if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
            {
                // Content controls have a dummy character at their end as well.
                SwContentIndex aEndIdx(this, *pAttr->GetEnd());
                OUString aEnd
                    = InsertText(OUString(GetCharOfTextAttr(*pAttr)), aEndIdx, nInsertFlags);
                if (aEnd.isEmpty())
                {
                    DestroyAttr(pAttr);
                    return false;
                }

                pEnd = pAttr->GetEnd();
                if (pEnd)
                {
                    pAttr->SetEnd(*pEnd + 1);
                }
            }
        }
    }

    // handle attributes which provide content
    sal_Int32 nEnd = nStart;
    bool bInputFieldStartCharInserted = false;
    bool bInputFieldEndCharInserted = false;
    const bool bHasContent( pAttr->HasContent() );
    if ( bHasContent )
    {
        switch( pAttr->Which() )
        {
        case RES_TXTATR_INPUTFIELD:
            {
                SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pAttr);
                if ( pTextInputField )
                {
                    if( !(SetAttrMode::NOTXTATRCHR & nMode) )
                    {
                        SwContentIndex aIdx( this, pAttr->GetStart() );
                        const OUString aContent = OUStringChar(CH_TXT_ATR_INPUTFIELDSTART)
                            + pTextInputField->GetFieldContent() + OUStringChar(CH_TXT_ATR_INPUTFIELDEND);
                        InsertText( aContent, aIdx, nInsertFlags );

                        const sal_Int32* const pEnd(pAttr->GetEnd());
                        assert(pEnd != nullptr);
                        pAttr->SetEnd(*pEnd + aContent.getLength());
                        nEnd = *pAttr->GetEnd();
                    }
                    else
                    {
                        // assure that CH_TXT_ATR_INPUTFIELDSTART and CH_TXT_ATR_INPUTFIELDEND are inserted.
                        if ( m_Text[ pAttr->GetStart() ] != CH_TXT_ATR_INPUTFIELDSTART )
                        {
                            SwContentIndex aIdx( this, pAttr->GetStart() );
                            InsertText( OUString(CH_TXT_ATR_INPUTFIELDSTART), aIdx, nInsertFlags );
                            bInputFieldStartCharInserted = true;
                            const sal_Int32* const pEnd(pAttr->GetEnd());
                            assert(pEnd != nullptr);
                            pAttr->SetEnd(*pEnd + 1);
                            nEnd = *pAttr->GetEnd();
                        }

                        const sal_Int32* const pEnd(pAttr->GetEnd());
                        assert(pEnd != nullptr);
                        if (m_Text[ *pEnd - 1 ] != CH_TXT_ATR_INPUTFIELDEND)
                        {
                            SwContentIndex aIdx( this, *pEnd );
                            InsertText( OUString(CH_TXT_ATR_INPUTFIELDEND), aIdx, nInsertFlags );
                            bInputFieldEndCharInserted = true;
                            pAttr->SetEnd(*pEnd + 1);
                            nEnd = *pAttr->GetEnd();
                        }
                    }
                }
            }
            break;
        default:
            break;
        }
    }

    GetOrCreateSwpHints();

    // handle overlap with an existing InputField
    bool bInsertHint = true;
    {
        const SwTextInputField* pTextInputField = GetOverlappingInputField( *pAttr );
        if ( pTextInputField != nullptr )
        {
            if ( pAttr->End() == nullptr )
            {
                bInsertHint = false;
                DestroyAttr(pAttr);
            }
            else
            {
                if ( pAttr->GetStart() > pTextInputField->GetStart() )
                {
                    pAttr->SetStart( pTextInputField->GetStart() );
                }
                if ( *(pAttr->End()) < *(pTextInputField->End()) )
                {
                    pAttr->SetEnd(*(pTextInputField->End()));
                }
            }
        }
    }

    if (bInsertHint)
    {
        // Handle the invariant that a plain text content control has the same character formatting
        // for all of its content.
        auto* pTextContentControl = static_txtattr_cast<SwTextContentControl*>(
            GetTextAttrAt(pAttr->GetStart(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent));
        // If the caller is SwTextNode::CopyText, we just copy an existing attribute, no need to
        // correct it.
        if (pTextContentControl && !(nMode & SetAttrMode::NOTXTATRCHR))
        {
            auto& rFormatContentControl
                = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
            std::shared_ptr<SwContentControl> pContentControl
                = rFormatContentControl.GetContentControl();
            if (pAttr->End() != nullptr && pContentControl->GetPlainText())
            {
                if (pAttr->GetStart() > pTextContentControl->GetStart())
                {
                    pAttr->SetStart(pTextContentControl->GetStart());
                }
                if (*pAttr->End() < *pTextContentControl->End())
                {
                    pAttr->SetEnd(*pTextContentControl->End());
                }
            }
        }
    }

    const bool bRet = bInsertHint
                      && m_pSwpHints->TryInsertHint( pAttr, *this, nMode );

    if ( !bRet )
    {
        if ( bDummyChar
             && !(SetAttrMode::NOTXTATRCHR & nMode) )
        {
            // undo insertion of dummy character
            // N.B. cannot insert the dummy character after inserting the hint,
            // because if the hint has no extent it will be moved in InsertText,
            // resulting in infinite recursion
            assert((CH_TXTATR_BREAKWORD == m_Text[nStart] ||
                    CH_TXTATR_INWORD    == m_Text[nStart] ));
            SwContentIndex aIdx( this, nStart );
            EraseText( aIdx, 1 );
        }

        if ( bHasContent )
        {
            if ( !(SetAttrMode::NOTXTATRCHR & nMode)
                 && (nEnd - nStart) > 0 )
            {
                SwContentIndex aIdx( this, nStart );
                EraseText( aIdx, (nEnd - nStart) );
            }
            else
            {
                if ( bInputFieldEndCharInserted
                     && (nEnd - nStart) > 0 )
                {
                    SwContentIndex aIdx( this, nEnd - 1 );
                    EraseText( aIdx, 1 );
                }

                if ( bInputFieldStartCharInserted )
                {
                    SwContentIndex aIdx( this, nStart );
                    EraseText( aIdx, 1 );
                }
            }
        }
    }

    if ( bHiddenPara )
    {
        SetCalcHiddenParaField();
    }

    return bRet;
}

void SwTextNode::DeleteAttribute( SwTextAttr * const pAttr )
{
    if ( !HasHints() )
    {
        OSL_FAIL("DeleteAttribute called, but text node without hints?");
        return;
    }

    if ( pAttr->HasDummyChar() )
    {
        // copy index!
        const SwContentIndex aIdx( this, pAttr->GetStart() );
        // erase the CH_TXTATR, which will also delete pAttr
        EraseText( aIdx, 1 );
    }
    else if ( pAttr->HasContent() )
    {
        const SwContentIndex aIdx( this, pAttr->GetStart() );
        assert(pAttr->End() != nullptr);
        EraseText( aIdx, *pAttr->End() - pAttr->GetStart() );
    }
    else
    {
        // create MsgHint before start/end become invalid
        SwUpdateAttr aHint(
            pAttr->GetStart(),
            *pAttr->GetEnd(),
            pAttr->Which());

        m_pSwpHints->Delete( pAttr );
        SwTextAttr::Destroy( pAttr );
        CallSwClientNotify(sw::UpdateAttrHint(nullptr, &aHint));

        TryDeleteSwpHints();
    }
}

//FIXME: this does NOT respect SORT NUMBER (for CHARFMT)!
void SwTextNode::DeleteAttributes(
    const sal_uInt16 nWhich,
    const sal_Int32 nStart,
    const sal_Int32 nEnd )
{
    if ( !HasHints() )
        return;

    for ( size_t nPos = 0; m_pSwpHints && nPos < m_pSwpHints->Count(); ++nPos )
    {
        SwTextAttr * const pTextHt = m_pSwpHints->Get( nPos );
        const sal_Int32 nHintStart = pTextHt->GetStart();
        if (nStart < nHintStart)
        {
            break; // sorted by start
        }
        else if ( (nStart == nHintStart) && (nWhich == pTextHt->Which()) )
        {
            if ( nWhich == RES_CHRATR_HIDDEN  )
            {
                assert(!"hey, that's a CHRATR! how did that get in?");
                SetCalcHiddenCharFlags();
            }
            else if ( nWhich == RES_TXTATR_CHARFMT )
            {
                // Check if character format contains hidden attribute:
                const SwCharFormat* pFormat = pTextHt->GetCharFormat().GetCharFormat();
                if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN ) )
                    SetCalcHiddenCharFlags();
            }
            // #i75430# Recalc hidden flags if necessary
            else if ( nWhich == RES_TXTATR_AUTOFMT )
            {
                // Check if auto style contains hidden attribute:
                const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pTextHt, RES_CHRATR_HIDDEN );
                if ( pHiddenItem )
                    SetCalcHiddenCharFlags();
                // for auto styles DeleteAttributes is only called from Undo
                // so it shouldn't need to care about ignore start/end flags
            }

            sal_Int32 const * const pEndIdx = pTextHt->GetEnd();

            if ( pTextHt->HasDummyChar() )
            {
                // copy index!
                const SwContentIndex aIdx( this, nStart );
                // erase the CH_TXTATR, which will also delete pTextHt
                EraseText( aIdx, 1 );
            }
            else if ( pTextHt->HasContent() )
            {
                const SwContentIndex aIdx( this, nStart );
                OSL_ENSURE( pTextHt->End() != nullptr, "<SwTextNode::DeleteAttributes(..)> - missing End() at <SwTextAttr> instance which has content" );
                EraseText( aIdx, *pTextHt->End() - nStart );
            }
            else if( *pEndIdx == nEnd )
            {
                // Create MsgHint before Start and End are gone.
                // For HiddenParaFields it's not necessary to call
                // SetCalcHiddenParaField because the dtor does that.
                SwUpdateAttr aHint(
                    nStart,
                    *pEndIdx,
                    nWhich);

                m_pSwpHints->DeleteAtPos( nPos );
                SwTextAttr::Destroy( pTextHt );
                CallSwClientNotify(sw::UpdateAttrHint(nullptr, &aHint));
            }
        }
    }
    TryDeleteSwpHints();
}

void SwTextNode::DelSoftHyph( const sal_Int32 nStt, const sal_Int32 nEnd )
{
    sal_Int32 nFndPos = nStt;
    sal_Int32 nEndPos = nEnd;
    for (;;)
    {
        nFndPos = m_Text.indexOf(CHAR_SOFTHYPHEN, nFndPos);
        if (nFndPos<0 || nFndPos>=nEndPos )
        {
            break;
        }
        const SwContentIndex aIdx( this, nFndPos );
        EraseText( aIdx, 1 );
        --nEndPos;
    }
}

bool SwTextNode::IsIgnoredCharFormatForNumbering(const sal_uInt16 nWhich, bool bIsCharStyle)
{
    // LO can save the char background as either shading or highlight, so check which mode is currently chosen.
    // Shading does not affect the numbering. Highlighting does (but isn't allowed in a char style).
    if (nWhich == RES_CHRATR_BACKGROUND)
        return bIsCharStyle || !officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::get();

    return (nWhich == RES_CHRATR_UNDERLINE
            || nWhich == RES_CHRATR_ESCAPEMENT);
}

// Set these attributes on SwTextNode.  If they apply to the entire paragraph
// text, set them in the SwTextNode's item set (SwContentNode::SetAttr).
bool SwTextNode::SetAttr(
    const SfxItemSet& rSet,
    const sal_Int32 nStt,
    const sal_Int32 nEnd,
    const SetAttrMode nMode,
    SwTextAttr **ppNewTextAttr )
{
    if( !rSet.Count() )
        return false;

    // split sets (for selection in nodes)
    const SfxItemSet* pSet = &rSet;
    SfxItemSetFixed<RES_TXTATR_BEGIN, RES_TXTATR_END-1> aTextSet( *rSet.GetPool() );

    // entire paragraph
    if ( !nStt && (nEnd == m_Text.getLength()) &&
         !(nMode & SetAttrMode::NOFORMATATTR ) )
    {
        // if the node already has CharFormat hints, the new attributes must
        // be set as hints too to override those.
        bool bHasCharFormats = false;
        if ( HasHints() )
        {
            for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
            {
                if ( m_pSwpHints->Get( n )->IsCharFormatAttr() )
                {
                    bHasCharFormats = true;
                    break;
                }
            }
        }

        if( !bHasCharFormats )
        {
            aTextSet.Put( rSet );
            // If there are any character attributes in rSet,
            // we want to set them at the paragraph:
            if( aTextSet.Count() != rSet.Count() )
            {
                const bool bRet = SetAttr( rSet );
                if( !aTextSet.Count() )
                    return bRet;
            }

            // check for auto style:
            if ( const SwFormatAutoFormat* pItem = aTextSet.GetItemIfSet( RES_TXTATR_AUTOFMT, false ) )
            {
                const bool bRet = SetAttr( *pItem->GetStyleHandle() );
                if( 1 == aTextSet.Count() )
                    return bRet;
            }

            // Continue with the text attributes:
            pSet = &aTextSet;
        }
    }

    GetOrCreateSwpHints();

    SfxItemSet aCharSet( *rSet.GetPool(), aCharAutoFormatSetRange );

    size_t nCount = 0;
    SfxItemIter aIter( *pSet );
    const SfxPoolItem* pItem = aIter.GetCurItem();

    do
    {
        if (!IsInvalidItem(pItem))
        {
            const sal_uInt16 nWhich = pItem->Which();
            OSL_ENSURE( isCHRATR(nWhich) || isTXTATR(nWhich),
                    "SwTextNode::SetAttr(): unknown attribute" );
            if ( isCHRATR(nWhich) || isTXTATR(nWhich) )
            {
                if ((RES_TXTATR_CHARFMT == nWhich) &&
                    (GetDoc().GetDfltCharFormat() ==
                     static_cast<const SwFormatCharFormat*>(pItem)->GetCharFormat()))
                {
                    RstTextAttr( nStt, nEnd - nStt, RES_TXTATR_CHARFMT );
                    DontExpandFormat( nStt );
                }
                else
                {
                    if (isCHRATR(nWhich) ||
                        (RES_TXTATR_UNKNOWN_CONTAINER == nWhich))
                    {
                        aCharSet.Put( *pItem );
                    }
                    else
                    {

                        SwTextAttr *const pNew = MakeTextAttr( GetDoc(),
                                const_cast<SfxPoolItem&>(*pItem), nStt, nEnd );
                        if ( pNew )
                        {
                            // store the first one we create into the pp
                            if (ppNewTextAttr && !*ppNewTextAttr)
                                *ppNewTextAttr = pNew;
                            if ( nEnd != nStt && !pNew->GetEnd() )
                            {
                                OSL_FAIL("Attribute without end, but area marked");
                                DestroyAttr( pNew ); // do not insert
                            }
                            else if ( InsertHint( pNew, nMode ) )
                            {
                                ++nCount;
                            }
                        }
                    }
                }
            }
        }
        pItem = aIter.NextItem();
    } while(pItem);

    if ( aCharSet.Count() )
    {
        SwTextAttr* pTmpNew = MakeTextAttr( GetDoc(), aCharSet, nStt, nEnd );
        if ( InsertHint( pTmpNew, nMode ) )
        {
            ++nCount;
        }
    }

    TryDeleteSwpHints();

    return nCount != 0;
}

static void lcl_MergeAttr( SfxItemSet& rSet, const SfxPoolItem& rAttr )
{
    if ( RES_TXTATR_AUTOFMT == rAttr.Which() )
    {
        const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr );
        if ( !pCFSet )
            return;
        SfxWhichIter aIter( *pCFSet );
        sal_uInt16 nWhich = aIter.FirstWhich();
        while( nWhich )
        {
            const SfxPoolItem* pItem = nullptr;
            if( ( nWhich < RES_CHRATR_END ||
                  RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) &&
                ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
                rSet.Put( *pItem );
            nWhich = aIter.NextWhich();
        }
    }
    else
        rSet.Put( rAttr );
}

static void lcl_MergeAttr_ExpandChrFormat( SfxItemSet& rSet, const SfxPoolItem& rAttr )
{
    if( RES_TXTATR_CHARFMT == rAttr.Which() ||
        RES_TXTATR_INETFMT == rAttr.Which() ||
        RES_TXTATR_AUTOFMT == rAttr.Which() )
    {
        const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr );

        if ( pCFSet )
        {
            SfxWhichIter aIter( *pCFSet );
            sal_uInt16 nWhich = aIter.FirstWhich();
            while( nWhich )
            {
                const SfxPoolItem* pItem = nullptr;
                if( ( nWhich < RES_CHRATR_END ||
                      ( RES_TXTATR_AUTOFMT == rAttr.Which() && RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) ) &&
                    ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
                    rSet.Put( *pItem );
                nWhich = aIter.NextWhich();
            }
        }
    }

/* If multiple attributes overlap, the last one wins!
   Probably this can only happen between a RES_TXTATR_INETFMT and one of the
   other hints, because BuildPortions ensures that CHARFMT/AUTOFMT don't
   overlap.  But there may be multiple CHARFMT/AUTOFMT with exactly the same
   start/end, sorted by BuildPortions, in which case the same logic applies.

            1234567890123456789
              |------------|        Font1
                 |------|           Font2
                    ^  ^
                    |--|        query range: -> Font2
*/
    // merge into set
    rSet.Put( rAttr );
}

namespace {

struct SwPoolItemEndPair
{
public:
    const SfxPoolItem* mpItem;
    sal_Int32 mnEndPos;

    SwPoolItemEndPair() : mpItem( nullptr ), mnEndPos( 0 ) {};
};

}

static void lcl_MergeListLevelIndentAsLRSpaceItem( const SwTextNode& rTextNode,
                                            SfxItemSet& rSet )
{
    ::sw::ListLevelIndents const indents(rTextNode.AreListLevelIndentsApplicable());
    if (indents == ::sw::ListLevelIndents::No)
        return;

    const SwNumRule* pRule = rTextNode.GetNumRule();
    if ( pRule && rTextNode.GetActualListLevel() >= 0 )
    {
        const SwNumFormat& rFormat = pRule->Get(o3tl::narrowing<sal_uInt16>(rTextNode.GetActualListLevel()));
        if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
        {
            if (indents & ::sw::ListLevelIndents::FirstLine)
            {
                SvxFirstLineIndentItem const firstLine(
                    SvxIndentValue{ static_cast<double>(rFormat.GetFirstLineIndent()),
                                    rFormat.GetFirstLineIndentUnit() },
                    RES_MARGIN_FIRSTLINE);
                rSet.Put(firstLine);
            }
            if (indents & ::sw::ListLevelIndents::LeftMargin)
            {
                SvxTextLeftMarginItem const leftMargin(SvxIndentValue::twips(rFormat.GetIndentAt()),
                                                       RES_MARGIN_TEXTLEFT);
                rSet.Put(leftMargin);
            }
        }
    }
}

// request the attributes of the TextNode at the range
bool SwTextNode::GetParaAttr(SfxItemSet& rSet, sal_Int32 nStt, sal_Int32 nEnd,
                         const bool bOnlyTextAttr, const bool bGetFromChrFormat,
                         const bool bMergeIndentValuesOfNumRule,
                         SwRootFrame const*const pLayout) const
{
    assert(!rSet.Count()); // handled inconsistently, typically an error?

    if (pLayout && pLayout->HasMergedParas())
    {
        if (GetRedlineMergeFlag() == SwNode::Merge::Hidden)
        {
            return false; // ignore deleted node
        }
    }

    // get the node's automatic attributes
    SfxItemSet aFormatSet( *rSet.GetPool(), rSet.GetRanges() );
    if (!bOnlyTextAttr)
    {
        SwTextNode const& rParaPropsNode(
                sw::GetAttrMerged(aFormatSet, *this, pLayout));
        if (bMergeIndentValuesOfNumRule)
        {
            lcl_MergeListLevelIndentAsLRSpaceItem(rParaPropsNode, aFormatSet);
        }
    }

    if( HasHints() )
    {
        // First, check which text attributes are valid in the range.
        // cases:
        // Ambiguous, if
        //  * the attribute is wholly contained in the range
        //  * the attribute end is in the range
        //  * the attribute start is in the range
        // Unambiguous (merge into set), if
        //  * the attribute wholly contains the range
        // Ignored, if
        //  * the attribute is wholly outside the range

        void (*fnMergeAttr)( SfxItemSet&, const SfxPoolItem& )
            = bGetFromChrFormat ? &lcl_MergeAttr_ExpandChrFormat
                             : &lcl_MergeAttr;

        const size_t nSize = m_pSwpHints->Count();

        if (nStt == nEnd)               // no range:
        {
            for (size_t n = 0; n < nSize; ++n)
            {
                const SwTextAttr* pHt = m_pSwpHints->Get(n);
                const sal_Int32 nAttrStart = pHt->GetStart();
                if (nAttrStart > nEnd)         // behind the range
                    break;

                const sal_Int32* pAttrEnd = pHt->End();
                if ( ! pAttrEnd ) // no attributes without end
                    continue;

                if( ( nAttrStart < nStt &&
                        ( pHt->DontExpand() ? nStt < *pAttrEnd
                                            : nStt <= *pAttrEnd )) ||
                    ( nStt == nAttrStart &&
                        ( nAttrStart == *pAttrEnd || !nStt )))
                    (*fnMergeAttr)( rSet, pHt->GetAttr() );
            }
        }
        else                            // a query range is defined
        {
            // #i75299#
            std::optional< std::vector< SwPoolItemEndPair > > pAttrArr;

            const size_t coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN;

            for (size_t n = 0; n < nSize; ++n)
            {
                const SwTextAttr* pHt = m_pSwpHints->Get(n);
                const sal_Int32 nAttrStart = pHt->GetStart();
                if (nAttrStart > nEnd)         // outside, behind
                    break;

                const sal_Int32* pAttrEnd = pHt->End();
                if ( ! pAttrEnd ) // no attributes without end
                    continue;

                bool bChkInvalid = false;
                if (nAttrStart <= nStt)        // before or exactly Start
                {
                    if (*pAttrEnd <= nStt)     // outside, before
                        continue;

                    if (nEnd <= *pAttrEnd)     // behind or exactly End
                        (*fnMergeAttr)( aFormatSet, pHt->GetAttr() );
                    else
//                  else if( pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) )
                        // ambiguous
                        bChkInvalid = true;
                }
                else if (nAttrStart < nEnd     // starts in the range
)//                      && pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) )
                    bChkInvalid = true;

                if( bChkInvalid )
                {
                    // ambiguous?
                    std::optional< SfxItemIter > oItemIter;
                    const SfxPoolItem* pItem = nullptr;

                    if ( RES_TXTATR_AUTOFMT == pHt->Which() )
                    {
                        const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() );
                        if ( pAutoSet )
                        {
                            oItemIter.emplace( *pAutoSet );
                            pItem = oItemIter->GetCurItem();
                        }
                    }
                    else
                        pItem = &pHt->GetAttr();

                    const sal_Int32 nHintEnd = *pAttrEnd;

                    for (; pItem; pItem = oItemIter ? oItemIter->NextItem() : nullptr)
                    {
                        const sal_uInt16 nHintWhich = pItem->Which();
                        OSL_ENSURE(!isUNKNOWNATR(nHintWhich),
                                "SwTextNode::GetAttr(): unknown attribute?");

                        if (!pAttrArr)
                        {
                            pAttrArr = std::vector< SwPoolItemEndPair >(coArrSz);
                        }

                        std::vector< SwPoolItemEndPair >::iterator pPrev = pAttrArr->begin();
                        if (isCHRATR(nHintWhich) ||
                            isTXTATR_WITHEND(nHintWhich))
                        {
                            pPrev += nHintWhich - RES_CHRATR_BEGIN;
                        }
                        else
                        {
                            pPrev = pAttrArr->end();
                        }

                        if( pPrev != pAttrArr->end() )
                        {
                            if( !pPrev->mpItem )
                            {
                                if ( bOnlyTextAttr || *pItem != aFormatSet.Get( nHintWhich ) )
                                {
                                    if( nAttrStart > nStt )
                                    {
                                        rSet.InvalidateItem( nHintWhich );
                                        pPrev->mpItem = INVALID_POOL_ITEM;
                                    }
                                    else
                                    {
                                        pPrev->mpItem = pItem;
                                        pPrev->mnEndPos = nHintEnd;
                                    }
                                }
                            }
                            else if( !IsInvalidItem(pPrev->mpItem) )
                            {
                                if( pPrev->mnEndPos == nAttrStart &&
                                    *pPrev->mpItem == *pItem )
                                {
                                    pPrev->mpItem = pItem;
                                    pPrev->mnEndPos = nHintEnd;
                                }
                                else
                                {
                                    rSet.InvalidateItem( nHintWhich );
                                    pPrev->mpItem = INVALID_POOL_ITEM;
                                }
                            }
                        }
                    } // end while
                }
            }

            if (pAttrArr)
            {
                for (size_t n = 0; n < coArrSz; ++n)
                {
                    const SwPoolItemEndPair& rItemPair = (*pAttrArr)[ n ];
                    if( rItemPair.mpItem && !IsInvalidItem(rItemPair.mpItem) )
                    {
                        const sal_uInt16 nWh =
                            o3tl::narrowing<sal_uInt16>(n + RES_CHRATR_BEGIN);

                        if (nEnd <= rItemPair.mnEndPos) // behind or exactly end
                        {
                            if( *rItemPair.mpItem != aFormatSet.Get( nWh ) )
                                (*fnMergeAttr)( rSet, *rItemPair.mpItem );
                        }
                        else
                            // ambiguous
                            rSet.InvalidateItem( nWh );
                    }
                }
            }
        }
        if( aFormatSet.Count() )
        {
            // remove all from the format-set that are also set in the text-set
            aFormatSet.Differentiate( rSet );
        }
    }

    if (aFormatSet.Count())
    {
        // now "merge" everything
        rSet.Put( aFormatSet );
    }

    return rSet.Count() != 0;
}

namespace
{

typedef std::pair<sal_Int32, sal_Int32> AttrSpan_t;
typedef std::multimap<AttrSpan_t, const SwTextAttr*> AttrSpanMap_t;

struct IsAutoStyle
{
    bool
    operator()(const AttrSpanMap_t::value_type& i_rAttrSpan)
    const
    {
        return i_rAttrSpan.second && i_rAttrSpan.second->Which() == RES_TXTATR_AUTOFMT;
    }
};

/** Removes from io_rAttrSet all items that are set by style on the
    given span.
  */
struct RemovePresentAttrs
{
    explicit RemovePresentAttrs(SfxItemSet& io_rAttrSet)
        : m_rAttrSet(io_rAttrSet)
    {
    }

    void
    operator()(const AttrSpanMap_t::value_type& i_rAttrSpan)
    const
    {
        if (!i_rAttrSpan.second)
        {
            return;
        }

        const SwTextAttr* const pAutoStyle(i_rAttrSpan.second);

        // ITEM: SfxItemIter and removing SfxPoolItems:
        std::vector<sal_uInt16> aDeleteWhichIDs;

        for (SfxItemIter aIter(m_rAttrSet); !aIter.IsAtEnd(); aIter.NextItem())
        {
            if (CharFormat::IsItemIncluded(aIter.GetCurWhich(), pAutoStyle))
            {
                aDeleteWhichIDs.push_back(aIter.GetCurWhich());
            }
        }

        for (auto nDelWhich : aDeleteWhichIDs)
            m_rAttrSet.ClearItem(nDelWhich);
    }

private:
    SfxItemSet& m_rAttrSet;
};

/** Collects all style-covered spans from i_rHints to o_rSpanMap. In
    addition inserts dummy spans with pointer to format equal to 0 for
    all gaps (i.e. spans not covered by any style). This simplifies
    creation of autostyles for all needed spans, but it means all code
    that tries to access the pointer has to check if it's non-null!
 */
void
lcl_CollectHintSpans(const SwpHints& i_rHints, const sal_Int32 nLength,
        AttrSpanMap_t& o_rSpanMap)
{
    sal_Int32 nLastEnd(0);

    for (size_t i = 0; i < i_rHints.Count(); ++i)
    {
        const SwTextAttr* pHint = i_rHints.Get(i);
        const sal_uInt16 nWhich(pHint->Which());
        if (nWhich == RES_TXTATR_CHARFMT || nWhich == RES_TXTATR_AUTOFMT)
        {
            const AttrSpan_t aSpan(pHint->GetStart(), *pHint->End());
            o_rSpanMap.emplace(aSpan, pHint);

            // < not != because there may be multiple CHARFMT at same range
            if (nLastEnd < aSpan.first)
            {
                // insert dummy span covering the gap
                o_rSpanMap.emplace( AttrSpan_t(nLastEnd, aSpan.first), nullptr );
            }

            nLastEnd = aSpan.second;
        }
    }

    // no hints at the end (special case: no hints at all in i_rHints)
    if (nLastEnd != nLength && nLength != 0)
    {
        o_rSpanMap.emplace(AttrSpan_t(nLastEnd, nLength), nullptr);
    }
}

void
lcl_FillWhichIds(const SfxItemSet& i_rAttrSet, std::vector<sal_uInt16>& o_rClearIds)
{
    o_rClearIds.reserve(i_rAttrSet.Count());
    SfxItemIter aIter(i_rAttrSet);
    for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
    {
        o_rClearIds.push_back(pItem->Which());
    }
}

struct SfxItemSetClearer
{
    SfxItemSet & m_rItemSet;
    explicit SfxItemSetClearer(SfxItemSet & rItemSet) : m_rItemSet(rItemSet) { }
    void operator()(sal_uInt16 const nWhich) { m_rItemSet.ClearItem(nWhich); }
};

}

/** Does the hard work of SwTextNode::FormatToTextAttr: the real conversion
    of items to automatic styles.
 */
void
SwTextNode::impl_FormatToTextAttr(const SfxItemSet& i_rAttrSet)
{
    typedef AttrSpanMap_t::iterator AttrSpanMap_iterator_t;
    AttrSpanMap_t aAttrSpanMap;

    if (i_rAttrSet.Count() == 0)
    {
        return;
    }

    // 1. Identify all spans in hints' array

    lcl_CollectHintSpans(*m_pSwpHints, m_Text.getLength(), aAttrSpanMap);

    // 2. Go through all spans and insert new attrs

    AttrSpanMap_iterator_t aCurRange(aAttrSpanMap.begin());
    const AttrSpanMap_iterator_t aEnd(aAttrSpanMap.end());
    while (aCurRange != aEnd)
    {
        typedef std::pair<AttrSpanMap_iterator_t, AttrSpanMap_iterator_t>
            AttrSpanMapRange_t;
        AttrSpanMapRange_t aRange(aAttrSpanMap.equal_range(aCurRange->first));

        // 2a. Collect attributes to insert

        SfxItemSet aCurSet(i_rAttrSet);
        std::for_each(aRange.first, aRange.second, RemovePresentAttrs(aCurSet));

        // 2b. Insert automatic style containing the collected attributes

        if (aCurSet.Count() != 0)
        {
            AttrSpanMap_iterator_t aAutoStyleIt(
                    std::find_if(aRange.first, aRange.second, IsAutoStyle()));
            if (aAutoStyleIt != aRange.second)
            {
                // there already is an automatic style on that span:
                // create new one and remove the original one
                SwTextAttr* const pAutoStyle(const_cast<SwTextAttr*>(aAutoStyleIt->second));
                const std::shared_ptr<SfxItemSet> pOldStyle(
                        static_cast<const SwFormatAutoFormat&>(
                            pAutoStyle->GetAttr()).GetStyleHandle());
                aCurSet.Put(*pOldStyle);

                // remove the old hint
                m_pSwpHints->Delete(pAutoStyle);
                DestroyAttr(pAutoStyle);
            }
            m_pSwpHints->Insert(
                    MakeTextAttr(GetDoc(), aCurSet,
                        aCurRange->first.first, aCurRange->first.second));
        }

        aCurRange = aRange.second;
    }

    // hints were directly inserted, so need to fix the Ignore flags now
    m_pSwpHints->MergePortions(*this);

    // 3. Clear items from the node
    std::vector<sal_uInt16> aClearedIds;
    lcl_FillWhichIds(i_rAttrSet, aClearedIds);
    ClearItemsFromAttrSet(aClearedIds);
}

void SwTextNode::FormatToTextAttr( SwTextNode* pNd )
{
    SfxItemSet aThisSet( GetDoc().GetAttrPool(), aCharFormatSetRange );
    if( HasSwAttrSet() && GetpSwAttrSet()->Count() )
        aThisSet.Put( *GetpSwAttrSet() );

    GetOrCreateSwpHints();

    if( pNd == this )
    {
        impl_FormatToTextAttr(aThisSet);
    }
    else
    {
        // There are five possible combinations of items from this and
        // pNd (pNd is the 'main' node):

        //  case    pNd     this     action

        //     1     -       -      do nothing
        //     2     -       a      convert item to attr of this
        //     3     a       -      convert item to attr of pNd
        //     4     a       a      clear item in this
        //     5     a       b      convert item to attr of this

        SfxItemSet aNdSet( pNd->GetDoc().GetAttrPool(), aCharFormatSetRange );
        if( pNd->HasSwAttrSet() && pNd->GetpSwAttrSet()->Count() )
            aNdSet.Put( *pNd->GetpSwAttrSet() );

        pNd->GetOrCreateSwpHints();

        std::vector<sal_uInt16> aProcessedIds;

        if( aThisSet.Count() )
        {
            SfxItemIter aIter( aThisSet );
            const SfxPoolItem* pItem = aIter.GetCurItem(), *pNdItem = nullptr;
            SfxItemSet aConvertSet( GetDoc().GetAttrPool(), aCharFormatSetRange );
            std::vector<sal_uInt16> aClearWhichIds;

            do
            {
                if( SfxItemState::SET == aNdSet.GetItemState( pItem->Which(), false, &pNdItem ) )
                {
                    if (*pItem == *pNdItem) // 4
                    {
                        aClearWhichIds.push_back( pItem->Which() );
                    }
                    else    // 5
                    {
                        aConvertSet.Put(*pItem);
                    }
                    aProcessedIds.push_back(pItem->Which());
                }
                else    // 2
                {
                    aConvertSet.Put(*pItem);
                }

                pItem = aIter.NextItem();
            } while (pItem);

            // 4/ clear items of this that are set with the same value on pNd
            ClearItemsFromAttrSet( aClearWhichIds );

            // 2, 5/ convert all other items to attrs
            impl_FormatToTextAttr(aConvertSet);
        }

        {
            std::for_each(aProcessedIds.begin(), aProcessedIds.end(),
                    SfxItemSetClearer(aNdSet));

            // 3/ convert items to attrs
            pNd->impl_FormatToTextAttr(aNdSet);

            if( aNdSet.Count() )
            {
                pNd->CallSwClientNotify(SwFormatChangeHint(pNd->GetFormatColl(), pNd->GetFormatColl()));
            }
        }
    }

    SetCalcHiddenCharFlags();

    pNd->TryDeleteSwpHints();
}

void SwpHints::CalcFlags()
{
    m_bDDEFields = m_bFootnote = false;
    const size_t nSize = Count();
    for( size_t nPos = 0; nPos < nSize; ++nPos )
    {
        const SwTextAttr* pAttr = Get( nPos );
        switch( pAttr->Which() )
        {
        case RES_TXTATR_FTN:
            m_bFootnote = true;
            if ( m_bDDEFields )
                return;
            break;
        case RES_TXTATR_FIELD:
            {
                const SwField* pField = pAttr->GetFormatField().GetField();
                if( SwFieldIds::Dde == pField->GetTyp()->Which() )
                {
                    m_bDDEFields = true;
                    if ( m_bFootnote )
                        return;
                }
            }
            break;
        }
    }
}

bool SwpHints::CalcHiddenParaField() const
{
    m_bCalcHiddenParaField = false;
    const bool bOldHiddenByParaField = m_bHiddenByParaField;
    bool bNewHiddenByParaField = false;
    int nNewResultWeight = 0;
    const size_t nSize = Count();
    const SwTextAttr* pTextHt;

    for (size_t nPos = 0; nPos < nSize; ++nPos)
    {
        pTextHt = Get(nPos);
        const sal_uInt16 nWhich = pTextHt->Which();

        if (RES_TXTATR_FIELD == nWhich)
        {
            // see also SwTextFrame::IsHiddenNow()
            const SwFormatField& rField = pTextHt->GetFormatField();
            int nCurWeight = m_rParent.GetDoc().FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which());
            if (nCurWeight > nNewResultWeight)
            {
                nNewResultWeight = nCurWeight;
                bNewHiddenByParaField = m_rParent.GetDoc().FieldHidesPara(*rField.GetField());
            }
            else if (nCurWeight == nNewResultWeight && bNewHiddenByParaField)
            {
                // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide"
                // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only
                // care about fields of higher weight.
                bNewHiddenByParaField = m_rParent.GetDoc().FieldHidesPara(*rField.GetField());
            }
        }
    }
    SetHiddenByParaField(bNewHiddenByParaField);
    return bOldHiddenByParaField != bNewHiddenByParaField;
}

void SwpHints::NoteInHistory( SwTextAttr *pAttr, const bool bNew )
{
    if ( m_pHistory ) { m_pHistory->AddHint( pAttr, bNew ); }
}

namespace {
struct Portion {
    SwTextAttr* pTextAttr;
    sal_Int32 nKey;
    bool isRsidOnlyAutoFormat;
};
typedef std::vector< Portion > PortionMap;
enum MergeResult { MATCH, DIFFER_ONLY_RSID, DIFFER };
}

static MergeResult lcl_Compare_Attributes(
        int i, int j,
        const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange1,
        const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange2,
        std::vector<bool>& RsidOnlyAutoFormatFlagMap);

bool SwpHints::MergePortions( SwTextNode& rNode )
{
    if ( !Count() )
        return false;

    // sort before merging
    Resort();

    bool bRet = false;
    PortionMap aPortionMap;
    aPortionMap.reserve(Count() + 1);
    std::vector<bool> RsidOnlyAutoFormatFlagMap;
    RsidOnlyAutoFormatFlagMap.resize(Count() + 1);
    sal_Int32 nLastPorStart = COMPLETE_STRING;
    sal_Int32 nKey = 0;

    // get portions by start position:
    for ( size_t i = 0; i < Count(); ++i )
    {
        SwTextAttr *pHt = Get( i );
        if ( RES_TXTATR_CHARFMT != pHt->Which() &&
             RES_TXTATR_AUTOFMT != pHt->Which() )
             //&&
             //RES_TXTATR_INETFMT != pHt->Which() )
            continue;

        bool isRsidOnlyAutoFormat(false);
        // check for RSID-only AUTOFMT
        if (RES_TXTATR_AUTOFMT == pHt->Which())
        {
            std::shared_ptr<SfxItemSet> const & pSet(
                    pHt->GetAutoFormat().GetStyleHandle());
            if ((pSet->Count() == 1) && pSet->GetItem(RES_CHRATR_RSID, false))
            {
                // fdo#70201: eliminate no-extent RSID-only AUTOFMT
                // could be produced by ReplaceText or (maybe?) RstAttr
                if (pHt->GetStart() == *pHt->GetEnd())
                {
                    DeleteAtPos(i); // kill it without History!
                    SwTextAttr::Destroy(pHt);
                    --i;
                    continue;
                }
                // fdo#52028: this one has _only_ RSID => ignore it completely
                if (!pHt->IsFormatIgnoreStart() || !pHt->IsFormatIgnoreEnd())
                {
                    NoteInHistory(pHt);
                    pHt->SetFormatIgnoreStart(true);
                    pHt->SetFormatIgnoreEnd  (true);
                    NoteInHistory(pHt, true);
                }
                isRsidOnlyAutoFormat = true;
            }
        }

        if (pHt->GetStart() == *pHt->GetEnd())
        {
            // no-length hints are a disease. ignore them here.
            // the SwAttrIter::SeekFwd will not call Rst/Chg for them.
            continue;
        }

        const sal_Int32 nPorStart = pHt->GetStart();
        if (nPorStart != nLastPorStart)
            ++nKey;
        nLastPorStart = nPorStart;
        aPortionMap.push_back(Portion {pHt, nKey, isRsidOnlyAutoFormat});
        RsidOnlyAutoFormatFlagMap[nKey] = isRsidOnlyAutoFormat;
    }

    // we add data strictly in-order, so we can forward-search the vector
    auto equal_range = [](PortionMap::const_iterator startIt, PortionMap::const_iterator endIt, int i)
    {
        auto it1 = startIt;
        while (it1 != endIt && it1->nKey < i)
            ++it1;
        auto it2 = it1;
        while (it2 != endIt && it2->nKey == i)
            ++it2;
        return std::pair<PortionMap::const_iterator, PortionMap::const_iterator>{ it1, it2 };
    };

    // check if portion i can be merged with portion i+1:
    // note: need to include i=0 to set IgnoreStart and j=nKey+1 to reset
    // IgnoreEnd at first / last portion
    int i = 0;
    int j = i + 1;
    // Store this outside the loop, because we limit the search area on subsequent searches.
    std::pair< PortionMap::const_iterator, PortionMap::const_iterator > aRange1 { aPortionMap.begin(), aPortionMap.begin() + aPortionMap.size() };
    while ( i <= nKey )
    {
        aRange1 = equal_range( aRange1.first, aPortionMap.begin() + aPortionMap.size(), i );
        // start the search for this one from where the first search ended.
        std::pair< PortionMap::const_iterator, PortionMap::const_iterator > aRange2
            = equal_range( aRange1.second, aPortionMap.begin() + aPortionMap.size(), j );

        MergeResult eMerge = lcl_Compare_Attributes(i, j, aRange1, aRange2, RsidOnlyAutoFormatFlagMap);

        if (MATCH == eMerge)
        {
            // important: delete second range so any IgnoreStart on the first
            // range is still valid
            // erase all elements with key i + 1
            sal_Int32 nNewPortionEnd = 0;
            for ( auto aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2 )
            {
                SwTextAttr *const p2 = aIter2->pTextAttr;
                nNewPortionEnd = *p2->GetEnd();

                const size_t nCountBeforeDelete = Count();
                Delete( p2 );

                // robust: check if deletion actually took place before destroying attribute:
                if ( Count() < nCountBeforeDelete )
                    rNode.DestroyAttr( p2 );
            }
            aPortionMap.erase( aRange2.first, aRange2.second );
            ++j;

            // change all attributes with key i
            aRange1 = equal_range( aRange1.first, aPortionMap.begin() + aPortionMap.size(), i );
            for ( auto aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1 )
            {
                SwTextAttr *const p1 = aIter1->pTextAttr;
                NoteInHistory( p1 );
                p1->SetEnd(nNewPortionEnd);
                NoteInHistory( p1, true );
                bRet = true;
            }

            if (bRet)
            {
                Resort();
            }
        }
        else
        {
            // when not merging the ignore flags need to be either set or reset
            // (reset too in case one of the autofmts was recently changed)
            bool const bSetIgnoreFlag(DIFFER_ONLY_RSID == eMerge);
            for (auto aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1)
            {
                if (!aIter1->isRsidOnlyAutoFormat) // already set above, don't change
                {
                    SwTextAttr *const pCurrent(aIter1->pTextAttr);
                    if (pCurrent->IsFormatIgnoreEnd() != bSetIgnoreFlag)
                    {
                        NoteInHistory(pCurrent);
                        pCurrent->SetFormatIgnoreEnd(bSetIgnoreFlag);
                        NoteInHistory(pCurrent, true);
                    }
                }
            }
            for (auto aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2)
            {
                if (!aIter2->isRsidOnlyAutoFormat) // already set above, don't change
                {
                    SwTextAttr *const pCurrent(aIter2->pTextAttr);
                    if (pCurrent->IsFormatIgnoreStart() != bSetIgnoreFlag)
                    {
                        NoteInHistory(pCurrent);
                        pCurrent->SetFormatIgnoreStart(bSetIgnoreFlag);
                        NoteInHistory(pCurrent, true);
                    }
                }
            }
            i = j; // ++i not enough: i + 1 may have been deleted (MATCH)!
            ++j;
        }
    }

    return bRet;
}


static MergeResult lcl_Compare_Attributes(
        int i, int j,
        const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange1,
        const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange2,
        std::vector<bool>& RsidOnlyAutoFormatFlagMap)
{
    PortionMap::const_iterator aIter1 = aRange1.first;
    PortionMap::const_iterator aIter2 = aRange2.first;

    size_t const nAttributesInPor1 = std::distance(aRange1.first, aRange1.second);
    size_t const nAttributesInPor2 = std::distance(aRange2.first, aRange2.second);
    bool const isRsidOnlyAutoFormat1 = i < sal_Int32(RsidOnlyAutoFormatFlagMap.size()) && RsidOnlyAutoFormatFlagMap[i];
    bool const isRsidOnlyAutoFormat2 = j < sal_Int32(RsidOnlyAutoFormatFlagMap.size()) && RsidOnlyAutoFormatFlagMap[j];

    // if both have one they could be equal, but not if only one has it
    bool const bSkipRsidOnlyAutoFormat(nAttributesInPor1 != nAttributesInPor2);

    // this loop needs to handle the case where one has a CHARFMT and the
    // other CHARFMT + RSID-only AUTOFMT, so...
    // want to skip over RSID-only AUTOFMT here, hence the -1
    if (!((nAttributesInPor1 - (isRsidOnlyAutoFormat1 ? 1 : 0)) ==
         (nAttributesInPor2 - (isRsidOnlyAutoFormat2 ? 1 : 0))
         && (nAttributesInPor1 != 0 || nAttributesInPor2 != 0)))
    {
        return DIFFER;
    }

    MergeResult eMerge(MATCH);

    // _if_ there is one element more either in aRange1 or aRange2
    // it _must_ be an RSID-only AUTOFMT, which can be ignored here...
    // But if both have RSID-only AUTOFMT they could be equal, no skip!
    while (aIter1 != aRange1.second || aIter2 != aRange2.second)
    {
        // first of all test if there's no gap (before skipping stuff!)
        if (aIter1 != aRange1.second && aIter2 != aRange2.second &&
            *aIter1->pTextAttr->End() < aIter2->pTextAttr->GetStart())
        {
            return DIFFER;
        }
        // skip it - cannot be equal if bSkipRsidOnlyAutoFormat is set
        if (bSkipRsidOnlyAutoFormat
            && aIter1 != aRange1.second && aIter1->isRsidOnlyAutoFormat)
        {
            assert(DIFFER != eMerge);
            eMerge = DIFFER_ONLY_RSID;
            ++aIter1;
            continue;
        }
        if (bSkipRsidOnlyAutoFormat
            && aIter2 != aRange2.second && aIter2->isRsidOnlyAutoFormat)
        {
            assert(DIFFER != eMerge);
            eMerge = DIFFER_ONLY_RSID;
            ++aIter2;
            continue;
        }
        assert(aIter1 != aRange1.second && aIter2 != aRange2.second);
        SwTextAttr const*const p1 = aIter1->pTextAttr;
        SwTextAttr const*const p2 = aIter2->pTextAttr;
        if (p1->Which() != p2->Which())
        {
            return DIFFER;
        }
        if (!(*p1 == *p2))
        {
            // fdo#52028: for auto styles, check if they differ only
            // in the RSID, which should have no effect on text layout
            if (RES_TXTATR_AUTOFMT != p1->Which())
                return DIFFER;

            const SfxItemSet& rSet1 = *p1->GetAutoFormat().GetStyleHandle();
            const SfxItemSet& rSet2 = *p2->GetAutoFormat().GetStyleHandle();

            // sadly SfxItemSet::operator== does not seem to work?
            SfxItemIter iter1(rSet1);
            SfxItemIter iter2(rSet2);
            for (SfxPoolItem const* pItem1 = iter1.GetCurItem(),
                                  * pItem2 = iter2.GetCurItem();;)
            {
                if (pItem1 && pItem1->Which() == RES_CHRATR_RSID)
                    pItem1 = iter1.NextItem();
                if (pItem2 && pItem2->Which() == RES_CHRATR_RSID)
                    pItem2 = iter2.NextItem();

                if (nullptr == pItem1 && nullptr == pItem2)
                {
                    eMerge = DIFFER_ONLY_RSID;
                    break;
                }

                if (nullptr == pItem1 || nullptr == pItem2)
                {
                    // one ptr is nullptr, not both, that would
                    // have triggered above
                    return DIFFER;
                }

                if (!SfxPoolItem::areSame(*pItem1, *pItem2))
                {
                    return DIFFER;
                }
                pItem1 = iter1.NextItem();
                pItem2 = iter2.NextItem();
            }
        }
        ++aIter1;
        ++aIter2;
    }
    return eMerge;
}


// check if there is already a character format and adjust the sort numbers
static void lcl_CheckSortNumber( const SwpHints& rHints, SwTextCharFormat& rNewCharFormat )
{
    const sal_Int32 nHtStart = rNewCharFormat.GetStart();
    const sal_Int32 nHtEnd   = *rNewCharFormat.GetEnd();
    sal_uInt16 nSortNumber = 0;

    for ( size_t i = 0; i < rHints.Count(); ++i )
    {
        const SwTextAttr* pOtherHt = rHints.Get(i);

        const sal_Int32 nOtherStart = pOtherHt->GetStart();

        if ( nOtherStart > nHtStart )
            break;

        if ( RES_TXTATR_CHARFMT == pOtherHt->Which() )
        {
            const sal_Int32 nOtherEnd = *pOtherHt->End();

            if ( nOtherStart == nHtStart && nOtherEnd == nHtEnd )
            {
                nSortNumber = static_txtattr_cast<const SwTextCharFormat*>(pOtherHt)->GetSortNumber() + 1;
            }
        }
    }

    if ( nSortNumber > 0 )
        rNewCharFormat.SetSortNumber( nSortNumber );
}

/*
 * Try to insert the new hint.
 * Depending on the type of the hint, this either always succeeds, or may fail.
 * Depending on the type of the hint, other hints may be deleted or
 * overwritten.
 * The return value indicates successful insertion.
 */
bool SwpHints::TryInsertHint(
    SwTextAttr* const pHint,
    SwTextNode &rNode,
    const SetAttrMode nMode )
{
    if ( MAX_HINTS <= Count() ) // we're sorry, this flight is overbooked...
    {
        OSL_FAIL("hints array full :-(");
        rNode.DestroyAttr(pHint);
        return false;
    }

    const sal_Int32 *pHtEnd = pHint->GetEnd();
    const sal_uInt16 nWhich = pHint->Which();
    std::vector<sal_uInt16> aWhichSublist;

    switch( nWhich )
    {
    case RES_TXTATR_CHARFMT:
    {
        // Check if character format contains hidden attribute:
        const SwCharFormat* pFormat = pHint->GetCharFormat().GetCharFormat();
        if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN ) )
            rNode.SetCalcHiddenCharFlags();

        static_txtattr_cast<SwTextCharFormat*>(pHint)->ChgTextNode( &rNode );
        break;
    }
    // #i75430# Recalc hidden flags if necessary
    case RES_TXTATR_AUTOFMT:
    {
        std::shared_ptr<SfxItemSet> const & pSet( pHint->GetAutoFormat().GetStyleHandle() );
        if (pHint->GetStart() == *pHint->GetEnd())
        {
            if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false))
            {   // empty range RSID-only hints could cause trouble, there's no
                rNode.DestroyAttr(pHint); // need for them so don't insert
                return false;
            }
        }
        // Check if auto style contains hidden attribute:
        const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pHint, RES_CHRATR_HIDDEN );
        if ( pHiddenItem )
            rNode.SetCalcHiddenCharFlags();

        // fdo#71556: populate aWhichFormatAttr member of SwMsgPoolItem
        pSet->CollectHasItems(aWhichSublist);
        break;
    }
    case RES_TXTATR_INETFMT:
        static_txtattr_cast<SwTextINetFormat*>(pHint)->InitINetFormat(rNode);
        break;

    case RES_TXTATR_FIELD:
    case RES_TXTATR_ANNOTATION:
    case RES_TXTATR_INPUTFIELD:
        {
            SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
            bool bDelFirst = nullptr != pTextField->GetpTextNode();
            pTextField->ChgTextNode( &rNode );
            SwDoc& rDoc = rNode.GetDoc();
            const SwField* pField = pTextField->GetFormatField().GetField();

            if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() )
            {
                // certain fields must update the SwDoc's calculation flags
                switch( pField->GetTyp()->Which() )
                {
                case SwFieldIds::Database:
                case SwFieldIds::SetExp:
                case SwFieldIds::HiddenPara:
                case SwFieldIds::HiddenText:
                case SwFieldIds::DbNumSet:
                case SwFieldIds::DbNextSet:
                    {
                        if( bDelFirst )
                            rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField);
                        if( rNode.GetNodes().IsDocNodes() )
                            rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(true, *pTextField);
                    }
                    break;
                case SwFieldIds::Dde:
                    if( rNode.GetNodes().IsDocNodes() )
                        static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt();
                    break;
                default: break;
                }
            }

            // insert into real document's nodes-array?
            if( rNode.GetNodes().IsDocNodes() )
            {
                bool bInsFieldType = false;
                switch( pField->GetTyp()->Which() )
                {
                case SwFieldIds::SetExp:
                    bInsFieldType = static_cast<SwSetExpFieldType*>(pField->GetTyp())->IsDeleted();
                    if( SwGetSetExpType::Sequence & static_cast<SwSetExpFieldType*>(pField->GetTyp())->GetType() )
                    {
                        // register the field at its FieldType before setting
                        // the sequence reference number!
                        SwSetExpFieldType* pFieldType = static_cast<SwSetExpFieldType*>(
                                    rDoc.getIDocumentFieldsAccess().InsertFieldType( *pField->GetTyp() ) );
                        if( pFieldType != pField->GetTyp() )
                        {
                            SwFormatField* pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField());
                            pFormatField->RegisterToFieldType( *pFieldType );
                            pFormatField->GetField()->ChgTyp( pFieldType );
                        }
                        pFieldType->SetSeqRefNo( *const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)) );
                    }
                    break;
                case SwFieldIds::User:
                    bInsFieldType = static_cast<SwUserFieldType*>(pField->GetTyp())->IsDeleted();
                    break;

                case SwFieldIds::Dde:
                    if( rDoc.getIDocumentFieldsAccess().IsNewFieldLst() )
                        static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt();
                    bInsFieldType = static_cast<SwDDEFieldType*>(pField->GetTyp())->IsDeleted();
                    break;

                case SwFieldIds::Postit:
                    if ( rDoc.GetDocShell() )
                    {
                        rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(
                            &pTextField->GetFormatField(), SwFormatFieldHintWhich::INSERTED));
                    }
                    break;
                default: break;
                }
                if( bInsFieldType )
                    rDoc.getIDocumentFieldsAccess().InsDeletedFieldType( *pField->GetTyp() );
            }
        }
        break;
    case RES_TXTATR_FTN :
        static_cast<SwTextFootnote*>(pHint)->ChgTextNode( &rNode );
        break;
    case RES_TXTATR_REFMARK:
        static_txtattr_cast<SwTextRefMark*>(pHint)->ChgTextNode( &rNode );
        if( rNode.GetNodes().IsDocNodes() )
        {
            // search for a reference with the same name
            SwTextAttr* pTmpHt;
            size_t n = 0, nEnd = Count();
            while (n < nEnd)
            {
                const sal_Int32 *pTmpHtEnd;
                const sal_Int32 *pTmpHintEnd;
                if (RES_TXTATR_REFMARK == (pTmpHt = Get(n))->Which() &&
                    pHint->GetAttr() == pTmpHt->GetAttr() &&
                    nullptr != ( pTmpHtEnd = pTmpHt->GetEnd() ) &&
                    nullptr != ( pTmpHintEnd = pHint->GetEnd() ) )
                {
                    SwComparePosition eCmp = ::ComparePosition(
                            pTmpHt->GetStart(), *pTmpHtEnd,
                            pHint->GetStart(), *pTmpHintEnd );
                    bool bDelOld = true, bChgStart = false, bChgEnd = false;
                    switch( eCmp )
                    {
                    case SwComparePosition::Before:
                    case SwComparePosition::Behind:    bDelOld = false; break;

                    case SwComparePosition::Outside:   bChgStart = bChgEnd = true; break;

                    case SwComparePosition::CollideEnd:
                    case SwComparePosition::OverlapBefore:    bChgStart = true; break;
                    case SwComparePosition::CollideStart:
                    case SwComparePosition::OverlapBehind:    bChgEnd = true; break;
                    default: break;
                    }

                    if( bChgStart )
                    {
                        pHint->SetStart( pTmpHt->GetStart() );
                    }
                    if( bChgEnd )
                        pHint->SetEnd(*pTmpHtEnd);

                    if( bDelOld )
                    {
                        NoteInHistory( pTmpHt );
                        rNode.DestroyAttr( Cut( n ) );
                        --nEnd;
                        continue; // n removed, nEnd adjusted
                    }
                }
                ++n;
            }
        }
        break;
    case RES_TXTATR_TOXMARK:
        static_txtattr_cast<SwTextTOXMark*>(pHint)->ChgTextNode( &rNode );
        break;

    case RES_TXTATR_CJK_RUBY:
        static_txtattr_cast<SwTextRuby*>(pHint)->InitRuby(rNode);
        break;

    case RES_TXTATR_META:
    case RES_TXTATR_METAFIELD:
        static_txtattr_cast<SwTextMeta *>(pHint)->ChgTextNode( &rNode );
        break;

    case RES_TXTATR_CONTENTCONTROL:
        static_txtattr_cast<SwTextContentControl*>(pHint)->ChgTextNode( &rNode );
        break;

    case RES_CHRATR_HIDDEN:
        rNode.SetCalcHiddenCharFlags();
        break;
    }

    if( SetAttrMode::DONTEXPAND & nMode )
        pHint->SetDontExpand( true );

    // special handling for SwTextAttrs without end:
    // 1) they cannot overlap
    // 2) if two fields are adjacent, they must not be merged into one
    // this is guaranteed by inserting a CH_TXTATR_* into the paragraph text!
    sal_Int32 nHtStart = pHint->GetStart();
    if( !pHtEnd )
    {
        Insert( pHint );
        NoteInHistory(pHint, true);
        CalcFlags();
#ifdef DBG_UTIL
        if( !rNode.GetDoc().IsInReading() )
            CHECK;
#endif
        // ... and notify listeners
        if(rNode.HasWriterListeners())
        {
            SwUpdateAttr aHint(
                nHtStart,
                nHtStart,
                nWhich);

            rNode.TriggerNodeUpdate(sw::UpdateAttrHint(&aHint, &aHint));
        }

        return true;
    }

    // from here on, pHint is known to have an end index!

    if( *pHtEnd < nHtStart )
    {
        assert(*pHtEnd >= nHtStart);

        // just swap the nonsense:
        pHint->SetStart(*pHtEnd);
        pHint->SetEnd(nHtStart);
        nHtStart = pHint->GetStart();
    }

    // I need this value later on for notification but the pointer may become invalid
    const sal_Int32 nHintEnd = *pHtEnd;
    const bool bNoHintAdjustMode = bool(SetAttrMode::NOHINTADJUST & nMode);

    // handle nesting attributes: inserting may fail due to overlap!
    if (pHint->IsNesting())
    {
        const bool bRet(
            TryInsertNesting(rNode, *static_txtattr_cast<SwTextAttrNesting*>(pHint)));
        if (!bRet) return false;
    }
    // Currently REFMARK and TOXMARK have OverlapAllowed set to true.
    // These attributes may be inserted directly.
    // Also attributes without length may be inserted directly.
    // SETATTR_NOHINTADJUST is set e.g., during undo.
    // Portion building in not necessary during XML import.
    else if ( !bNoHintAdjustMode &&
         !pHint->IsOverlapAllowedAttr() &&
         !rNode.GetDoc().IsInXMLImport() &&
         ( RES_TXTATR_AUTOFMT == nWhich ||
           RES_TXTATR_CHARFMT == nWhich ) )
    {
        assert( nWhich != RES_TXTATR_AUTOFMT ||
                static_cast<const SwFormatAutoFormat&>(pHint->GetAttr()).GetStyleHandle()->GetPool() ==
                &rNode.GetDoc().GetAttrPool());

        BuildPortions( rNode, *pHint, nMode );

        if ( nHtStart < nHintEnd ) // skip merging for 0-length attributes
            MergePortions( rNode );
    }
    else
    {
        // There may be more than one character style at the current position.
        // Take care of the sort number.
        // FME 2007-11-08 #i82989# in NOHINTADJUST mode, we want to insert
        // character attributes directly
        if (!bNoHintAdjustMode
            && (   (RES_TXTATR_CHARFMT == nWhich)
                // tdf#149978 also for import of automatic styles, could be produced by non-LO application
                || (RES_TXTATR_AUTOFMT == nWhich && rNode.GetDoc().IsInXMLImport())))
        {
            BuildPortions( rNode, *pHint, nMode );
        }
        else
        {
            // #i82989# Check sort numbers in NoHintAdjustMode
            if ( RES_TXTATR_CHARFMT == nWhich )
                lcl_CheckSortNumber(*this, *static_txtattr_cast<SwTextCharFormat*>(pHint));

            Insert( pHint );
            NoteInHistory( pHint, true );
        }
    }

    // ... and notify listeners
    if ( rNode.HasWriterListeners() )
    {
        const SwUpdateAttr aHint(nHtStart, nHintEnd, nWhich, std::move(aWhichSublist));
        rNode.TriggerNodeUpdate(sw::UpdateAttrHint(&aHint, &aHint));
    }

#ifdef DBG_UTIL
    if( !bNoHintAdjustMode && !rNode.GetDoc().IsInReading() )
        CHECK;
#endif

    return true;
}

void SwpHints::DeleteAtPos( const size_t nPos )
{
    assert(m_StartMapNeedsSortingRange.first == SAL_MAX_INT32 && "deleting at pos and the list needs sorting?");

    SwTextAttr *pHint = Get(nPos);
    assert( pHint->m_pHints == this );
    // ChainDelete( pHint );
    NoteInHistory( pHint );

    // optimization: nPos is the position in the Starts array
    SwTextAttr *pHt = m_HintsByStart[ nPos ];
    m_HintsByStart.erase( m_HintsByStart.begin() + nPos );

    if (m_StartMapNeedsSortingRange.first != SAL_MAX_INT32)
        ResortStartMap();
    if (m_EndMapNeedsSortingRange.first != SAL_MAX_INT32)
        ResortEndMap();
    if (m_WhichMapNeedsSortingRange.first.first != SAL_MAX_INT32)
        ResortWhichMap();

    auto findIt = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd());
    assert(*findIt == pHt);
    m_HintsByEnd.erase(findIt);

    auto findIt2 = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), pHt, CompareSwpHtWhichStart());
    assert(*findIt2 == pHt);
    m_HintsByWhichAndStart.erase(findIt2);

    pHt->m_pHints = nullptr;

    if( pHint->Which() == RES_TXTATR_FIELD )
    {
        SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
        const SwFieldType* pFieldTyp = pTextField->GetFormatField().GetField()->GetTyp();
        if( SwFieldIds::Dde == pFieldTyp->Which() )
        {
            const SwTextNode* pNd = pTextField->GetpTextNode();
            if( pNd && pNd->GetNodes().IsDocNodes() )
                const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pFieldTyp))->DecRefCnt();
            pTextField->ChgTextNode(nullptr);
        }
        else if (m_bHiddenByParaField
                 && m_rParent.GetDoc().FieldCanHideParaWeight(pFieldTyp->Which()))
        {
            m_bCalcHiddenParaField = true;
        }
    }
    else if ( pHint->Which() == RES_TXTATR_ANNOTATION )
    {
        SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
        const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(
            SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED));
    }

    CalcFlags();
    CHECK_NOTMERGED; // called from BuildPortions
}

/// delete the hint
/// precondition: pTextHt must be in this array
void SwpHints::Delete( SwTextAttr const * pTextHt )
{
    const size_t nPos = GetIndexOf( pTextHt );
    assert(SAL_MAX_SIZE != nPos);
    if( SAL_MAX_SIZE != nPos )
        DeleteAtPos( nPos );
}

void SwTextNode::ClearSwpHintsArr( bool bDelFields )
{
    if ( !HasHints() )
        return;

    size_t nPos = 0;
    while ( nPos < m_pSwpHints->Count() )
    {
        SwTextAttr* pDel = m_pSwpHints->Get( nPos );
        bool bDel = false;

        switch( pDel->Which() )
        {
        case RES_TXTATR_FLYCNT:
        case RES_TXTATR_FTN:
            break;

        case RES_TXTATR_FIELD:
        case RES_TXTATR_ANNOTATION:
        case RES_TXTATR_INPUTFIELD:
            if( bDelFields )
                bDel = true;
            break;
        default:
            bDel = true; break;
        }

        if( bDel )
        {
            m_pSwpHints->DeleteAtPos( nPos );
            DestroyAttr( pDel );
        }
        else
            ++nPos;
    }
}

LanguageType SwTextNode::GetLang( const sal_Int32 nBegin, const sal_Int32 nLen,
                           sal_uInt16 nScript, bool const bNoneIfNoHyphenation ) const
{
    LanguageType nRet = LANGUAGE_DONTKNOW;

    if ( ! nScript )
    {
        nScript = g_pBreakIt->GetRealScriptOfText( m_Text, nBegin );
    }

    // #i91465# Consider nScript if pSwpHints == 0
    const sal_uInt16 nWhichId = bNoneIfNoHyphenation
            ? RES_CHRATR_NOHYPHEN
            : GetWhichOfScript( RES_CHRATR_LANGUAGE, nScript );

    if ( HasHints() )
    {
        const sal_Int32 nEnd = nBegin + nLen;
        const size_t nSize = m_pSwpHints->Count();
        for ( size_t i = 0; i < nSize; ++i )
        {
            const SwTextAttr *pHt = m_pSwpHints->Get(i);
            const sal_Int32 nAttrStart = pHt->GetStart();
            if( nEnd < nAttrStart )
                break;

            const sal_uInt16 nWhich = pHt->Which();

            if( nWhichId == nWhich ||
                    ( ( pHt->IsCharFormatAttr() || RES_TXTATR_AUTOFMT == nWhich ) && CharFormat::IsItemIncluded( nWhichId, pHt ) ) )
            {
                const sal_Int32 *pEndIdx = pHt->End();
                // do the attribute and the range overlap?
                if( !pEndIdx )
                    continue;
                if( nLen )
                {
                    if( nAttrStart >= nEnd || nBegin >= *pEndIdx )
                        continue;
                }
                else if( nBegin != nAttrStart || ( nAttrStart != *pEndIdx && nBegin ))
                {
                    if( nAttrStart >= nBegin )
                        continue;
                    if( pHt->DontExpand() ? nBegin >= *pEndIdx : nBegin > *pEndIdx)
                        continue;
                }
                const SfxPoolItem* pItem = CharFormat::GetItem( *pHt, nWhichId );

                if ( RES_CHRATR_NOHYPHEN == nWhichId )
                {
                   // bNoneIfNoHyphenation = true: return with LANGUAGE_NONE,
                   // if the hyphenation is disabled by character formatting
                   if  ( static_cast<const SvxNoHyphenItem*>(pItem)->GetValue() )
                       return LANGUAGE_NONE;
                   continue;
                }

                const LanguageType nLng = static_cast<const SvxLanguageItem*>(pItem)->GetLanguage();

                // does the attribute completely cover the range?
                if( nAttrStart <= nBegin && nEnd <= *pEndIdx )
                    nRet = nLng;
                else if( LANGUAGE_DONTKNOW == nRet )
                    nRet = nLng; // partial overlap, the first one wins
            }
        }
    }
    if( LANGUAGE_DONTKNOW == nRet && !bNoneIfNoHyphenation )
    {
        nRet = static_cast<const SvxLanguageItem&>(GetSwAttrSet().Get( nWhichId )).GetLanguage();
        if( LANGUAGE_DONTKNOW == nRet )
            nRet = GetAppLanguage();
    }
    return nRet;
}

sal_Unicode GetCharOfTextAttr( const SwTextAttr& rAttr )
{
    sal_Unicode cRet = CH_TXTATR_BREAKWORD;
    switch ( rAttr.Which() )
    {
        case RES_TXTATR_REFMARK:
        case RES_TXTATR_TOXMARK:
        case RES_TXTATR_ANNOTATION:
            cRet = CH_TXTATR_INWORD;
        break;

        case RES_TXTATR_FIELD:
        case RES_TXTATR_FLYCNT:
        case RES_TXTATR_FTN:
        case RES_TXTATR_META:
        case RES_TXTATR_METAFIELD:
        case RES_TXTATR_CONTENTCONTROL:
        {
            cRet = CH_TXTATR_BREAKWORD;
        }
        break;
        case RES_TXTATR_LINEBREAK:
        {
            cRet = CH_TXTATR_NEWLINE;
        }
        break;

        default:
            assert(!"GetCharOfTextAttr: unknown attr");
            break;
    }
    return cRet;
}

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