/* -*- 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 <hintids.hxx>

#include <osl/diagnose.h>
#include <unotools/charclass.hxx>

#include <editeng/boxitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/tstpitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/acorrcfg.hxx>
#include <o3tl/string_view.hxx>

#include <swwait.hxx>
#include <fmtpdsc.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <DocumentRedlineManager.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <redline.hxx>
#include <unocrsr.hxx>
#include <docary.hxx>
#include <editsh.hxx>
#include <contentindex.hxx>
#include <pam.hxx>
#include <swundo.hxx>
#include <poolfmt.hxx>
#include <ndtxt.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>
#include <frminf.hxx>
#include <pagedesc.hxx>
#include <paratr.hxx>
#include <acorrect.hxx>
#include <shellres.hxx>
#include <section.hxx>
#include <frmatr.hxx>
#include <charatr.hxx>
#include <mdiexp.hxx>
#include <strings.hrc>
#include <comcore.hxx>
#include <numrule.hxx>
#include <itabenum.hxx>

#include <memory>
#include <utility>

using namespace ::com::sun::star;

//JP 16.12.99: definition:
//      from pos cPosEnDash to cPosEmDash all chars changed to em dashes,
//      from pos cPosEmDash to cPosEnd    all chars changed to em dashes
//      all other chars are changed to the user configuration

const sal_Unicode pBulletChar[6] = { '+', '*', '-', 0x2013, 0x2014, 0 };
const int cnPosEnDash = 2, cnPosEmDash = 4;

const sal_Unicode cStarSymbolEnDash = 0x2013;
const sal_Unicode cStarSymbolEmDash = 0x2014;

SvxSwAutoFormatFlags* SwEditShell::s_pAutoFormatFlags = nullptr;

// Number of num-/bullet-paragraph templates. MAXLEVEL will soon be raised
// to x, but not the number of templates. (Artifact from <= 4.0)
const sal_uInt16 cnNumBullColls = 4;

class SwAutoFormat
{
    SvxSwAutoFormatFlags m_aFlags;
    SwPaM m_aDelPam;            // a Pam that can be used
    SwNodeIndex m_aNdIdx;       // the index on the current TextNode
    SwNodeIndex m_aEndNdIdx;    // index on the end of the area

    SwEditShell* m_pEditShell;
    SwDoc* m_pDoc;
    SwTextNode* m_pCurTextNd;     // the current TextNode
    SwTextFrame* m_pCurTextFrame;     // frame of the current TextNode
    bool m_bIsRightToLeft;      // text direction of the current frame
    SwNodeOffset m_nEndNdIdx;      // for the percentage-display
    mutable std::optional<CharClass> m_oCharClass; // Character classification
    mutable LanguageType m_eCharClassLang;

    sal_uInt16 m_nRedlAutoFormatSeqId;

    enum
    {
        NONE = 0,
        DELIM = 1,
        DIGIT = 2,
        CHG = 4,
        LOWER_ALPHA = 8,
        UPPER_ALPHA = 16,
        LOWER_ROMAN = 32,
        UPPER_ROMAN = 64,
        NO_DELIM = (DIGIT|LOWER_ALPHA|UPPER_ALPHA|LOWER_ROMAN|UPPER_ROMAN)
    };

    bool m_bEnd : 1;
    bool m_bMoreLines : 1;

    CharClass& GetCharClass( LanguageType eLang ) const
    {
        if( !m_oCharClass || eLang != m_eCharClassLang )
        {
            m_oCharClass.emplace( LanguageTag( eLang ) );
            m_eCharClassLang = eLang;
        }
        return *m_oCharClass;
    }

    static bool IsSpace( const sal_Unicode c )
        { return (' ' == c || '\t' == c || 0x0a == c|| 0x3000 == c /* Jap. space */); }

    void SetColl( sal_uInt16 nId, bool bHdLineOrText = false );
    void GoNextPara();
    static bool HasObjects(const SwTextFrame &);

    // TextNode methods
    const SwTextFrame * GetNextNode(bool isCheckEnd = true) const;
    static bool IsEmptyLine(const SwTextFrame & rFrame)
    {
        return rFrame.GetText().isEmpty()
            || rFrame.GetText().getLength() == GetLeadingBlanks(rFrame.GetText());
    }

    bool IsOneLine(const SwTextFrame &) const;
    bool IsFastFullLine(const SwTextFrame &) const;
    bool IsNoAlphaLine(const SwTextFrame &) const;
    bool IsEnumericChar(const SwTextFrame &) const;
    static bool IsBlanksInString(const SwTextFrame&);
    sal_uInt16 CalcLevel(const SwTextFrame&, sal_uInt16 *pDigitLvl = nullptr) const;
    sal_Int32 GetBigIndent(TextFrameIndex & rCurrentSpacePos) const;

    static OUString DelLeadingBlanks(const OUString& rStr);
    static OUString DelTrailingBlanks( const OUString& rStr );
    static sal_Int32 GetLeadingBlanks( std::u16string_view aStr );
    static sal_Int32 GetTrailingBlanks( std::u16string_view aStr );

    bool IsFirstCharCapital(const SwTextFrame & rNd) const;
    sal_uInt16 GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos,
                            OUString* pPrefix = nullptr, OUString* pPostfix = nullptr,
                            OUString* pNumTypes = nullptr ) const;
    /// get the FORMATTED TextFrame
    SwTextFrame* GetFrame( const SwTextNode& rTextNd ) const;
    SwTextFrame * EnsureFormatted(SwTextFrame const&) const;

    void BuildIndent();
    void BuildText();
    void BuildTextIndent();
    void BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel );
    void BuildNegIndent( SwTwips nSpaces );
    void BuildHeadLine( sal_uInt16 nLvl );

    static bool HasBreakAttr(const SwTextFrame &);
    void DeleteSel( SwPaM& rPam );
    void DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect);
    bool DeleteJoinCurNextPara(SwTextFrame const* pNextFrame, bool bIgnoreLeadingBlanks = false);
    /// delete in the node start and/or end
    void DeleteLeadingTrailingBlanks( bool bStart = true, bool bEnd = true );
    void DelEmptyLine( bool bTstNextPara = true );
    /// when using multiline paragraphs delete the "left" and/or
    /// "right" margins
    void DelMoreLinesBlanks( bool bWithLineBreaks = false );
    /// join with the previous paragraph
    void JoinPrevPara();
    /// execute AutoCorrect on current TextNode
    void AutoCorrect(TextFrameIndex nSttPos = TextFrameIndex(0));

    bool CanJoin(const SwTextFrame * pNextFrame) const
    {
        return !m_bEnd && pNextFrame
            && !IsEmptyLine(*pNextFrame)
            && !IsNoAlphaLine(*pNextFrame)
            && !IsEnumericChar(*pNextFrame)
            // check the last / first nodes here...
            && ((COMPLETE_STRING - 50 - pNextFrame->GetTextNodeFirst()->GetText().getLength())
                > (m_pCurTextFrame->GetMergedPara()
                      ? m_pCurTextFrame->GetMergedPara()->pLastNode
                      : m_pCurTextNd)->GetText().getLength())
            && !HasBreakAttr(*pNextFrame);
    }

    /// is a dot at the end ??
    static bool IsSentenceAtEnd(const SwTextFrame & rTextFrame);

    bool DoUnderline();
    bool DoTable();

    void SetRedlineText_( sal_uInt16 nId );
    bool SetRedlineText( sal_uInt16 nId ) {
        if( m_aFlags.bWithRedlining )
            SetRedlineText_( nId );
        return true;
    }
    void ClearRedlineText() {
        if( m_aFlags.bWithRedlining )
            m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment(nullptr);
    }

public:
    SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags,
                SwNode const * pSttNd = nullptr, SwNode const * pEndNd = nullptr );
};

static const sal_Unicode* StrChr( const sal_Unicode* pSrc, sal_Unicode c )
{
    while( *pSrc && *pSrc != c )
        ++pSrc;
    return *pSrc ? pSrc : nullptr;
}

SwTextFrame* SwAutoFormat::GetFrame( const SwTextNode& rTextNd ) const
{
    // get the Frame
    const SwContentFrame *pFrame = rTextNd.getLayoutFrame( m_pEditShell->GetLayout() );
    assert(pFrame && "For Autoformat a Layout is needed");
    return EnsureFormatted(*static_cast<SwTextFrame const*>(pFrame));
}

SwTextFrame * SwAutoFormat::EnsureFormatted(SwTextFrame const& rFrame) const
{
    SwTextFrame *const pFrame(const_cast<SwTextFrame*>(&rFrame));
    if( m_aFlags.bAFormatByInput && !pFrame->isFrameAreaDefinitionValid() )
    {
        DisableCallbackAction a(*pFrame->getRootFrame());
        SwRect aTmpFrame( pFrame->getFrameArea() );
        SwRect aTmpPrt( pFrame->getFramePrintArea() );
        pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut());

        if( pFrame->getFrameArea() != aTmpFrame || pFrame->getFramePrintArea() != aTmpPrt ||
            !pFrame->GetPaintSwRect().IsEmpty())
        {
            pFrame->SetCompletePaint();
        }
    }

    return pFrame->GetFormatted();
}

void SwAutoFormat::SetRedlineText_( sal_uInt16 nActionId )
{
    OUString sText;
    sal_uInt16 nSeqNo = 0;
    if( STR_AUTOFMTREDL_END > nActionId )
    {
        sText = SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ nActionId ];
        switch( nActionId )
        {
        case STR_AUTOFMTREDL_SET_NUMBER_BULLET:
        case STR_AUTOFMTREDL_DEL_MORELINES:

        // AutoCorrect actions
        case STR_AUTOFMTREDL_USE_REPLACE:
        case STR_AUTOFMTREDL_CPTL_STT_WORD:
        case STR_AUTOFMTREDL_CPTL_STT_SENT:
        case STR_AUTOFMTREDL_TYPO:
        case STR_AUTOFMTREDL_UNDER:
        case STR_AUTOFMTREDL_BOLD:
        case STR_AUTOFMTREDL_FRACTION:
        case STR_AUTOFMTREDL_DASH:
        case STR_AUTOFMTREDL_ORDINAL:
        case STR_AUTOFMTREDL_NON_BREAK_SPACE:
        case STR_AUTOFMTREDL_TRANSLITERATE_RTL:
        case STR_AUTOFMTREDL_ITALIC:
        case STR_AUTOFMTREDL_STRIKETHROUGH:
            nSeqNo = ++m_nRedlAutoFormatSeqId;
            break;
        }
    }
#if OSL_DEBUG_LEVEL > 0
    else
        sText = "Action text is missing";
#endif

    m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText, nSeqNo );
}

void SwAutoFormat::GoNextPara()
{
    SwNode* pNewNd = nullptr;
    do {
        // has to be checked twice before and after incrementation
        if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
        {
            m_bEnd = true;
            return;
        }

        sw::GotoNextLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
        if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
        {
            m_bEnd = true;
            return;
        }
        else
            pNewNd = &m_aNdIdx.GetNode();

        // not a TextNode ->
        //      TableNode   : skip table
        //      NoTextNode   : skip nodes
        //      EndNode     : at the end, terminate
        if( pNewNd->IsEndNode() )
        {
            m_bEnd = true;
            return;
        }
        else if( pNewNd->IsTableNode() )
            m_aNdIdx = *pNewNd->EndOfSectionNode();
        else if( pNewNd->IsSectionNode() )
        {
            const SwSection& rSect = pNewNd->GetSectionNode()->GetSection();
            if( rSect.IsHiddenFlag() || rSect.IsProtectFlag() )
                m_aNdIdx = *pNewNd->EndOfSectionNode();
        }
    } while( !pNewNd->IsTextNode() );

    if( !m_aFlags.bAFormatByInput )
        ::SetProgressState( sal_Int32(m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex()),
                            m_pDoc->GetDocShell() );

    m_pCurTextNd = static_cast<SwTextNode*>(pNewNd);
    m_pCurTextFrame = GetFrame( *m_pCurTextNd );
    m_bIsRightToLeft = m_pCurTextFrame->IsRightToLeft();
}

bool SwAutoFormat::HasObjects(const SwTextFrame & rFrame)
{
    // Is there something bound to the paragraph in the paragraph
    // like Frames, DrawObjects, ...
    SwNodeIndex node(*rFrame.GetTextNodeFirst());
    do
    {
        if (!node.GetNode().GetAnchoredFlys().empty())
            return true;
        ++node;
    }
    while (sw::FrameContainsNode(rFrame, node.GetIndex()));
    return false;
}

const SwTextFrame* SwAutoFormat::GetNextNode(bool const isCheckEnd) const
{
    SwNodeIndex tmp(m_aNdIdx);
    sw::GotoNextLayoutTextFrame(tmp, m_pEditShell->GetLayout());
    if ((isCheckEnd && m_aEndNdIdx <= tmp) || !tmp.GetNode().IsTextNode())
        return nullptr;
    // note: the returned frame is not necessarily formatted, have to call
    // EnsureFormatted for that
    return static_cast<SwTextFrame*>(tmp.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout()));
}

bool SwAutoFormat::IsOneLine(const SwTextFrame & rFrame) const
{
    SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
    return aFInfo.IsOneLine();
}

bool SwAutoFormat::IsFastFullLine(const SwTextFrame & rFrame) const
{
    bool bRet = m_aFlags.bRightMargin;
    if( bRet )
    {
        SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
        bRet = aFInfo.IsFilled( m_aFlags.nRightMargin );
    }
    return bRet;
}

bool SwAutoFormat::IsEnumericChar(const SwTextFrame& rFrame) const
{
    const OUString& rText = rFrame.GetText();
    bool bIsShortBullet = rText == "* " || rText == "- ";
    sal_uInt16 nMinLen = bIsShortBullet ? 1 : 2;
    TextFrameIndex nBlanks(GetLeadingBlanks(rText));
    const TextFrameIndex nLen = TextFrameIndex(rText.getLength()) - nBlanks;
    if( !nLen )
        return false;

    // -, +, * separated by blank ??
    if (TextFrameIndex(nMinLen) < nLen && IsSpace(rText[sal_Int32(nBlanks) + 1]))
    {
        if (StrChr(pBulletChar, rText[sal_Int32(nBlanks)]))
            return true;
        // Should there be a symbol font at the position?
        SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
        if (aFInfo.IsBullet(nBlanks))
            return true;
    }

    // 1.) / 1. / 1.1.1 / (1). / (1) / ...
    return USHRT_MAX != GetDigitLevel(rFrame, nBlanks);
}

bool SwAutoFormat::IsBlanksInString(const SwTextFrame& rFrame)
{
    // Search more than 5 consecutive blanks/tabs in the string.
    OUString sTmp( DelLeadingBlanks(rFrame.GetText()) );
    const sal_Int32 nLen = sTmp.getLength();
    sal_Int32 nIdx = 0;
    while (nIdx < nLen)
    {
        // Skip non-blanks
        while (nIdx < nLen && !IsSpace(sTmp[nIdx])) ++nIdx;
        if (nIdx == nLen)
            return false;
        // Then count consecutive blanks
        const sal_Int32 nFirst = nIdx;
        while (nIdx < nLen && IsSpace(sTmp[nIdx])) ++nIdx;
        // And exit if enough consecutive blanks were found
        if (nIdx-nFirst > 5)
            return true;
    }
    return false;
}

sal_uInt16 SwAutoFormat::CalcLevel(const SwTextFrame & rFrame,
        sal_uInt16 *const pDigitLvl) const
{
    sal_uInt16 nLvl = 0, nBlnk = 0;
    const OUString& rText = rFrame.GetText();
    if( pDigitLvl )
        *pDigitLvl = USHRT_MAX;

    if (RES_POOLCOLL_TEXT_MOVE == rFrame.GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId())
    {
        if( m_aFlags.bAFormatByInput )
        {
            // this is very non-obvious: on the *first* invocation of
            // AutoFormat, the node will have the tabs (any number) converted
            // to a fixed indent in BuildTextIndent(), and the number of tabs
            // is stored in the node;
            // on the *second* invocation of AutoFormat, CalcLevel() will
            // retrieve the stored number, and it will be used by
            // BuildHeadLine() to select the corresponding heading style.
            nLvl = rFrame.GetTextNodeForParaProps()->GetAutoFormatLvl();
            const_cast<SwTextNode *>(rFrame.GetTextNodeForParaProps())->SetAutoFormatLvl(0);
            if( nLvl )
                return nLvl;
        }
        ++nLvl;
    }

    for (TextFrameIndex n(0),
                     nEnd(rText.getLength()); n < nEnd; ++n)
    {
        switch (rText[sal_Int32(n)])
        {
        case ' ':   if( 3 == ++nBlnk )
                    {
                        ++nLvl;
                        nBlnk = 0;
                    }
                    break;
        case '\t':  ++nLvl;
                    nBlnk = 0;
                    break;
        default:
            if( pDigitLvl )
                // test 1.) / 1. / 1.1.1 / (1). / (1) / ...
                *pDigitLvl = GetDigitLevel(rFrame, n);
            return nLvl;
        }
    }
    return nLvl;
}

sal_Int32 SwAutoFormat::GetBigIndent(TextFrameIndex & rCurrentSpacePos) const
{
    SwTextFrameInfo aFInfo( m_pCurTextFrame );
    const SwTextFrame* pNextFrame = nullptr;

    if( !m_bMoreLines )
    {
        pNextFrame = GetNextNode();
        if (!CanJoin(pNextFrame) || !IsOneLine(*pNextFrame))
            return 0;

        pNextFrame = EnsureFormatted(*pNextFrame);
    }

    return aFInfo.GetBigIndent( rCurrentSpacePos, pNextFrame );
}

bool SwAutoFormat::IsNoAlphaLine(const SwTextFrame & rFrame) const
{
    const OUString& rStr = rFrame.GetText();
    if( rStr.isEmpty() )
        return false;
    // or better: determine via number of AlphaNum and !AlphaNum characters
    sal_Int32 nANChar = 0, nBlnk = 0;

    for (TextFrameIndex n(0),
                     nEnd(rStr.getLength()); n < nEnd; ++n)
        if (IsSpace(rStr[sal_Int32(n)]))
            ++nBlnk;
        else
        {
            auto const pair = rFrame.MapViewToModel(n);
            CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage());
            if (rCC.isLetterNumeric(rStr, sal_Int32(n)))
                ++nANChar;
        }

    // If there are 75% of non-alphanumeric characters, then true
    sal_uLong nLen = rStr.getLength() - nBlnk;
    nLen = ( nLen * 3 ) / 4;            // long overflow, if the strlen > sal_uInt16
    return sal_Int32(nLen) < (rStr.getLength() - nANChar - nBlnk);
}

bool SwAutoFormat::DoUnderline()
{
    if( !m_aFlags.bSetBorder )
        return false;

    OUString const& rText(m_pCurTextFrame->GetText());
    int eState = 0;
    sal_Int32 nCnt = 0;
    while (nCnt < rText.getLength())
    {
        int eTmp = 0;
        switch (rText[nCnt])
        {
            case '-': eTmp = 1; break;
            case '_': eTmp = 2; break;
            case '=': eTmp = 3; break;
            case '*': eTmp = 4; break;
            case '~': eTmp = 5; break;
            case '#': eTmp = 6; break;
            default:
                return false;
        }
        if( 0 == eState )
            eState = eTmp;
        else if( eState != eTmp )
            return false;
        ++nCnt;
    }

    if( 2 < nCnt )
    {
        // then underline the previous paragraph if one exists
        DelEmptyLine( false ); // -> point will be on end of current paragraph
        // WARNING: rText may be deleted now, m_pCurTextFrame may be nullptr
        m_aDelPam.SetMark();
        // apply to last node & rely on InsertItemSet to apply it to props-node

        editeng::SvxBorderLine aLine;
        switch( eState )
        {
        case 1:         // single, hairline
            aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
            aLine.SetWidth( SvxBorderLineWidth::Hairline );
            break;
        case 2:         // single, thin
            aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
            aLine.SetWidth( SvxBorderLineWidth::Thin );
            break;
        case 3:         // double, thin
            aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
            aLine.SetWidth( SvxBorderLineWidth::Thin );
            break;
        case 4:         // double, thick/thin
            aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP);
            aLine.SetWidth( SvxBorderLineWidth::Thick  );
            break;
        case 5:         // double, thin/thick
            aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP);
            aLine.SetWidth( SvxBorderLineWidth::Thick );
            break;
        case 6:         // double, medium
            aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
            aLine.SetWidth( SvxBorderLineWidth::Medium );
            break;
        }
        SfxItemSetFixed<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER,
                    RES_BOX, RES_BOX>  aSet(m_pDoc->GetAttrPool());
        aSet.Put( SwParaConnectBorderItem( false ) );
        SvxBoxItem aBox( RES_BOX );
        aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM );
        aBox.SetDistance(42, SvxBoxItemLine::BOTTOM );     // ~0,75 mm
        aSet.Put(aBox);
        m_pDoc->getIDocumentContentOperations().InsertItemSet(m_aDelPam, aSet,
                SetAttrMode::DEFAULT, m_pEditShell->GetLayout());

        m_aDelPam.DeleteMark();
    }
    return 2 < nCnt;
}

bool SwAutoFormat::DoTable()
{
    if( !m_aFlags.bCreateTable || !m_aFlags.bAFormatByInput ||
        m_pCurTextNd->FindTableNode() )
        return false;

    const OUString& rTmp = m_pCurTextFrame->GetText();
    TextFrameIndex nSttPlus(GetLeadingBlanks(rTmp));
    TextFrameIndex nEndPlus(GetTrailingBlanks(rTmp));
    sal_Unicode cChar;

    if (TextFrameIndex(2) > nEndPlus - nSttPlus
        || ('+' != (cChar = rTmp[sal_Int32(nSttPlus)]) && '|' != cChar)
        || ('+' != (cChar = rTmp[sal_Int32(nEndPlus) - 1]) && '|' != cChar))
        return false;

    SwTextFrameInfo aInfo( m_pCurTextFrame );

    TextFrameIndex n = nSttPlus;
    std::vector<sal_uInt16> aPosArr;

    while (n < TextFrameIndex(rTmp.getLength()))
    {
        switch (rTmp[sal_Int32(n)])
        {
        case '-':
        case '_':
        case '=':
        case ' ':
        case '\t':
            break;

        case '+':
        case '|':
            aPosArr.push_back( o3tl::narrowing<sal_uInt16>(aInfo.GetCharPos(n)) );
            break;

        default:
            return false;
        }
        if( ++n == nEndPlus )
            break;
    }

    if( 1 < aPosArr.size() )
    {
        // get the text node's alignment
        sal_uInt16 nColCnt = aPosArr.size() - 1;
        SwTwips nSttPos = aPosArr[ 0 ];
        sal_Int16 eHori;
        switch (m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust())
        {
        case SvxAdjust::Center:     eHori = text::HoriOrientation::CENTER;    break;
        case SvxAdjust::Right:      eHori = text::HoriOrientation::RIGHT;     break;

        default:
            if( nSttPos )
            {
                eHori = text::HoriOrientation::NONE;
                // then - as last - we need to add the current frame width into the array
                aPosArr.push_back( o3tl::narrowing<sal_uInt16>(m_pCurTextFrame->getFrameArea().Width()) );
            }
            else
                eHori = text::HoriOrientation::LEFT;
            break;
        }

        // then create a table that matches the character
        DelEmptyLine();
        // WARNING: rTmp may be deleted now, m_pCurTextFrame may be nullptr
        SwNodeIndex aIdx( m_aDelPam.GetPoint()->GetNode() );
        m_aDelPam.Move( fnMoveForward );
        m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ),
                           *m_aDelPam.GetPoint(), 1, nColCnt, eHori,
                           nullptr, &aPosArr );
        m_aDelPam.GetPoint()->Assign(aIdx);
    }
    return 1 < aPosArr.size();
}

OUString SwAutoFormat::DelLeadingBlanks( const OUString& rStr )
{
    sal_Int32 nL, n;
    for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[n] ); ++n )
        ;
    if( n ) // no Spaces
        return rStr.copy(n);
    return rStr;
}

OUString SwAutoFormat::DelTrailingBlanks( const OUString& rStr )
{
    sal_Int32 nL = rStr.getLength(), n = nL;
    if( !nL )
        return rStr;

    while( --n && IsSpace( rStr[ n ] )  )
        ;
    if( n+1 != nL ) // no Spaces
        return rStr.copy( 0, n+1 );
    return rStr;
}

sal_Int32 SwAutoFormat::GetLeadingBlanks( std::u16string_view aStr )
{
    size_t nL;
    size_t n;

    for( nL = aStr.size(), n = 0; n < nL && IsSpace( aStr[ n ] ); ++n )
        ;
    return n;
}

sal_Int32 SwAutoFormat::GetTrailingBlanks( std::u16string_view aStr )
{
    size_t nL = aStr.size(), n = nL;
    if( !nL )
        return 0;

    while( --n && IsSpace( aStr[ n ] )  )
        ;
    return ++n;
}

bool SwAutoFormat::IsFirstCharCapital(const SwTextFrame& rFrame) const
{
    const OUString& rText = rFrame.GetText();
    for (TextFrameIndex n(0),
                     nEnd(rText.getLength()); n < nEnd; ++n)
        if (!IsSpace(rText[sal_Int32(n)]))
        {
            auto const pair = rFrame.MapViewToModel(n);
            CharClass& rCC = GetCharClass( pair.first->GetSwAttrSet().
                                        GetLanguage().GetLanguage() );
            sal_Int32 nCharType = rCC.getCharacterType(rText, sal_Int32(n));
            return CharClass::isLetterType( nCharType ) &&
                   0 != ( i18n::KCharacterType::UPPER &
                                                    nCharType );
        }
    return false;
}

sal_uInt16
SwAutoFormat::GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos,
        OUString* pPrefix, OUString* pPostfix, OUString* pNumTypes ) const
{

    // check for 1.) / 1. / 1.1.1 / (1). / (1) / ...
    const OUString& rText = rFrame.GetText();
    sal_Int32 nPos(rPos);
    int eScan = NONE;

    sal_uInt16 nStart = 0;
    sal_uInt8 nDigitLvl = 0, nDigitCnt = 0;
    // count number of parenthesis to assure a sensible order is found
    sal_uInt16 nOpeningParentheses = 0;
    sal_uInt16 nClosingParentheses = 0;

    while (nPos < rText.getLength() && nDigitLvl < MAXLEVEL - 1)
    {
        auto const pair = rFrame.MapViewToModel(TextFrameIndex(nPos));
        CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage());
        const sal_Unicode cCurrentChar = rText[nPos];
        if( ('0' <= cCurrentChar &&  '9' >= cCurrentChar) ||
            (0xff10 <= cCurrentChar &&  0xff19 >= cCurrentChar) )
        {
            if( eScan & DELIM )
            {
                if( eScan & CHG )   // not if it starts with a number
                {
                    ++nDigitLvl;
                    if( pPostfix )
                        *pPostfix += "\x01";
                }

                if( pNumTypes )
                    *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC));

                eScan = eScan | CHG;
            }
            else if( pNumTypes && !(eScan & DIGIT) )
                *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC));

            eScan &= ~DELIM;        // remove Delim
            if( 0 != (eScan & ~CHG) && DIGIT != (eScan & ~CHG))
                return USHRT_MAX;

            eScan |= DIGIT;         // add Digit
            if( 3 == ++nDigitCnt )  // more than 2 numbers are not an enum anymore
                return USHRT_MAX;

            nStart *= 10;
            nStart += cCurrentChar <= '9' ? cCurrentChar - '0' : cCurrentChar - 0xff10;
        }
        else if( rCC.isAlpha( rText, nPos ) )
        {
            bool bIsUpper =
                0 != ( i18n::KCharacterType::UPPER &
                                        rCC.getCharacterType( rText, nPos ));
            sal_Unicode cLow = rCC.lowercase(rText, nPos, 1)[0], cNumTyp;
            int eTmpScan;

            // Roman numbers are "mdclxvi". Since we want to start numbering with c or d more often,
            // convert first to characters and later to roman numbers if needed.
            if( 256 > cLow  && strchr( "mdclxvi", cLow ) )
            {
                if( bIsUpper )
                {
                    cNumTyp = '0' + SVX_NUM_ROMAN_UPPER;
                    eTmpScan = UPPER_ROMAN;
                }
                else
                {
                    cNumTyp = '0' + SVX_NUM_ROMAN_LOWER;
                    eTmpScan = LOWER_ROMAN;
                }
            }
            else if( bIsUpper )
            {
                cNumTyp = '0' + SVX_NUM_CHARS_UPPER_LETTER;
                eTmpScan = UPPER_ALPHA;
            }
            else
            {
                cNumTyp = '0' + SVX_NUM_CHARS_LOWER_LETTER;
                eTmpScan = LOWER_ALPHA;
            }

            // Switch to roman numbers (only for c/d!)
            if( 1 == nDigitCnt && ( eScan & (UPPER_ALPHA|LOWER_ALPHA) ) &&
                ( 3 == nStart || 4 == nStart) && 256 > cLow &&
                strchr( "mdclxvi", cLow ) &&
                (( eScan & UPPER_ALPHA ) ? (eTmpScan & (UPPER_ALPHA|UPPER_ROMAN))
                                         : (eTmpScan & (LOWER_ALPHA|LOWER_ROMAN))) )
            {
                sal_Unicode c = '0';
                nStart = 3 == nStart ? 100 : 500;
                if( UPPER_ALPHA == eTmpScan )
                {
                    eTmpScan = UPPER_ROMAN;
                    c += SVX_NUM_ROMAN_UPPER;
                }
                else
                {
                    eTmpScan = LOWER_ROMAN;
                    c += SVX_NUM_ROMAN_LOWER;
                }

                eScan = (eScan & ~(UPPER_ALPHA|LOWER_ALPHA)) | eTmpScan;
                if( pNumTypes )
                    (*pNumTypes) = pNumTypes->replaceAt( pNumTypes->getLength() - 1, 1, rtl::OUStringChar(c) );
            }

            if( eScan & DELIM )
            {
                if( eScan & CHG )   // not if it starts with a number
                {
                    ++nDigitLvl;
                    if( pPostfix )
                        *pPostfix += "\x01";
                }

                if( pNumTypes )
                    *pNumTypes += OUStringChar(cNumTyp);
                eScan = eScan | CHG;
            }
            else if( pNumTypes && !(eScan & eTmpScan) )
                *pNumTypes += OUStringChar(cNumTyp);

            eScan &= ~DELIM;        // remove Delim

            // if another type is set, stop here
            if( 0 != ( eScan & ~CHG ) && eTmpScan != ( eScan & ~CHG ))
                return USHRT_MAX;

            if( eTmpScan & (UPPER_ALPHA | LOWER_ALPHA) )
            {
                // allow characters only if they appear once
                return USHRT_MAX;
            }
            else
            {
                // roman numbers, check if valid characters
                sal_uInt16 nVal;
                bool bError = false;
                switch( cLow )
                {
                case 'm':   nVal = 1000; goto CHECK_ROMAN_1;
                case 'd':   nVal =  500; goto CHECK_ROMAN_5;
                case 'c':   nVal =  100; goto CHECK_ROMAN_1;
                case 'l':   nVal =   50; goto CHECK_ROMAN_5;
                case 'x':   nVal =   10; goto CHECK_ROMAN_1;
                case 'v':   nVal =    5; goto CHECK_ROMAN_5;

CHECK_ROMAN_1:
                    {
                        int nMod5 = nStart % (nVal * 5);
                        int nLast = nStart % nVal;
                        int n10 = nVal / 10;

                        if( nMod5 == ((3 * nVal) + n10 ) ||
                            nMod5 == ((4 * nVal) + n10 ) ||
                            nLast == n10 )
                            nStart = o3tl::narrowing<sal_uInt16>(nStart + (n10 * 8));
                        else if( nMod5 == 0 ||
                                 nMod5 == (1 * nVal) ||
                                 nMod5 == (2 * nVal) )
                            nStart = nStart + nVal;
                        else
                            bError = true;
                    }
                    break;

CHECK_ROMAN_5:
                    {
                        if( ( nStart / nVal ) & 1 )
                            bError = true;
                        else
                        {
                            int nMod = nStart % nVal;
                            int n10 = nVal / 5;
                            if( n10 == nMod )
                                nStart = o3tl::narrowing<sal_uInt16>(nStart + (3 * n10));
                            else if( 0 == nMod )
                                nStart = nStart + nVal;
                            else
                                bError = true;
                        }
                    }
                    break;

                case 'i':
                        if( nStart % 5 >= 3 )
                            bError = true;
                        else
                            nStart += 1;
                        break;

                default:
                    bError = true;
                }

                if( bError )
                    return USHRT_MAX;
            }
            eScan |= eTmpScan;          // add Digit
            ++nDigitCnt;
        }
        else if( (256 > cCurrentChar &&
                 strchr( ".)(", cCurrentChar )) ||
                 0x3002 == cCurrentChar /* Chinese trad. dot */||
                 0xff0e == cCurrentChar /* Japanese dot */||
                 0xFF08 == cCurrentChar /* opening bracket Chin./Jap.*/||
                 0xFF09 == cCurrentChar )/* closing bracket Chin./Jap. */
        {
            if(cCurrentChar == '(' || cCurrentChar == 0xFF09)
                nOpeningParentheses++;
            else if(cCurrentChar == ')'|| cCurrentChar == 0xFF08)
                nClosingParentheses++;
            // only if no numbers were read until here
            if( pPrefix && !( eScan & ( NO_DELIM | CHG )) )
                *pPrefix += OUStringChar(rText[nPos]);
            else if( pPostfix )
                *pPostfix += OUStringChar(rText[nPos]);

            if( NO_DELIM & eScan )
            {
                eScan |= CHG;
                if( pPrefix )
                    *pPrefix += "\x01" + OUString::number( nStart );
            }
            eScan &= ~NO_DELIM;     // remove Delim
            eScan |= DELIM;         // add Digit
            nDigitCnt = 0;
            nStart = 0;
        }
        else
            break;
        ++nPos;
    }
    if (!( CHG & eScan ) || rPos == TextFrameIndex(nPos) ||
        nPos == rText.getLength() || !IsSpace(rText[nPos]) ||
        (nOpeningParentheses > nClosingParentheses))
        return USHRT_MAX;

    if( (NO_DELIM & eScan) && pPrefix )     // do not forget the last one
        *pPrefix += "\x01" + OUString::number( nStart );

    rPos = TextFrameIndex(nPos);
    return nDigitLvl;       // 0 .. 9 (MAXLEVEL - 1)
}

void SwAutoFormat::SetColl( sal_uInt16 nId, bool bHdLineOrText )
{
    m_aDelPam.DeleteMark();
    m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() );

    // keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes
    SfxItemSetFixed<
            RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE,
            RES_PARATR_ADJUST, RES_PARATR_ADJUST,
            RES_PARATR_TABSTOP, RES_PARATR_DROP,
            RES_BACKGROUND, RES_SHADOW>  aSet(m_pDoc->GetAttrPool());

    if (m_aDelPam.GetPoint()->GetNode().GetTextNode()->HasSwAttrSet())
    {
        aSet.Put(*m_aDelPam.GetPoint()->GetNode().GetTextNode()->GetpSwAttrSet());
        // take HeaderLine/TextBody only if centered or right aligned, otherwise only justification
        if( SvxAdjustItem const * pAdj = aSet.GetItemIfSet( RES_PARATR_ADJUST, false) )
        {
            SvxAdjust eAdj = pAdj->GetAdjust();
            if( bHdLineOrText ? (SvxAdjust::Right != eAdj &&
                                 SvxAdjust::Center != eAdj)
                              : SvxAdjust::Block != eAdj )
                aSet.ClearItem( RES_PARATR_ADJUST );
        }
    }

    m_pDoc->SetTextFormatCollByAutoFormat( *m_aDelPam.GetPoint(), nId, &aSet );
}

static bool HasSelBlanks(
        SwTextFrame const*const pStartFrame, TextFrameIndex & rStartIndex,
        SwTextFrame const*const pEndFrame, TextFrameIndex & rEndIndex)
{
    if (TextFrameIndex(0) < rEndIndex
        && rEndIndex < TextFrameIndex(pEndFrame->GetText().getLength())
        && ' ' == pEndFrame->GetText()[sal_Int32(rEndIndex) - 1])
    {
        --rEndIndex;
        return true;
    }
    if (rStartIndex < TextFrameIndex(pStartFrame->GetText().getLength())
        && ' ' == pStartFrame->GetText()[sal_Int32(rStartIndex)])
    {
        ++rStartIndex;
        return true;
    }
    return false;
}

bool SwAutoFormat::HasBreakAttr(const SwTextFrame& rTextFrame)
{
    const SfxItemSet *const pSet = rTextFrame.GetTextNodeFirst()->GetpSwAttrSet();
    if( !pSet )
        return false;

    const SvxFormatBreakItem* pBreakItem = pSet->GetItemIfSet( RES_BREAK, false );
    if( pBreakItem && SvxBreak::NONE != pBreakItem->GetBreak() )
        return true;

    const SwFormatPageDesc* pItem = pSet->GetItemIfSet( RES_PAGEDESC, false );
    if( pItem && pItem->GetPageDesc()
        && UseOnPage::NONE != pItem->GetPageDesc()->GetUseOn() )
        return true;
    return false;
}

/// Is there a dot at the end?
bool SwAutoFormat::IsSentenceAtEnd(const SwTextFrame & rTextFrame)
{
    const OUString& rStr = rTextFrame.GetText();
    sal_Int32 n = rStr.getLength();
    if( !n )
        return true;

    while( --n && IsSpace( rStr[ n ] ) )
        ;
    return '.' == rStr[ n ];
}

/// Delete beginning and/or end in a node
void SwAutoFormat::DeleteLeadingTrailingBlanks(bool bStart, bool bEnd)
{
    if( !(m_aFlags.bAFormatByInput
        ? m_aFlags.bAFormatByInpDelSpacesAtSttEnd
        : m_aFlags.bAFormatDelSpacesAtSttEnd) )
        return;

    // delete blanks at the end of the current and at the beginning of the next one
    m_aDelPam.DeleteMark();
    TextFrameIndex nPos(GetLeadingBlanks(m_pCurTextFrame->GetText()));
    if (bStart && TextFrameIndex(0) != nPos)
    {
        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
        m_aDelPam.SetMark();
        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
        DeleteSel( m_aDelPam );
        m_aDelPam.DeleteMark();
    }
    nPos = TextFrameIndex(GetTrailingBlanks(m_pCurTextFrame->GetText()));
    if (bEnd && TextFrameIndex(m_pCurTextFrame->GetText().getLength()) != nPos)
    {
        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
                TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
        m_aDelPam.SetMark();
        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
        DeleteSel( m_aDelPam );
        m_aDelPam.DeleteMark();
    }
}

namespace sw {

bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges,
        SwDoc & rDoc, SwPaM const& rDelPam)
{
    bool isNoRedline(true);
    SwRedlineTable::size_type tmp;
    IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess());
    if (!(rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete))
    {
        return isNoRedline;
    }
    rIDRA.GetRedline(*rDelPam.Start(), &tmp);
    SwPosition const* pCurrent(rDelPam.Start());
    for ( ; tmp < rIDRA.GetRedlineTable().size(); ++tmp)
    {
        SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]);
        if (*rDelPam.End() <= *pRedline->Start())
        {
            break;
        }
        if (*pRedline->End() <= *rDelPam.Start())
        {
            continue;
        }
        if (pRedline->GetType() == RedlineType::Delete)
        {
            assert(*pRedline->Start() != *pRedline->End());
            isNoRedline = false;
            if (*pCurrent < *pRedline->Start())
            {
                rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent));
                rRanges.back()->SetMark();
                *rRanges.back()->GetPoint() = *pRedline->Start();
            }
            pCurrent = pRedline->End();
        }
    }
    if (!isNoRedline && *pCurrent < *rDelPam.End())
    {
        rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent));
        rRanges.back()->SetMark();
        *rRanges.back()->GetPoint() = *rDelPam.End();
    }
    return isNoRedline;
}

} // namespace sw

void SwAutoFormat::DeleteSel(SwPaM & rDelPam)
{
    std::vector<std::shared_ptr<SwUnoCursor>> ranges; // need correcting cursor
    if (GetRanges(ranges, *m_pDoc, rDelPam))
    {
        DeleteSelImpl(rDelPam, rDelPam);
    }
    else
    {
        for (auto const& pCursor : ranges)
        {
            DeleteSelImpl(*pCursor, rDelPam);
        }
    }
}

void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect)
{
    if (m_aFlags.bWithRedlining || &rDelPam != &rPamToCorrect)
    {
        // Add to Shell-Cursor-Ring so that DelPam will be moved as well!
        SwPaM* pShCursor = m_pEditShell->GetCursor_();
        SwPaM aTmp( *m_pCurTextNd, 0, pShCursor );

        SwPaM* pPrev = rPamToCorrect.GetPrev();
        rPamToCorrect.GetRingContainer().merge( pShCursor->GetRingContainer() );

        m_pEditShell->DeleteSel(rDelPam, true);

        // and remove Pam again:
        SwPaM* p;
        SwPaM* pNext = &rPamToCorrect;
        do {
            p = pNext;
            pNext = p->GetNext();
            p->MoveTo( &rPamToCorrect );
        } while( p != pPrev );

        m_aNdIdx = aTmp.GetPoint()->GetNode();
        m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
        m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date
    }
    else
        m_pEditShell->DeleteSel(rDelPam, true);
}

bool SwAutoFormat::DeleteJoinCurNextPara(SwTextFrame const*const pNextFrame,
        bool const bIgnoreLeadingBlanks)
{
    // delete blanks at the end of the current and at the beginning of the next one
    m_aDelPam.DeleteMark();
    TextFrameIndex nTrailingPos(GetTrailingBlanks(m_pCurTextFrame->GetText()));

    SwTextFrame const*const pEndFrame(pNextFrame ? pNextFrame : m_pCurTextFrame);
    TextFrameIndex nLeadingPos(0);
    if (pNextFrame)
    {
        nLeadingPos = TextFrameIndex(
            bIgnoreLeadingBlanks ? 0 : GetLeadingBlanks(pNextFrame->GetText()));
    }
    else
    {
        nLeadingPos = TextFrameIndex(m_pCurTextFrame->GetText().getLength());
    }

    // Is there a Blank at the beginning or end?
    // Do not delete it, it will be inserted again.
    bool bHasBlnks = HasSelBlanks(m_pCurTextFrame, nTrailingPos, pEndFrame, nLeadingPos);

    *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTrailingPos);
    m_aDelPam.SetMark();
    *m_aDelPam.GetPoint() = pEndFrame->MapViewToModelPos(nLeadingPos);

    if( *m_aDelPam.GetPoint() != *m_aDelPam.GetMark() )
        DeleteSel( m_aDelPam );
    m_aDelPam.DeleteMark();
    // note: keep m_aDelPam point at insert pos. for clients

    return !bHasBlnks;
}

void SwAutoFormat::DelEmptyLine( bool bTstNextPara )
{
    SetRedlineText( STR_AUTOFMTREDL_DEL_EMPTY_PARA );
    // delete blanks in empty paragraph
    m_aDelPam.DeleteMark();
    *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
            TextFrameIndex(0));
    m_aDelPam.SetMark();

    m_aDelPam.GetMark()->Assign( m_pCurTextFrame->GetTextNodeFirst()->GetIndex() - 1 );
    SwTextNode* pTNd = m_aDelPam.GetMarkNode().GetTextNode();
    if( pTNd )
        // first use the previous text node
        m_aDelPam.GetMark()->SetContent(pTNd->GetText().getLength());
    else if( bTstNextPara )
    {
        // then try the next (at the beginning of a Doc, table cells, frames, ...)
        const SwTextNode* pNext = m_pCurTextFrame->GetMergedPara()
                    ? m_pCurTextFrame->GetMergedPara()->pLastNode
                    : m_pCurTextNd;
        m_aDelPam.GetMark()->Assign(pNext->GetIndex() + 1);
        pTNd = m_aDelPam.GetMarkNode().GetTextNode();
        if( pTNd )
        {
            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
                TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
        }
    }
    if( pTNd )
    {   // join with previous or next paragraph
        DeleteSel(m_aDelPam);
    }
    assert(m_aDelPam.GetPointNode().IsTextNode());
    assert(!m_aDelPam.HasMark());
    m_aDelPam.SetMark(); // mark remains at join position
    m_pCurTextFrame = GetFrame(*m_aDelPam.GetPointNode().GetTextNode());
    // replace until the end of the merged paragraph
    *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
        TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
    if (*m_aDelPam.GetPoint() != *m_aDelPam.GetMark())
    {   // tdf#137245 replace (not delete) to preserve any flys
        m_pDoc->getIDocumentContentOperations().ReplaceRange(m_aDelPam, u""_ustr, false);
    }

    m_aDelPam.DeleteMark();
    ClearRedlineText();
    // note: this likely has deleted m_pCurTextFrame - update it...
    m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
    m_pCurTextFrame = m_pCurTextNd ? GetFrame( *m_pCurTextNd ) : nullptr;
}

void SwAutoFormat::DelMoreLinesBlanks( bool bWithLineBreaks )
{
    if( !(m_aFlags.bAFormatByInput
        ? m_aFlags.bAFormatByInpDelSpacesBetweenLines
        : m_aFlags.bAFormatDelSpacesBetweenLines) )
        return;

    // delete all blanks on the left and right of the indentation
    m_aDelPam.DeleteMark();

    SwTextFrameInfo aFInfo( m_pCurTextFrame );
    std::vector<std::pair<TextFrameIndex, TextFrameIndex>> spaces;
    aFInfo.GetSpaces(spaces, !m_aFlags.bAFormatByInput || bWithLineBreaks);

    // tdf#123285 iterate backwards - delete invalidates following indexes
    for (auto iter = spaces.rbegin(); iter != spaces.rend(); ++iter)
    {
        auto & rSpaceRange(*iter);
        assert(rSpaceRange.first != rSpaceRange.second);
        bool const bHasBlanks = HasSelBlanks(
                m_pCurTextFrame, rSpaceRange.first,
                m_pCurTextFrame, rSpaceRange.second);
        if (rSpaceRange.first != rSpaceRange.second)
        {
            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.first);
            m_aDelPam.SetMark();
            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.second);
            DeleteSel(m_aDelPam);
            if (!bHasBlanks)
            {
                m_pDoc->getIDocumentContentOperations().InsertString(m_aDelPam, OUString(' '));
            }
            m_aDelPam.DeleteMark();
        }
    }
}

void SwAutoFormat::JoinPrevPara()
{
    m_aDelPam.DeleteMark();
    m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeFirst() );
    m_aDelPam.SetMark();

    m_aDelPam.GetPoint()->Adjust(SwNodeOffset(-1));
    SwTextNode* pTNd = m_aDelPam.GetPointNode().GetTextNode();
    if( pTNd )
    {
        // use the previous text node first
        m_aDelPam.GetPoint()->SetContent(pTNd->GetText().getLength());
        DeleteSel( m_aDelPam );
    }
    m_aDelPam.DeleteMark();
}

void SwAutoFormat::BuildIndent()
{
    SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_INDENT );

    // read all succeeding paragraphs that belong to this indentation
    bool bBreak = true;
    if( m_bMoreLines )
        DelMoreLinesBlanks( true );
    else
        bBreak = !IsFastFullLine(*m_pCurTextFrame)
                || IsBlanksInString(*m_pCurTextFrame)
                || IsSentenceAtEnd(*m_pCurTextFrame);
    SetColl( RES_POOLCOLL_TEXT_IDENT );
    if( !bBreak )
    {
        SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
        const SwTextFrame * pNextFrame = GetNextNode();
        if (pNextFrame && !m_bEnd)
        {
            do {
                bBreak = !IsFastFullLine(*pNextFrame)
                    || IsBlanksInString(*pNextFrame)
                    || IsSentenceAtEnd(*pNextFrame);
                if (DeleteJoinCurNextPara(pNextFrame))
                {
                    m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
                }
                if( bBreak )
                    break;
                pNextFrame = GetNextNode();
            }
            while (CanJoin(pNextFrame)
                && !CalcLevel(*pNextFrame));
        }
    }
    DeleteLeadingTrailingBlanks();
    AutoCorrect();
}

void SwAutoFormat::BuildTextIndent()
{
    SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT_INDENT);
    // read all succeeding paragraphs that belong to this indentation
    bool bBreak = true;
    if( m_bMoreLines )
        DelMoreLinesBlanks( true );
    else
        bBreak = !IsFastFullLine(*m_pCurTextFrame)
               || IsBlanksInString(*m_pCurTextFrame)
               || IsSentenceAtEnd(*m_pCurTextFrame);

    if( m_aFlags.bAFormatByInput )
    {
        const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAutoFormatLvl(
                static_cast<sal_uInt8>(CalcLevel(*m_pCurTextFrame)));
    }

    SetColl( RES_POOLCOLL_TEXT_MOVE );
    if( !bBreak )
    {
        SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
        const SwTextFrame * pNextFrame = GetNextNode();
        while (CanJoin(pNextFrame) &&
               CalcLevel(*pNextFrame))
        {
            bBreak = !IsFastFullLine(*pNextFrame)
                    || IsBlanksInString(*pNextFrame)
                    || IsSentenceAtEnd(*pNextFrame);
            if (DeleteJoinCurNextPara(pNextFrame))
            {
                m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
            }
            if( bBreak )
                break;
            pNextFrame = GetNextNode();
        }
    }
    DeleteLeadingTrailingBlanks();
    AutoCorrect();
}

void SwAutoFormat::BuildText()
{
    SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT );
    // read all succeeding paragraphs that belong to this text without indentation
    bool bBreak = true;
    if( m_bMoreLines )
        DelMoreLinesBlanks();
    else
        bBreak = !IsFastFullLine(*m_pCurTextFrame)
                || IsBlanksInString(*m_pCurTextFrame)
                || IsSentenceAtEnd(*m_pCurTextFrame);
    if( !bBreak )
    {
        SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
        const SwTextFrame * pNextFrame = GetNextNode();
        while (CanJoin(pNextFrame) &&
               !CalcLevel(*pNextFrame))
        {
            bBreak = !IsFastFullLine(*pNextFrame)
                    || IsBlanksInString(*pNextFrame)
                    || IsSentenceAtEnd(*pNextFrame);
            if (DeleteJoinCurNextPara(pNextFrame))
            {
                m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
            }
            if( bBreak )
                break;
            const SwTextFrame *const pCurrNode = pNextFrame;
            pNextFrame = GetNextNode();
            if (!pNextFrame || pCurrNode == pNextFrame)
                break;
        }
    }
    DeleteLeadingTrailingBlanks();
    AutoCorrect();
}

void SwAutoFormat::BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel )
{
    SetRedlineText( STR_AUTOFMTREDL_SET_NUMBER_BULLET );

    bool bBreak = true;

    // first, determine current indentation and frame width
    SwTwips nFrameWidth = m_pCurTextFrame->getFramePrintArea().Width();
    SwTwips nLeftTextPos;
    {
        TextFrameIndex nPos(0);
        while (nPos < TextFrameIndex(m_pCurTextFrame->GetText().getLength())
               && IsSpace(m_pCurTextFrame->GetText()[sal_Int32(nPos)]))
        {
            ++nPos;
        }

        SwTextFrameInfo aInfo( m_pCurTextFrame );
        nLeftTextPos = aInfo.GetCharPos(nPos);

        nLeftTextPos -= m_pCurTextFrame->GetTextNodeForParaProps()
                            ->GetSwAttrSet()
                            .GetTextLeftMargin()
                            .ResolveLeft(m_pCurTextFrame->GetTextNodeForParaProps()
                                             ->GetSwAttrSet()
                                             .GetFirstLineIndent(),
                                         /*metrics*/ {});
    }

    if( m_bMoreLines )
        DelMoreLinesBlanks();
    else
        bBreak = !IsFastFullLine(*m_pCurTextFrame)
                || IsBlanksInString(*m_pCurTextFrame)
                || IsSentenceAtEnd(*m_pCurTextFrame);
    bool bRTL = m_pEditShell->IsInRightToLeftText();

    const OUString sStrWithTrailingBlanks = DelLeadingBlanks(m_pCurTextFrame->GetText());
    bool bIsShortBullet = sStrWithTrailingBlanks == "* " || sStrWithTrailingBlanks == "- ";
    sal_uInt16 nMinLen = bIsShortBullet ? 1 : 2;

    // only delete blanks if something else (numbering or style) is going to be changed
    if (m_aFlags.bSetNumRule || !m_aFlags.bAFormatByInput || m_aFlags.bReplaceStyles)
        DeleteLeadingTrailingBlanks();

    bool bChgBullet = false, bChgEnum = false;
    TextFrameIndex nAutoCorrPos(0);

    // if numbering is set, get the current one
    SwNumRule aRule( m_pDoc->GetUniqueNumRuleName(),
                     // #i89178#
                     numfunc::GetDefaultPositionAndSpaceMode() );

    const SwNumRule* pCur = nullptr;
    if (m_aFlags.bSetNumRule)
    {
        pCur = m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule();
        if (pCur)
        {
            aRule = *pCur;
        }
    }

    // replace bullet character with defined one
    const OUString& rStr = bIsShortBullet ? sStrWithTrailingBlanks : m_pCurTextFrame->GetText();
    TextFrameIndex nTextStt(0);
    const sal_Unicode* pFndBulletChr = nullptr;
    if (m_aFlags.bChgEnumNum && nMinLen < rStr.getLength())
        pFndBulletChr = StrChr(pBulletChar, rStr[sal_Int32(nTextStt)]);
    if (nullptr != pFndBulletChr && IsSpace(rStr[sal_Int32(nTextStt) + 1]))
    {
        if( m_aFlags.bAFormatByInput )
        {
            if( m_aFlags.bSetNumRule)
            {
                SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
                                            RES_POOLCHR_BULLET_LEVEL );
                bChgBullet = true;
                // Was the format already somewhere adjusted?
                if( !aRule.GetNumFormat( nLvl ) )
                {
                    int nBulletPos = pFndBulletChr - pBulletChar;
                    sal_UCS4 cBullChar;
                    const vcl::Font* pBullFnt( nullptr );
                    if( nBulletPos < cnPosEnDash )
                    {
                        cBullChar = m_aFlags.cBullet;
                        pBullFnt = &m_aFlags.aBulletFont;
                    }
                    else
                    {
                        cBullChar = nBulletPos < cnPosEmDash
                                        ? cStarSymbolEnDash
                                        : cStarSymbolEmDash;
                        // #i63395#
                        // Only apply user defined default bullet font
                        if ( numfunc::IsDefBulletFontUserDefined() )
                        {
                            pBullFnt = &numfunc::GetDefBulletFont();
                        }
                    }

                    sal_Int32 nAbsPos = lBulletIndent;
                    SwTwips nSpaceSteps = nLvl
                                            ? nLeftTextPos / nLvl
                                            : lBulletIndent;
                    for( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nAbsPos = nAbsPos + nSpaceSteps )
                    {
                        SwNumFormat aFormat( aRule.Get( n ) );
                        aFormat.SetBulletFont( pBullFnt );
                        aFormat.SetBulletChar( cBullChar );
                        aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
                        // #i93908# clear suffix for bullet lists
                        aFormat.SetListFormat(u""_ustr, u""_ustr, n);
                        aFormat.SetFirstLineOffset( lBulletFirstLineOffset );
                        aFormat.SetAbsLSpace( nAbsPos );
                        if( !aFormat.GetCharFormat() )
                            aFormat.SetCharFormat( pCFormat );
                        if( bRTL )
                            aFormat.SetNumAdjust( SvxAdjust::Right );

                        aRule.Set( n, aFormat );

                        if( n == nLvl &&
                            nFrameWidth < ( nSpaceSteps * MAXLEVEL ) )
                            nSpaceSteps = ( nFrameWidth - nLeftTextPos ) /
                                                ( MAXLEVEL - nLvl );
                    }
                }
            }
        }
        else
        {
            bChgBullet = true;
            SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_BULLET_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 )) );
        }
    }
    else
    {
        // Then it is a numbering

        //JP 21.11.97: The NumLevel is either the DigitLevel or, if the latter is not existent or 0,
        //             it is determined by the indentation level.

        OUString aPostfix, aPrefix, aNumTypes;
        nDigitLevel = GetDigitLevel(*m_pCurTextFrame, nTextStt,
                                            &aPrefix, &aPostfix, &aNumTypes);
        if (USHRT_MAX != nDigitLevel)
        {
            bChgEnum = true;

            // Level 0 and Indentation, determine level by left indentation and default NumIndent
            if( !nDigitLevel && nLeftTextPos )
                nLvl = std::min( sal_uInt16( nLeftTextPos / lNumberIndent ),
                            sal_uInt16( MAXLEVEL - 1 ) );
            else
                nLvl = nDigitLevel;
        }

        if( bChgEnum && m_aFlags.bSetNumRule )
        {
            if( !pCur )         // adjust NumRule if it is new
            {
                SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
                                            RES_POOLCHR_NUM_LEVEL );

                sal_Int32 nPrefixIdx{ 0 };
                if( !nDigitLevel )
                {
                    SwNumFormat aFormat( aRule.Get( nLvl ) );
                    const OUString sPrefix = aPrefix.getToken(0, u'\x0001', nPrefixIdx);
                    aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx ))));
                    aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001'), nLvl);
                    aFormat.SetIncludeUpperLevels( 0 );

                    if( !aFormat.GetCharFormat() )
                        aFormat.SetCharFormat( pCFormat );

                    if( !aNumTypes.isEmpty() )
                        aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ 0 ] - '0'));

                    if( bRTL )
                        aFormat.SetNumAdjust( SvxAdjust::Right );
                    aRule.Set( nLvl, aFormat );
                }
                else
                {
                    auto const nSpaceSteps = nLvl ? nLeftTextPos / nLvl : 0;
                    sal_uInt16 n;
                    sal_Int32 nPostfixIdx{ 0 };
                    for( n = 0; n <= nLvl; ++n )
                    {
                        SwNumFormat aFormat( aRule.Get( n ) );

                        const OUString sPrefix = n ? u""_ustr : aPrefix.getToken(0, u'\x0001', nPrefixIdx);
                        aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx )) ));
                        aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001', nPostfixIdx), n);
                        aFormat.SetIncludeUpperLevels( MAXLEVEL );
                        if( n < aNumTypes.getLength() )
                            aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ n ] - '0'));

                        aFormat.SetAbsLSpace( nSpaceSteps * n
                                            + lNumberIndent );

                        if( !aFormat.GetCharFormat() )
                            aFormat.SetCharFormat( pCFormat );
                        if( bRTL )
                            aFormat.SetNumAdjust( SvxAdjust::Right );

                        aRule.Set( n, aFormat );
                    }

                    // Does it fit completely into the frame?
                    bool bDefStep = nFrameWidth < (nSpaceSteps * MAXLEVEL);
                    for( ; n < MAXLEVEL; ++n )
                    {
                        SwNumFormat aFormat( aRule.Get( n ) );
                        aFormat.SetIncludeUpperLevels( MAXLEVEL );
                        if( bDefStep )
                            aFormat.SetAbsLSpace( nLeftTextPos +
                                SwNumRule::GetNumIndent(static_cast<sal_uInt8>(n-nLvl)));
                        else
                            aFormat.SetAbsLSpace( nSpaceSteps * n
                                                + lNumberIndent );
                        aRule.Set( n, aFormat );
                    }
                }
            }
        }
        else if( !m_aFlags.bAFormatByInput )
            SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) ));
        else
            bChgEnum = false;
    }

    if ( bChgEnum || bChgBullet )
    {
        m_aDelPam.DeleteMark();
        m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() );

        if( m_aFlags.bSetNumRule )
        {
            if( m_aFlags.bAFormatByInput )
            {
                m_aDelPam.SetMark();
                SwTextFrame const*const pNextFrame = GetNextNode(false);
                if (pNextFrame)
                {
                    m_aDelPam.GetMark()->Assign( *pNextFrame->GetTextNodeForParaProps() );
                    m_aDelPam.GetMarkNode().GetTextNode()->SetAttrListLevel( nLvl );
                }
            }

            const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAttrListLevel(nLvl);

            // start new list
            m_pDoc->SetNumRule(m_aDelPam, aRule, SwDoc::SetNumRuleMode::CreateNewList, m_pEditShell->GetLayout());
            m_aDelPam.DeleteMark();

            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
        }
        else
        {
            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
                        bChgEnum ? nTextStt : TextFrameIndex(0));
        }
        m_aDelPam.SetMark();

        if ( bChgBullet )
            nTextStt += TextFrameIndex(bIsShortBullet ? 1 : 2);

        while (!bIsShortBullet && nTextStt < TextFrameIndex(rStr.getLength()) && IsSpace(rStr[sal_Int32(nTextStt)]))
            nTextStt++;

        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTextStt);
        DeleteSel( m_aDelPam );

        if( !m_aFlags.bSetNumRule )
        {
            OUString sChgStr('\t');
            if( bChgBullet )
                sChgStr = OUString(&m_aFlags.cBullet, 1) + sChgStr;
            m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, sChgStr );

            SfxItemSet aSet( m_pDoc->GetAttrPool(), aTextNodeSetRange );
            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
            assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps());
            if( bChgBullet )
            {
                m_aDelPam.SetMark();
                *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(1));
                SetAllScriptItem( aSet,
                     SvxFontItem( m_aFlags.aBulletFont.GetFamilyTypeMaybeAskConfig(),
                                  m_aFlags.aBulletFont.GetFamilyName(),
                                  m_aFlags.aBulletFont.GetStyleName(),
                                  m_aFlags.aBulletFont.GetPitchMaybeAskConfig(),
                                  m_aFlags.aBulletFont.GetCharSet(),
                                  RES_CHRATR_FONT ) );
                m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
                m_aDelPam.DeleteMark();
                nAutoCorrPos = TextFrameIndex(2);
                aSet.ClearItem();
            }
            SvxTabStopItem aTStops( RES_PARATR_TABSTOP );
            aTStops.Insert( SvxTabStop( 0 ) );
            aSet.Put( aTStops );
            assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps());
            m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
        }
    }

    if( bBreak )
    {
        AutoCorrect( nAutoCorrPos );       /* Offset due to Bullet + Tab */
        return;
    }

    const SwTextFrame * pNextFrame = GetNextNode();
    while (CanJoin(pNextFrame)
            && nLvl == CalcLevel(*pNextFrame))
    {
        SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
        bBreak = !IsFastFullLine(*pNextFrame)
                || IsBlanksInString(*pNextFrame)
                || IsSentenceAtEnd(*pNextFrame);
        if (DeleteJoinCurNextPara(pNextFrame))
        {
            m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
        }
        if( bBreak )
            break;
        const SwTextFrame *const pCurrNode = pNextFrame;
        pNextFrame = GetNextNode();
        if (!pNextFrame || pCurrNode == pNextFrame)
            break;
    }
    DeleteLeadingTrailingBlanks( false );
    AutoCorrect( nAutoCorrPos );
}

void SwAutoFormat::BuildNegIndent( SwTwips nSpaces )
{
    SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_NEG_INDENT );
    // Test of contraposition (n words, divided by spaces/tabs, with same indentation in 2nd line)

    // read all succeeding paragraphs that belong to this enumeration
    bool bBreak = true;
    TextFrameIndex nSpacePos(0);
    const sal_Int32 nTextPos = GetBigIndent( nSpacePos );
    if( m_bMoreLines )
        DelMoreLinesBlanks( true );
    else
        bBreak = !IsFastFullLine(*m_pCurTextFrame)
            || (!nTextPos && IsBlanksInString(*m_pCurTextFrame))
            || IsSentenceAtEnd(*m_pCurTextFrame);

    SetColl( o3tl::narrowing<sal_uInt16>( nTextPos
                ? RES_POOLCOLL_CONFRONTATION
                : RES_POOLCOLL_TEXT_NEGIDENT ) );

    if( nTextPos )
    {
        const OUString& rStr = m_pCurTextFrame->GetText();
        bool bInsTab = true;

        if ('\t' == rStr[sal_Int32(nSpacePos) + 1]) // leave tab alone
        {
            --nSpacePos;
            bInsTab = false;
        }

        TextFrameIndex nSpaceStt = nSpacePos;
        while (nSpaceStt && IsSpace(rStr[sal_Int32(--nSpaceStt)]))
            ;
        ++nSpaceStt;

        if (bInsTab && '\t' == rStr[sal_Int32(nSpaceStt)]) // leave tab alone
        {
            ++nSpaceStt;
            bInsTab = false;
        }

        m_aDelPam.DeleteMark();
        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nSpacePos);

        // delete old Spaces, etc.
        if( nSpaceStt < nSpacePos )
        {
            m_aDelPam.SetMark();
            *m_aDelPam.GetMark() = m_pCurTextFrame->MapViewToModelPos(nSpaceStt);
            DeleteSel( m_aDelPam );
            if( bInsTab )
            {
                m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString('\t') );
            }
        }
    }

    if( !bBreak )
    {
        SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
        SwTextFrameInfo aFInfo( m_pCurTextFrame );
        const SwTextFrame * pNextFrame = GetNextNode();
        while (CanJoin(pNextFrame) &&
                20 < std::abs( static_cast<tools::Long>(nSpaces - aFInfo.SetFrame(
                                EnsureFormatted(*pNextFrame)).GetLineStart()) )
            )
        {
            bBreak = !IsFastFullLine(*pNextFrame)
                   || IsBlanksInString(*pNextFrame)
                   || IsSentenceAtEnd(*pNextFrame);
            if (DeleteJoinCurNextPara(pNextFrame))
            {
                m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
            }
            if( bBreak )
                break;
            pNextFrame = GetNextNode();
        }
    }
    DeleteLeadingTrailingBlanks();
    AutoCorrect();
}

void SwAutoFormat::BuildHeadLine( sal_uInt16 nLvl )
{
    if( m_aFlags.bWithRedlining )
    {
        OUString sText(SwViewShell::GetShellRes()->GetAutoFormatNameLst()[
                                    STR_AUTOFMTREDL_SET_TMPL_HEADLINE ] );
        sText = sText.replaceAll( "$(ARG1)", OUString::number( nLvl + 1 ) );
        m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText );
    }

    SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true );
    if( m_aFlags.bAFormatByInput )
    {
        SwTextFormatColl& rNxtColl = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetNextTextFormatColl();

        JoinPrevPara();

        DeleteLeadingTrailingBlanks( true, false );
        const SwTextFrame* pNextFrame = GetNextNode(false);
        if (pNextFrame->GetNext())
        {
            (void)DeleteJoinCurNextPara(pNextFrame, true);
            pNextFrame = GetNextNode(false);
        }
        m_aDelPam.DeleteMark();
        m_aDelPam.GetPoint()->Assign( *pNextFrame->GetTextNodeForParaProps() );
        m_pDoc->SetTextFormatColl( m_aDelPam, &rNxtColl );
    }
    else
    {
        DeleteLeadingTrailingBlanks();
        AutoCorrect();
    }
}

/// Start autocorrection for the current TextNode
void SwAutoFormat::AutoCorrect(TextFrameIndex nPos)
{
    SvxAutoCorrect* pATst = SvxAutoCorrCfg::Get().GetAutoCorrect();
    ACFlags aSvxFlags = pATst->GetFlags( );
    bool bReplaceQuote( aSvxFlags & ACFlags::ChgQuotes );
    bool bReplaceSglQuote( aSvxFlags & ACFlags::ChgSglQuotes );

    if( m_aFlags.bAFormatByInput ||
        (!m_aFlags.bAutoCorrect && !bReplaceQuote && !bReplaceSglQuote &&
        !m_aFlags.bCapitalStartSentence && !m_aFlags.bCapitalStartWord &&
        !m_aFlags.bChgOrdinalNumber && !m_aFlags.bTransliterateRTL &&
        !m_aFlags.bChgToEnEmDash && !m_aFlags.bSetINetAttr &&
        !m_aFlags.bChgWeightUnderl && !m_aFlags.bAddNonBrkSpace) )
        return;

    const OUString* pText = &m_pCurTextFrame->GetText();
    if (TextFrameIndex(pText->getLength()) <= nPos)
        return;

    bool bGetLanguage = m_aFlags.bChgOrdinalNumber || m_aFlags.bTransliterateRTL ||
                        m_aFlags.bChgToEnEmDash || m_aFlags.bSetINetAttr ||
                        m_aFlags.bCapitalStartWord || m_aFlags.bCapitalStartSentence ||
                        m_aFlags.bAddNonBrkSpace;

    m_aDelPam.DeleteMark();
    *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));

    SwAutoCorrDoc aACorrDoc( *m_pEditShell, m_aDelPam );

    SwTextFrameInfo aFInfo( nullptr );

    TextFrameIndex nSttPos, nLastBlank = nPos;
    bool bFirst = m_aFlags.bCapitalStartSentence, bFirstSent = bFirst;
    sal_Unicode cChar = 0;
    bool bNbspRunNext = false;

    CharClass& rAppCC = GetAppCharClass();

    do {
        while (nPos < TextFrameIndex(pText->getLength())
                && IsSpace(cChar = (*pText)[sal_Int32(nPos)]))
            ++nPos;
        if (nPos == TextFrameIndex(pText->getLength()))
            break;      // that's it

        if( ( ( bReplaceQuote && '\"' == cChar ) ||
              ( bReplaceSglQuote && '\'' == cChar ) ) &&
            (!nPos || ' ' == (*pText)[sal_Int32(nPos)-1]))
        {

            // note: special case symbol fonts !!!
            if( !aFInfo.GetFrame() )
                aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
            if( !aFInfo.IsBullet( nPos ))
            {
                SetRedlineText( STR_AUTOFMTREDL_TYPO );
                *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
                bool bSetHardBlank = false;

                OUString sReplace( pATst->GetQuote( aACorrDoc,
                                    sal_Int32(nPos), cChar, true ));

                m_aDelPam.SetMark();
                m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 );
                if( 2 == sReplace.getLength() && ' ' == sReplace[ 1 ])
                {
                    sReplace = sReplace.copy( 0, 1 );
                    bSetHardBlank = true;
                }
                m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );

                if( m_aFlags.bWithRedlining )
                {
                    m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                    m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                    m_pCurTextFrame = GetFrame( *m_pCurTextNd );
                    pText = &m_pCurTextFrame->GetText();
                    m_aDelPam.SetMark();
                    aFInfo.SetFrame( nullptr );
                }

                nPos += TextFrameIndex(sReplace.getLength() - 1);
                m_aDelPam.DeleteMark();
                if( bSetHardBlank )
                {
                    m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
                    ++nPos;
                }
            }
        }

        bool bCallACorr = false;
        int bBreak = 0;
        if (nPos && IsSpace((*pText)[sal_Int32(nPos) - 1]))
            nLastBlank = nPos;
        for (nSttPos = nPos; !bBreak && nPos < TextFrameIndex(pText->getLength()); ++nPos)
        {
            cChar = (*pText)[sal_Int32(nPos)];
            switch (cChar)
            {
            case '\"':
            case '\'':
                if( ( cChar == '\"' && bReplaceQuote ) || ( cChar == '\'' && bReplaceSglQuote ) )
                {
                    // consider Symbolfonts!
                    if( !aFInfo.GetFrame() )
                        aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
                    if( !aFInfo.IsBullet( nPos ))
                    {
                        SetRedlineText( STR_AUTOFMTREDL_TYPO );
                        bool bSetHardBlank = false;
                        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
                        OUString sReplace( pATst->GetQuote( aACorrDoc,
                                            sal_Int32(nPos), cChar, false) );

                        if( 2 == sReplace.getLength() && ' ' == sReplace[ 0 ])
                        {
                            sReplace = sReplace.copy( 1 );
                            bSetHardBlank = true;
                        }

                        m_aDelPam.SetMark();
                        m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 );
                        m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );

                        if( m_aFlags.bWithRedlining )
                        {
                            m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                            m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                            m_pCurTextFrame = GetFrame( *m_pCurTextNd );
                            pText = &m_pCurTextFrame->GetText();
                            m_aDelPam.SetMark();
                            m_aDelPam.DeleteMark();
                            aFInfo.SetFrame( nullptr );
                        }

                        nPos += TextFrameIndex(sReplace.getLength() - 1);
                        m_aDelPam.DeleteMark();

                        if( bSetHardBlank )
                        {
                            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
                            m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
                            ++nPos;
                            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
                        }
                    }
                }
                break;
            case '*':
            case '_':
                if( m_aFlags.bChgWeightUnderl )
                {
                    // consider Symbolfonts!
                    if( !aFInfo.GetFrame() )
                        aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
                    if( !aFInfo.IsBullet( nPos ))
                    {
                        SetRedlineText( '*' == cChar
                                            ? STR_AUTOFMTREDL_BOLD
                                            : STR_AUTOFMTREDL_UNDER );

                        sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0;
                        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);

                        if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos)))
                        {
                            if( m_aFlags.bWithRedlining )
                            {
                                m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                                m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                                m_pCurTextFrame = GetFrame( *m_pCurTextNd );
                                pText = &m_pCurTextFrame->GetText();
                                m_aDelPam.SetMark();
                                m_aDelPam.DeleteMark();
                                aFInfo.SetFrame( nullptr );
                            }
                            //#125102# in case of the mode RedlineFlags::ShowDelete the ** are still contained in pText
                            if (m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete)
                            {
                                nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint())
                                       - TextFrameIndex(1);
                                bBreak++;
                            }
                            // Was a character deleted before starting?
                            if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1])
                                --nSttPos;
                        }
                    }
                }
                break;
            case '/':
                if ( m_aFlags.bAddNonBrkSpace )
                {
                    LanguageType eLang = bGetLanguage
                        ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
                        : LANGUAGE_SYSTEM;

                    SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
                    if (sal_Int32 nUpdatedPos = pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); nUpdatedPos >= 0)
                    {
                        nPos = TextFrameIndex(nUpdatedPos);
                        break;
                    }
                }
                [[fallthrough]];
            case '-':
                if (m_aFlags.bChgWeightUnderl)
                {
                    // consider Symbolfonts!
                    if (!aFInfo.GetFrame())
                        aFInfo.SetFrame(GetFrame(*m_pCurTextNd));
                    if (!aFInfo.IsBullet(nPos))
                    {
                        SetRedlineText('/' == cChar ? STR_AUTOFMTREDL_ITALIC : STR_AUTOFMTREDL_STRIKETHROUGH);

                        sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0;
                        *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);

                        if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos)))
                        {
                            if (m_aFlags.bWithRedlining)
                            {
                                m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                                m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                                m_pCurTextFrame = GetFrame(*m_pCurTextNd);
                                pText = &m_pCurTextFrame->GetText();
                                m_aDelPam.SetMark();
                                m_aDelPam.DeleteMark();
                                aFInfo.SetFrame(nullptr);
                            }
                            //#125102# in case of the mode RedlineFlags::ShowDelete the // or -- are still contained in pText
                            if (m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete)
                            {
                                nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint())
                                       - TextFrameIndex(1);
                                bBreak++;
                            }
                            // Was a character deleted before starting?
                            if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1])
                                --nSttPos;
                        }
                    }
                }
                break;

            case '.':
            case '!':
            case '?':
                if( m_aFlags.bCapitalStartSentence )
                    bFirstSent = true;
                [[fallthrough]];
            default:
                if (!(rAppCC.isBase(*pText, sal_Int32(nPos))
                        || '/' == cChar )) //  '/' should not be a word separator (e.g. '1/2' needs to be handled as one word for replacement)
                {
                    --nPos;     // revert ++nPos which was decremented in for loop
                    ++bBreak;
                }
                break;
            }
        }

        if( nPos == nSttPos )
        {
            if (++nPos == TextFrameIndex(pText->getLength()))
                bCallACorr = true;
        }
        else
            bCallACorr = true;

        if( bCallACorr )
        {
            *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
            SetRedlineText( STR_AUTOFMTREDL_USE_REPLACE );

            LanguageType eLang = bGetLanguage
                    ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
                    : LANGUAGE_SYSTEM;

            if( m_bIsRightToLeft && m_aFlags.bTransliterateRTL && eLang == LANGUAGE_HUNGARIAN &&
                SetRedlineText( STR_AUTOFMTREDL_TRANSLITERATE_RTL ) &&
                aACorrDoc.TransliterateRTLWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), /*bApply=*/true))
            {
                nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());

                if( m_aFlags.bWithRedlining )
                {
                    m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                    m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                    m_pCurTextFrame = GetFrame( *m_pCurTextNd );
                    pText = &m_pCurTextFrame->GetText();
                    m_aDelPam.SetMark();
                    m_aDelPam.DeleteMark();
                }

                continue;       // do not check further
            }

            if( m_aFlags.bAutoCorrect &&
                aACorrDoc.ChgAutoCorrWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), *pATst, nullptr) )
            {
                nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
                if( m_aFlags.bWithRedlining )
                {
                    m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                    m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                    m_pCurTextFrame = GetFrame( *m_pCurTextNd );
                    pText = &m_pCurTextFrame->GetText();
                    m_aDelPam.SetMark();
                    m_aDelPam.DeleteMark();
                }

                continue;       // do not check further
            }

            if ( m_aFlags.bAddNonBrkSpace && nPos < TextFrameIndex(pText->getLength()) )
            {
                SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
                if (sal_Int32 nUpdatedPos = pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); nUpdatedPos >= 0)
                    nPos = TextFrameIndex(nUpdatedPos);
            }

            if( ( m_aFlags.bChgOrdinalNumber &&
                    SetRedlineText( STR_AUTOFMTREDL_ORDINAL ) &&
                    pATst->FnChgOrdinalNumber(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) ||
                ( m_aFlags.bChgToEnEmDash &&
                    SetRedlineText( STR_AUTOFMTREDL_DASH ) &&
                    pATst->FnChgToEnEmDash(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) ||
                ( m_aFlags.bSetINetAttr &&
                    (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) &&
                    SetRedlineText( STR_AUTOFMTREDL_DETECT_URL ) &&
                    pATst->FnSetINetAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)) ||
                ( m_aFlags.bSetDOIAttr &&
                    (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) &&
                    SetRedlineText( STR_AUTOFMTREDL_DETECT_DOI ) &&
                    pATst->FnSetDOIAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)))
            {
                nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
            }
            else
            {
                // two capital letters at the beginning of a word?
                if( m_aFlags.bCapitalStartWord )
                {
                    SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_WORD );
                    pATst->FnCapitalStartWord(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang);
                }
                // capital letter at the beginning of a sentence?
                if( m_aFlags.bCapitalStartSentence && bFirst )
                {
                    SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_SENT );
                    pATst->FnCapitalStartSentence(aACorrDoc, *pText, true, sal_Int32(nSttPos), sal_Int32(nPos), eLang);
                }

                bFirst = bFirstSent;
                bFirstSent = false;

                if( m_aFlags.bWithRedlining )
                {
                    m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
                    m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
                    m_pCurTextFrame = GetFrame( *m_pCurTextNd );
                    pText = &m_pCurTextFrame->GetText();
                    m_aDelPam.SetMark();
                    m_aDelPam.DeleteMark();
                }
            }
        }
    }
    while (nPos < TextFrameIndex(pText->getLength()));
    ClearRedlineText();
}

SwAutoFormat::SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags,
                            SwNode const * pSttNd, SwNode const * pEndNd )
    : m_aFlags(std::move( aFlags )),
    m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ),
    m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), SwNodeOffset(+1) ),
    m_aEndNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfContent() ),
    m_pEditShell( pEdShell ),
    m_pDoc( pEdShell->GetDoc() ),
    m_pCurTextNd( nullptr ), m_pCurTextFrame( nullptr ),
    m_nRedlAutoFormatSeqId( 0 )
{
    OSL_ENSURE( (pSttNd && pEndNd) || (!pSttNd && !pEndNd),
            "Got no area" );

    if( m_aFlags.bSetNumRule && !m_aFlags.bAFormatByInput )
        m_aFlags.bSetNumRule = false;

    SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
    bool bReplaceStyles = m_aFlags.bAFormatByInput ? m_aFlags.bReplaceStylesByInput : m_aFlags.bReplaceStyles;

    const SwTextFrame * pNextFrame = nullptr;
    bool bNxtEmpty = false;
    bool bNxtAlpha = false;
    sal_uInt16 nNxtLevel = 0;
    bool bEmptyLine;

    // set area for autoformatting
    if( pSttNd )
    {
        assert(pEndNd);
        m_aNdIdx = *pSttNd;
        // for GoNextPara, one paragraph prior to that
        sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
        m_aEndNdIdx = *pEndNd;
        sw::GotoNextLayoutTextFrame(m_aEndNdIdx, m_pEditShell->GetLayout());

        // check the previous TextNode
        SwTextFrame const*const pPrevFrame = m_aNdIdx.GetNode().GetTextNode()
            ? static_cast<SwTextFrame const*>(m_aNdIdx.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout()))
            : nullptr;
        bEmptyLine = !pPrevFrame
                    || IsEmptyLine(*pPrevFrame)
                    || IsNoAlphaLine(*pPrevFrame);
    }
    else
        bEmptyLine = true;      // at document beginning

    m_bEnd = false;

    // set value for percentage display
    m_nEndNdIdx = m_aEndNdIdx.GetIndex();

    if( !m_aFlags.bAFormatByInput )
    {
        ::StartProgress( STR_STATSTR_AUTOFORMAT, sal_Int32(m_aNdIdx.GetIndex()),
                         sal_Int32(m_nEndNdIdx),
                         m_pDoc->GetDocShell() );
    }

    RedlineFlags eRedlMode = m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
    if( m_aFlags.bWithRedlining )
    {
        m_pDoc->SetAutoFormatRedline( true );
        eRedlMode = RedlineFlags::On | (eOldMode & RedlineFlags::ShowMask);
    }
    else
      eRedlMode = RedlineFlags::Ignore | (eOldMode & RedlineFlags::ShowMask);
    m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode );

    // save undo state (might be turned off)
    bool const bUndoState = m_pDoc->GetIDocumentUndoRedo().DoesUndo();

    // If multiple lines, then do not merge with next paragraph
    m_bMoreLines = false;

    sal_uInt16 nLastCalcHeadLvl = 0;
    sal_uInt16 nLastHeadLvl = USHRT_MAX;
    sal_uInt16 nLevel = 0;
    sal_uInt16 nDigitLvl = 0;

    // set defaults
    SwTextFrameInfo aFInfo( nullptr );

    enum Format_Status
    {
        READ_NEXT_PARA, // -> ISEND, TST_EMPTY_LINE
        TST_EMPTY_LINE, // -> READ_NEXT_PARA, TST_ALPHA_LINE
        TST_ALPHA_LINE, // -> READ_NEXT_PARA, GET_ALL_INFO, TST_ENUMERIC, IS_END
        GET_ALL_INFO,   // -> READ_NEXT_PARA, IS_ONE_LINE, TST_ENUMERIC, HAS_FMTCOLL
        IS_ONE_LINE,    // -> READ_NEXT_PARA, TST_ENUMERIC
        TST_ENUMERIC,   // -> READ_NEXT_PARA, TST_IDENT, TST_NEG_IDENT
        TST_IDENT,      // -> READ_NEXT_PARA, TST_TXT_BODY
        TST_NEG_IDENT,  // -> READ_NEXT_PARA, TST_TXT_BODY
        TST_TXT_BODY,   // -> READ_NEXT_PARA
        HAS_FMTCOLL,    // -> READ_NEXT_PARA
        IS_END
    } eStat;

    // This is the automat for autoformatting
    eStat = READ_NEXT_PARA;
    while( !m_bEnd )
    {
        switch( eStat )
        {
        case READ_NEXT_PARA:
            {
                GoNextPara();
                eStat = m_bEnd ? IS_END : TST_EMPTY_LINE;
            }
            break;

        case TST_EMPTY_LINE:
            assert(m_pCurTextFrame);
            if (IsEmptyLine(*m_pCurTextFrame))
            {
                if (m_aFlags.bDelEmptyNode && !HasObjects(*m_pCurTextFrame))
                {
                    bEmptyLine = true;
                    SwNodeOffset nOldCnt = m_pDoc->GetNodes().Count();
                    DelEmptyLine();
                    // Was there really a deletion of a node?
                    if( nOldCnt != m_pDoc->GetNodes().Count() )
                    {
                        // do not skip the next paragraph
                        sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
                    }
                }
                eStat = READ_NEXT_PARA;
            }
            else
                eStat = TST_ALPHA_LINE;
            break;

        case TST_ALPHA_LINE:
            assert(m_pCurTextFrame);
            if (IsNoAlphaLine(*m_pCurTextFrame))
            {
                // recognize a table definition +---+---+
                if( m_aFlags.bAFormatByInput && m_aFlags.bCreateTable && DoTable() )
                {
                    //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor after AutoFormat!
                    pEdShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
                    static_cast<SwPaM&>(*pEdShell->GetCursor()) = m_aDelPam;
                    pEdShell->Push();

                    eStat = IS_END;
                    break;
                }

                const OUString& rStr = m_pCurTextFrame->GetText();
                SvxSwAutoFormatFlags& rFlags = pACorr->GetSwFlags();
                if (rFlags.bChgEnumNum && (rStr == "- " || rStr == "* "))
                {
                    eStat = TST_ENUMERIC;
                    break;
                }

                // Check for 3 "---" or "===". In this case, the previous paragraph should be
                // underlined and the current be deleted!
                if( !DoUnderline() && bReplaceStyles )
                {
                    SetColl( RES_POOLCOLL_STANDARD, true );
                    bEmptyLine = true;
                }
                eStat = READ_NEXT_PARA;
            }
            else
                eStat = GET_ALL_INFO;
            break;

        case GET_ALL_INFO:
            {
                assert(m_pCurTextFrame);
                if (m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule())
                {
                    // do nothing in numbering, go to next
                    bEmptyLine = false;
                    eStat = READ_NEXT_PARA;
                    // delete all blanks at beginning/end and in between
                    //JP 29.04.98: first only "all in between"
                    DelMoreLinesBlanks();
                    // auto correct paragraphs that fail to enter state HAS_FMTCOLL
                    AutoCorrect();
                    break;
                }

                aFInfo.SetFrame( m_pCurTextFrame );

                // so far: if there were templates assigned, keep these and go to next node
                sal_uInt16 nPoolId = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId();
                if( IsPoolUserFormat( nPoolId )
                        ? !m_aFlags.bChgUserColl
                        : ( RES_POOLCOLL_STANDARD != nPoolId &&
                           ( !m_aFlags.bAFormatByInput ||
                            (RES_POOLCOLL_TEXT_MOVE != nPoolId &&
                             RES_POOLCOLL_TEXT != nPoolId )) ))
                {
                    eStat = HAS_FMTCOLL;
                    break;
                }

                // replace custom styles with text body
                if ( IsPoolUserFormat( nPoolId ) && m_aFlags.bChgUserColl )
                {
                    SetColl( RES_POOLCOLL_TEXT, true );
                }

                // check for left margin set by the style
                if( IsPoolUserFormat( nPoolId ) ||
                    RES_POOLCOLL_STANDARD == nPoolId )
                {
                    SvxFirstLineIndentItem const*const pFirstLineIndent(
                        m_pCurTextFrame->GetTextNodeForParaProps()
                            ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE));
                    SvxTextLeftMarginItem const*const pTextLeftMargin(
                        m_pCurTextFrame->GetTextNodeForParaProps()
                            ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT));

                    // Unit conversion is not needed here: check the sign only
                    double dIndentValue = 0.0;
                    if (pFirstLineIndent)
                        dIndentValue = pFirstLineIndent->GetTextFirstLineOffset().m_dValue;

                    if (0.0 != dIndentValue
                        || (pTextLeftMargin && (0.0 != pTextLeftMargin->GetTextLeft().m_dValue)))
                    {
                        // exception: numbering/enumeration can have an indentation
                        if (IsEnumericChar(*m_pCurTextFrame))
                        {
                            nLevel = CalcLevel(*m_pCurTextFrame, &nDigitLvl);
                            if( nLevel >= MAXLEVEL )
                                nLevel = MAXLEVEL-1;
                            BuildEnum( nLevel, nDigitLvl );
                            eStat = READ_NEXT_PARA;
                            break;
                        }

                        // never merge (maybe only indent as exception)
                        m_bMoreLines = true;

                        if( bReplaceStyles )
                        {
                            // then use one of our templates
                            if (0.0 < dIndentValue) // positive 1st line indentation
                                BuildIndent();
                            else if (0.0 > dIndentValue) // negative 1st line indentation
                                BuildNegIndent( aFInfo.GetLineStart() );
                            else if (pTextLeftMargin
                                     && (pTextLeftMargin->GetTextLeft().m_dValue
                                         != 0.0)) // is indentation
                                BuildTextIndent();
                        }
                        eStat = READ_NEXT_PARA;
                        break;
                    }
                }

                nLevel = CalcLevel( *m_pCurTextFrame, &nDigitLvl );
                m_bMoreLines = !IsOneLine(*m_pCurTextFrame);
                // note: every use of pNextFrame in following states, until the
                // next READ_NEXT_PARA, relies on this update
                pNextFrame = GetNextNode();
                if (pNextFrame)
                {
                    bNxtEmpty = IsEmptyLine(*pNextFrame);
                    bNxtAlpha = IsNoAlphaLine(*pNextFrame);
                    nNxtLevel = CalcLevel(*pNextFrame);

                    if (!bEmptyLine && HasBreakAttr(*m_pCurTextFrame))
                        bEmptyLine = true;
                    if (!bNxtEmpty && HasBreakAttr(*pNextFrame))
                        bNxtEmpty = true;

                }
                else
                {
                    bNxtEmpty = false;
                    bNxtAlpha = false;
                    nNxtLevel = 0;
                }
                eStat = !m_bMoreLines ? IS_ONE_LINE : TST_ENUMERIC;
            }
            break;

        case IS_ONE_LINE:
            {
                eStat = TST_ENUMERIC;
                if( !bReplaceStyles )
                    break;

                assert(m_pCurTextFrame);

                const OUString sClrStr( DelLeadingBlanks(m_pCurTextFrame->GetText()) );

                if( sClrStr.isEmpty() )
                {
                    bEmptyLine = true;
                    eStat = READ_NEXT_PARA;
                    break;      // read next paragraph
                }

                // check if headline
                if (!bEmptyLine || !IsFirstCharCapital(*m_pCurTextFrame)
                    || IsBlanksInString(*m_pCurTextFrame))
                    break;

                bEmptyLine = false;
                const OUString sEndClrStr( DelTrailingBlanks(sClrStr) );
                const sal_Unicode cLast = sEndClrStr[sEndClrStr.getLength() - 1];

                // not, then check if headline
                if( ':' == cLast )
                {
                    BuildHeadLine( 2 );
                    eStat = READ_NEXT_PARA;
                    break;
                }
                else if( 256 <= cLast || !strchr( ",.;", cLast ) )
                {
                    if( bNxtEmpty || bNxtAlpha
                        || (pNextFrame && IsEnumericChar(*pNextFrame)))
                    {

                        // one level below?
                        if( nLevel >= MAXLEVEL )
                            nLevel = MAXLEVEL-1;

                        if( USHRT_MAX == nLastHeadLvl )
                            nLastHeadLvl = 0;
                        else if( nLastCalcHeadLvl < nLevel )
                        {
                            if( nLastHeadLvl+1 < MAXLEVEL )
                                ++nLastHeadLvl;
                        }
                        // one level above?
                        else if( nLastCalcHeadLvl > nLevel )
                        {
                            if( nLastHeadLvl )
                                --nLastHeadLvl;
                        }
                        nLastCalcHeadLvl = nLevel;

                        if( m_aFlags.bAFormatByInput )
                            BuildHeadLine( nLevel );
                        else
                            BuildHeadLine( nLastHeadLvl );
                        eStat = READ_NEXT_PARA;
                        break;
                    }
                }
            }
            break;

        case TST_ENUMERIC:
            {
                bEmptyLine = false;
                assert(m_pCurTextFrame);
                if (IsEnumericChar(*m_pCurTextFrame))
                {
                    if( nLevel >= MAXLEVEL )
                        nLevel = MAXLEVEL-1;
                    BuildEnum( nLevel, nDigitLvl );
                    eStat = READ_NEXT_PARA;
                }
                else if( bReplaceStyles )
                    eStat = nLevel ? TST_IDENT : TST_NEG_IDENT;
                else
                {
                    eStat = READ_NEXT_PARA;
                    BuildText();
                }
            }
            break;

        case TST_IDENT:
            // Spaces at the beginning, check again for indentation
            if( m_bMoreLines && nLevel )
            {
                SwTwips nSz = aFInfo.GetFirstIndent();
                if( 0 < nSz )           // positive 1st line indentation
                    BuildIndent();
                else if( 0 > nSz )      // negative 1st line indentation
                    BuildNegIndent( aFInfo.GetLineStart() );
                else                    // is indentation
                    BuildTextIndent();
                eStat = READ_NEXT_PARA;
            }
            else if (nLevel && pNextFrame &&
                     !bNxtEmpty && !bNxtAlpha && !nNxtLevel &&
                     !IsEnumericChar(*pNextFrame))
            {
                // is an indentation
                BuildIndent();
                eStat = READ_NEXT_PARA;
            }
            else
                eStat = TST_TXT_BODY;
            break;

        case TST_NEG_IDENT:
            // no spaces at the beginning, check again for negative indentation
            {
                if( m_bMoreLines && !nLevel )
                {
                    SwTwips nSz = aFInfo.GetFirstIndent();
                    if( 0 < nSz )           // positive 1st line indentation
                        BuildIndent();
                    else if( 0 > nSz )      // negative 1st line indentation
                        BuildNegIndent( aFInfo.GetLineStart() );
                    else                    // is _no_ indentation
                        BuildText();
                    eStat = READ_NEXT_PARA;
                }
                else if (!nLevel && pNextFrame &&
                         !bNxtEmpty && !bNxtAlpha && nNxtLevel &&
                         !IsEnumericChar(*pNextFrame))
                {
                    // is a negative indentation
                    BuildNegIndent( aFInfo.GetLineStart() );
                    eStat = READ_NEXT_PARA;
                }
                else
                    eStat = TST_TXT_BODY;
            }
            break;

        case TST_TXT_BODY:
            {
                if( m_bMoreLines )
                {
                    SwTwips nSz = aFInfo.GetFirstIndent();
                    if( 0 < nSz )           // positive 1st line indentation
                        BuildIndent();
                    else if( 0 > nSz )      // negative 1st line indentation
                        BuildNegIndent( aFInfo.GetLineStart() );
                    else if( nLevel )       // is indentation
                        BuildTextIndent();
                    else
                        BuildText();
                }
                else if( nLevel )
                    BuildTextIndent();
                else
                    BuildText();
                eStat = READ_NEXT_PARA;
            }
            break;

        case HAS_FMTCOLL:
            {
                // so far: if there were templates assigned, keep these and go to next node
                bEmptyLine = false;
                eStat = READ_NEXT_PARA;
                // delete all blanks at beginning/end and in between
                //JP 29.04.98: first only "all in between"
                DelMoreLinesBlanks();

                // handle hard attributes
                if (m_pCurTextFrame->GetTextNodeForParaProps()->HasSwAttrSet())
                {
                    SvxFirstLineIndentItem const*const pFirstLineIndent(
                        m_pCurTextFrame->GetTextNodeForParaProps()
                            ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE, false));
                    SvxTextLeftMarginItem const*const pTextLeftMargin(
                        m_pCurTextFrame->GetTextNodeForParaProps()
                            ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT, false));

                    // Unit conversion is not needed here: check the sign only
                    double dIndentValue = 0.0;
                    if (pFirstLineIndent)
                        dIndentValue = pFirstLineIndent->GetTextFirstLineOffset().m_dValue;

                    if (bReplaceStyles
                        && (0.0 != dIndentValue
                            || (pTextLeftMargin
                                && (0.0 != pTextLeftMargin->GetTextLeft().m_dValue))))
                    {
                        // then use one of our templates
                        if (0.0 < dIndentValue) // positive 1st line indentation
                            BuildIndent();
                        else if (0.0 > dIndentValue) // negative 1st line indentation
                        {
                            BuildNegIndent( aFInfo.GetLineStart() );
                        }
                        else if (pTextLeftMargin
                                 && (0.0
                                     != pTextLeftMargin->GetTextLeft().m_dValue)) // is indentation
                            BuildTextIndent();
                        else
                            BuildText();
                    }
                }
                // force auto correct
                AutoCorrect();
            }
            break;

        case IS_END:
            m_bEnd = true;
            break;
        }
    }

    if( m_aFlags.bWithRedlining )
        m_pDoc->SetAutoFormatRedline( false );
    m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );

    // restore undo (in case it has been changed)
    m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoState);

    // disable display of percentage again
    if( !m_aFlags.bAFormatByInput )
        ::EndProgress( m_pDoc->GetDocShell() );
}

void SwEditShell::AutoFormat( const SvxSwAutoFormatFlags* pAFlags, bool bCurrentParagraphOnly )
{
    std::optional<SwWait> oWait;

    CurrShell aCurr( this );
    StartAllAction();
    StartUndo( SwUndoId::AUTOFORMAT );

    SvxSwAutoFormatFlags aAFFlags;     // use default values or add params?
    if( pAFlags )
    {
        aAFFlags = *pAFlags;
        if( !aAFFlags.bAFormatByInput )
            oWait.emplace( *GetDoc()->GetDocShell(), true );
    }

    SwPaM* pCursor = GetCursor();
    // There are more than one or a selection is open
    if( pCursor->GetNext() != pCursor || pCursor->HasMark() )
    {
        for(SwPaM& rPaM : GetCursor()->GetRingContainer())
        {
            if( rPaM.HasMark() )
            {
                SwAutoFormat( this, aAFFlags, &rPaM.Start()->GetNode(),
                                     &rPaM.End()->GetNode() );
            }
        }
    }
    else if (bCurrentParagraphOnly)
    {
        pCursor->SetMark();
        SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(),
                                &pCursor->GetPoint()->GetNode() );
    }
    else
    {
        SwAutoFormat( this, std::move(aAFFlags) );
    }

    EndUndo( SwUndoId::AUTOFORMAT );
    EndAllAction();
}

void SwEditShell::AutoFormatBySplitNode()
{
    CurrShell aCurr( this );
    SwPaM* pCursor = GetCursor();
    if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) )
        return;

    StartAllAction();
    StartUndo( SwUndoId::AUTOFORMAT );

    bool bRange = false;
    pCursor->SetMark();
    SwPosition* pMarkPos = pCursor->GetMark();
    if( pMarkPos->GetContentIndex() )
    {
        pMarkPos->SetContent(0);
        bRange = true;
    }
    else
    {
        // then go one node backwards
        SwNodeIndex aNdIdx(pCursor->GetMark()->GetNode());
        sw::GotoPrevLayoutTextFrame(aNdIdx, GetLayout());
        SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode();
        if (pTextNd && !pTextNd->GetText().isEmpty())
        {
            pCursor->GetMark()->Assign( aNdIdx );
            bRange = true;
        }
    }

    if( bRange )
    {
        Push();     // save cursor

        SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far

        SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(),
                                &pCursor->GetPoint()->GetNode() );
        SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
        if( pACorr && !pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord |
                                ACFlags::AddNonBrkSpace | ACFlags::ChgOrdinalNumber | ACFlags::TransliterateRTL |
                                ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | ACFlags::Autocorrect |
                                ACFlags::SetDOIAttr ))
            pACorr = nullptr;

        if( pACorr )
            AutoCorrect( *pACorr,false, u'\0' );

        //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor!
        Pop(PopMode::DeleteCurrent);
        pCursor = GetCursor();
    }
    pCursor->DeleteMark();
    pCursor->Move( fnMoveForward, GoInNode );

    EndUndo( SwUndoId::AUTOFORMAT );
    EndAllAction();

}

SvxSwAutoFormatFlags* SwEditShell::GetAutoFormatFlags()
{
    if (!s_pAutoFormatFlags)
        s_pAutoFormatFlags = new SvxSwAutoFormatFlags;

    return s_pAutoFormatFlags;
}

void SwEditShell::SetAutoFormatFlags(SvxSwAutoFormatFlags const * pFlags)
{
    SvxSwAutoFormatFlags* pEditFlags = GetAutoFormatFlags();

    pEditFlags->bSetNumRule     = pFlags->bSetNumRule;
    pEditFlags->bChgEnumNum     = pFlags->bChgEnumNum;
    pEditFlags->bSetBorder      = pFlags->bSetBorder;
    pEditFlags->bCreateTable    = pFlags->bCreateTable;
    pEditFlags->bReplaceStyles  = pFlags->bReplaceStyles;
    pEditFlags->bReplaceStylesByInput = pFlags->bReplaceStylesByInput;
    pEditFlags->bAFormatByInpDelSpacesAtSttEnd =
                                    pFlags->bAFormatByInpDelSpacesAtSttEnd;
    pEditFlags->bAFormatByInpDelSpacesBetweenLines =
                                    pFlags->bAFormatByInpDelSpacesBetweenLines;

    //JP 15.12.98: copy BulletChar and Font into "normal" ones
    //             because AutoFormat can only work with the latter!
    pEditFlags->cBullet             = pFlags->cByInputBullet;
    pEditFlags->aBulletFont         = pFlags->aByInputBulletFont;
    pEditFlags->cByInputBullet      = pFlags->cByInputBullet;
    pEditFlags->aByInputBulletFont  = pFlags->aByInputBulletFont;
}

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