/* -*- 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 <com/sun/star/table/TableSortField.hpp>
#include <cppuhelper/exc_hlp.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/any.hxx>
#include <osl/endian.h>
#include <rtl/ustrbuf.hxx>
#include <unotools/collatorwrapper.hxx>
#include <swtypes.hxx>
#include <hintids.hxx>
#include <cmdid.h>
#include <hints.hxx>
#include <IMark.hxx>
#include <frmfmt.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <istyleaccess.hxx>
#include <ndtxt.hxx>
#include <ndnotxt.hxx>
#include <unocrsr.hxx>
#include <unocrsrhelper.hxx>
#include <swundo.hxx>
#include <rootfrm.hxx>
#include <flyfrm.hxx>
#include <ftnidx.hxx>
#include <sfx2/linkmgr.hxx>
#include <docary.hxx>
#include <paratr.hxx>
#include <pam.hxx>
#include <shellio.hxx>
#include <swerror.h>
#include <swtblfmt.hxx>
#include <fmtruby.hxx>
#include <docsh.hxx>
#include <docstyle.hxx>
#include <charfmt.hxx>
#include <txtfld.hxx>
#include <fmtfld.hxx>
#include <fmtpdsc.hxx>
#include <pagedesc.hxx>
#include <edimp.hxx>
#include <fchrfmt.hxx>
#include <fmtautofmt.hxx>
#include <cntfrm.hxx>
#include <pagefrm.hxx>
#include <doctxm.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/fcontnr.hxx>
#include <fmtrfmrk.hxx>
#include <txtrfmrk.hxx>
#include <unotextrange.hxx>
#include <unotextcursor.hxx>
#include <unomap.hxx>
#include <unosett.hxx>
#include <unoprnms.hxx>
#include <unodraw.hxx>
#include <unocoll.hxx>
#include <unostyle.hxx>
#include <unometa.hxx>
#include <fmtanchr.hxx>
#include <editeng/flstitem.hxx>
#include <svtools/ctrltool.hxx>
#include <flypos.hxx>
#include <txtftn.hxx>
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/TextMarkupType.hpp>
#include <com/sun/star/style/PageStyleLayout.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <unoframe.hxx>
#include <fmthdft.hxx>
#include <vcl/svapp.hxx>
#include <unotools/syslocale.hxx>
#include <fmtflcnt.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/unolingu.hxx>
#include <fmtclds.hxx>
#include <dcontact.hxx>
#include <SwStyleNameMapper.hxx>
#include <sortopt.hxx>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <memory>
#include <unoparaframeenum.hxx>
#include <unoparagraph.hxx>
#include <iodetect.hxx>
#include <comphelper/servicehelper.hxx>
#include <comphelper/profilezone.hxx>
 
using namespace ::com::sun::star;
 
// Helper classes
SwUnoInternalPaM::SwUnoInternalPaM(SwDoc& rDoc) :
    SwPaM(rDoc.GetNodes())
{
}
 
SwUnoInternalPaM::~SwUnoInternalPaM()
{
    while( GetNext() != this)
    {
        delete GetNext();
    }
}
 
SwUnoInternalPaM&   SwUnoInternalPaM::operator=(const SwPaM& rPaM)
{
    const SwPaM* pTmp = &rPaM;
    *GetPoint() = *rPaM.GetPoint();
    if(rPaM.HasMark())
    {
        SetMark();
        *GetMark() = *rPaM.GetMark();
    }
    else
        DeleteMark();
    while(&rPaM != (pTmp = pTmp->GetNext()))
    {
        if(pTmp->HasMark())
            new SwPaM(*pTmp->GetMark(), *pTmp->GetPoint(), this);
        else
            new SwPaM(*pTmp->GetPoint(), this);
    }
    return *this;
}
 
void SwUnoCursorHelper::SelectPam(SwPaM & rPam, const bool bExpand)
{
    if (bExpand)
    {
        if (!rPam.HasMark())
        {
            rPam.SetMark();
        }
    }
    else if (rPam.HasMark())
    {
        rPam.DeleteMark();
    }
}
 
void SwUnoCursorHelper::GetTextFromPam(SwPaM & rPam, OUString & rBuffer)
{
    if (!rPam.HasMark())
    {
        return;
    }
    SvMemoryStream aStream;
#ifdef OSL_BIGENDIAN
    aStream.SetEndian( SvStreamEndian::BIG );
#else
    aStream.SetEndian( SvStreamEndian::LITTLE );
#endif
    WriterRef xWrt;
    // TODO/MBA: looks like a BaseURL doesn't make sense here
    SwReaderWriter::GetWriter( FILTER_TEXT_DLG, OUString(), xWrt );
    if( !xWrt.is() )
        return;
 
    SwWriter aWriter( aStream, rPam );
    xWrt->m_bASCII_NoLastLineEnd = true;
    xWrt->m_bExportPargraphNumbering = false;
    SwAsciiOptions aOpt = xWrt->GetAsciiOptions();
    aOpt.SetCharSet( RTL_TEXTENCODING_UNICODE );
    xWrt->SetAsciiOptions( aOpt );
    xWrt->m_bUCS2_WithStartChar = false;
    // #i68522#
    const bool bOldShowProgress = xWrt->m_bShowProgress;
    xWrt->m_bShowProgress = false;
 
    if( ! aWriter.Write( xWrt ).IsError() )
    {
        const sal_uInt64 lUniLen = aStream.GetSize()/sizeof( sal_Unicode );
        if (lUniLen < static_cast<sal_uInt64>(SAL_MAX_INT32-1))
        {
            aStream.WriteUInt16( '\0' );
 
            aStream.Seek( 0 );
            aStream.ResetError();
 
            rtl_uString *pStr = rtl_uString_alloc(lUniLen);
            aStream.ReadBytes(pStr->buffer, lUniLen * sizeof(sal_Unicode));
            rBuffer = OUString(pStr, SAL_NO_ACQUIRE);
        }
    }
    xWrt->m_bShowProgress = bOldShowProgress;
 
}
 
/// @throws lang::IllegalArgumentException
/// @throws uno::RuntimeException
static void
lcl_setCharStyle(SwDoc *const pDoc, const uno::Any & rValue, SfxItemSet & rSet)
{
    SwDocShell *const pDocSh = pDoc->GetDocShell();
    if(pDocSh)
    {
        OUString uStyle;
        if (!(rValue >>= uStyle))
        {
            throw lang::IllegalArgumentException();
        }
        OUString sStyle;
        SwStyleNameMapper::FillUIName(uStyle, sStyle,
                SwGetPoolIdFromName::ChrFmt);
        SwDocStyleSheet *const pStyle = static_cast<SwDocStyleSheet*>(
            pDocSh->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Char));
        if (!pStyle)
        {
            throw lang::IllegalArgumentException();
        }
        const SwFormatCharFormat aFormat(pStyle->GetCharFormat());
        rSet.Put(aFormat);
    }
};
 
/// @throws lang::IllegalArgumentException
static void
lcl_setAutoStyle(IStyleAccess & rStyleAccess, const uno::Any & rValue,
        SfxItemSet & rSet, const bool bPara)
{
    OUString uStyle;
    if (!(rValue >>= uStyle))
    {
         throw lang::IllegalArgumentException();
    }
    std::shared_ptr<SfxItemSet> pStyle = bPara ?
        rStyleAccess.getByName(uStyle, IStyleAccess::AUTO_STYLE_PARA ):
        rStyleAccess.getByName(uStyle, IStyleAccess::AUTO_STYLE_CHAR );
    if(!pStyle.get())
    {
         throw lang::IllegalArgumentException();
    }
 
    SwFormatAutoFormat aFormat( bPara
        ? sal::static_int_cast< sal_uInt16 >(RES_AUTO_STYLE)
        : sal::static_int_cast< sal_uInt16 >(RES_TXTATR_AUTOFMT) );
    aFormat.SetStyleHandle( pStyle );
    rSet.Put(aFormat);
};
 
void
SwUnoCursorHelper::SetTextFormatColl(const uno::Any & rAny, SwPaM & rPaM)
{
    SwDoc *const pDoc = rPaM.GetDoc();
    SwDocShell *const pDocSh = pDoc->GetDocShell();
    if(!pDocSh)
        return;
    OUString uStyle;
    rAny >>= uStyle;
    OUString sStyle;
    SwStyleNameMapper::FillUIName(uStyle, sStyle,
            SwGetPoolIdFromName::TxtColl );
    SwDocStyleSheet *const pStyle = static_cast<SwDocStyleSheet*>(
            pDocSh->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Para));
    if (!pStyle)
    {
        throw lang::IllegalArgumentException();
    }
 
    SwTextFormatColl *const pLocal = pStyle->GetCollection();
    UnoActionContext aAction(pDoc);
    pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
    SwPaM *pTmpCursor = &rPaM;
    do {
        pDoc->SetTextFormatColl(*pTmpCursor, pLocal);
        pTmpCursor = pTmpCursor->GetNext();
    } while ( pTmpCursor != &rPaM );
    pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
}
 
bool
SwUnoCursorHelper::SetPageDesc(
        const uno::Any& rValue, SwDoc & rDoc, SfxItemSet & rSet)
{
    OUString uDescName;
    if (!(rValue >>= uDescName))
    {
        return false;
    }
    std::unique_ptr<SwFormatPageDesc> pNewDesc;
    const SfxPoolItem* pItem;
    if(SfxItemState::SET == rSet.GetItemState( RES_PAGEDESC, true, &pItem ) )
    {
        pNewDesc.reset(new SwFormatPageDesc(
                    *static_cast<const SwFormatPageDesc*>(pItem)));
    }
    if (!pNewDesc.get())
    {
        pNewDesc.reset(new SwFormatPageDesc());
    }
    OUString sDescName;
    SwStyleNameMapper::FillUIName(uDescName, sDescName,
            SwGetPoolIdFromName::PageDesc);
    if (!pNewDesc->GetPageDesc() ||
        (pNewDesc->GetPageDesc()->GetName() != sDescName))
    {
        bool bPut = false;
        if (!sDescName.isEmpty())
        {
            SwPageDesc *const pPageDesc = SwPageDesc::GetByName(rDoc, sDescName);
            if (!pPageDesc)
            {
                throw lang::IllegalArgumentException();
            }
            pNewDesc.get()->RegisterToPageDesc( *pPageDesc );
            bPut = true;
        }
        if(!bPut)
        {
            rSet.ClearItem(RES_BREAK);
            rSet.Put(SwFormatPageDesc());
        }
        else
        {
            rSet.Put(*pNewDesc);
        }
    }
    return true;
}
 
static void
lcl_SetNodeNumStart(SwPaM & rCursor, uno::Any const& rValue)
{
    sal_Int16 nTmp = 1;
    rValue >>= nTmp;
    sal_uInt16 nStt = (nTmp < 0 ? USHRT_MAX : static_cast<sal_uInt16>(nTmp));
    SwDoc* pDoc = rCursor.GetDoc();
    UnoActionContext aAction(pDoc);
 
    if( rCursor.GetNext() != &rCursor )         // MultiSelection?
    {
        pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
        SwPamRanges aRangeArr( rCursor );
        SwPaM aPam( *rCursor.GetPoint() );
        for( size_t n = 0; n < aRangeArr.Count(); ++n )
        {
          pDoc->SetNumRuleStart(*aRangeArr.SetPam( n, aPam ).GetPoint());
          pDoc->SetNodeNumStart(*aRangeArr.SetPam( n, aPam ).GetPoint(),
                    nStt );
        }
        pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
    }
    else
    {
        pDoc->SetNumRuleStart( *rCursor.GetPoint());
        pDoc->SetNodeNumStart( *rCursor.GetPoint(), nStt );
    }
}
 
static bool
lcl_setCharFormatSequence(SwPaM & rPam, uno::Any const& rValue)
{
    uno::Sequence<OUString> aCharStyles;
    if (!(rValue >>= aCharStyles))
    {
        return false;
    }
 
    for (sal_Int32 nStyle = 0; nStyle < aCharStyles.getLength(); nStyle++)
    {
        uno::Any aStyle;
        rPam.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::START, nullptr);
        aStyle <<= aCharStyles.getConstArray()[nStyle];
        // create a local set and apply each format directly
        SfxItemSet aSet(rPam.GetDoc()->GetAttrPool(),
                svl::Items<RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT>{});
        lcl_setCharStyle(rPam.GetDoc(), aStyle, aSet);
        // the first style should replace the current attributes,
        // all other have to be added
        SwUnoCursorHelper::SetCursorAttr(rPam, aSet, nStyle
                ? SetAttrMode::DONTREPLACE
                : SetAttrMode::DEFAULT);
        rPam.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::START, nullptr);
    }
    return true;
}
 
static void
lcl_setDropcapCharStyle(SwPaM const & rPam, SfxItemSet & rItemSet,
        uno::Any const& rValue)
{
    OUString uStyle;
    if (!(rValue >>= uStyle))
    {
        throw lang::IllegalArgumentException();
    }
    OUString sStyle;
    SwStyleNameMapper::FillUIName(uStyle, sStyle,
            SwGetPoolIdFromName::ChrFmt);
    SwDoc *const pDoc = rPam.GetDoc();
    //default character style must not be set as default format
    SwDocStyleSheet *const pStyle = static_cast<SwDocStyleSheet*>(
            pDoc->GetDocShell()
            ->GetStyleSheetPool()->Find(sStyle, SfxStyleFamily::Char));
    if (!pStyle || pStyle->GetCharFormat() == pDoc->GetDfltCharFormat())
    {
        throw lang::IllegalArgumentException();
    }
    std::unique_ptr<SwFormatDrop> pDrop;
    SfxPoolItem const* pItem(nullptr);
    if (SfxItemState::SET ==
            rItemSet.GetItemState(RES_PARATR_DROP, true, &pItem))
    {
        pDrop.reset(new SwFormatDrop(*static_cast<const SwFormatDrop*>(pItem)));
    }
    if (!pDrop.get())
    {
        pDrop.reset(new SwFormatDrop);
    }
    const rtl::Reference<SwDocStyleSheet> xStyle(new SwDocStyleSheet(*pStyle));
    pDrop->SetCharFormat(xStyle->GetCharFormat());
    rItemSet.Put(*pDrop);
}
 
static void
lcl_setRubyCharstyle(SfxItemSet & rItemSet, uno::Any const& rValue)
{
    OUString sTmp;
    if (!(rValue >>= sTmp))
    {
        throw lang::IllegalArgumentException();
    }
 
    std::unique_ptr<SwFormatRuby> pRuby;
    const SfxPoolItem* pItem;
    if (SfxItemState::SET ==
            rItemSet.GetItemState(RES_TXTATR_CJK_RUBY, true, &pItem))
    {
        pRuby.reset(new SwFormatRuby(*static_cast<const SwFormatRuby*>(pItem)));
    }
    if (!pRuby.get())
    {
        pRuby.reset(new SwFormatRuby(OUString()));
    }
    OUString sStyle;
    SwStyleNameMapper::FillUIName(sTmp, sStyle,
            SwGetPoolIdFromName::ChrFmt);
    pRuby->SetCharFormatName(sStyle);
    pRuby->SetCharFormatId(0);
    if (!sStyle.isEmpty())
    {
        const sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName(
                sStyle, SwGetPoolIdFromName::ChrFmt);
        pRuby->SetCharFormatId(nId);
    }
    rItemSet.Put(*pRuby);
}
 
bool
SwUnoCursorHelper::SetCursorPropertyValue(
        SfxItemPropertySimpleEntry const& rEntry, const uno::Any& rValue,
        SwPaM & rPam, SfxItemSet & rItemSet)
{
    if (!(rEntry.nFlags & beans::PropertyAttribute::MAYBEVOID) &&
        (rValue.getValueType() == cppu::UnoType<void>::get()))
    {
        return false;
    }
    bool bRet = true;
    switch (rEntry.nWID)
    {
        case RES_TXTATR_CHARFMT:
            lcl_setCharStyle(rPam.GetDoc(), rValue, rItemSet);
        break;
        case RES_TXTATR_AUTOFMT:
            lcl_setAutoStyle(rPam.GetDoc()->GetIStyleAccess(),
                    rValue, rItemSet, false);
        break;
        case FN_UNO_CHARFMT_SEQUENCE:
            lcl_setCharFormatSequence(rPam, rValue);
        break;
        case FN_UNO_PARA_STYLE :
            SwUnoCursorHelper::SetTextFormatColl(rValue, rPam);
        break;
        case RES_AUTO_STYLE:
            lcl_setAutoStyle(rPam.GetDoc()->GetIStyleAccess(),
                    rValue, rItemSet, true);
        break;
        case FN_UNO_PAGE_STYLE:
            //FIXME nothing here?
        break;
        case FN_UNO_NUM_START_VALUE:
            lcl_SetNodeNumStart( rPam, rValue );
        break;
        case FN_UNO_NUM_LEVEL:
        // #i91601#
        case FN_UNO_LIST_ID:
        case FN_UNO_IS_NUMBER:
        {
            // multi selection is not considered
            SwTextNode *const pTextNd = rPam.GetNode().GetTextNode();
            if (!pTextNd)
            {
                throw lang::IllegalArgumentException();
            }
            if (FN_UNO_NUM_LEVEL == rEntry.nWID)
            {
                sal_Int16 nLevel = 0;
                if (rValue >>= nLevel)
                {
                    if (nLevel < 0 || MAXLEVEL <= nLevel)
                    {
                        throw lang::IllegalArgumentException(
                            "invalid NumberingLevel", nullptr, 0);
                    }
                    pTextNd->SetAttrListLevel(nLevel);
                }
            }
            // #i91601#
            else if (FN_UNO_LIST_ID == rEntry.nWID)
            {
                OUString sListId;
                if (rValue >>= sListId)
                {
                    pTextNd->SetListId( sListId );
                }
            }
            else if (FN_UNO_IS_NUMBER == rEntry.nWID)
            {
                bool bIsNumber(false);
                if (rValue >>= bIsNumber)
                {
                    if (!bIsNumber)
                    {
                        pTextNd->SetCountedInList( false );
                    }
                }
            }
            //PROPERTY_MAYBEVOID!
        }
        break;
        case FN_NUMBER_NEWSTART:
        {
            bool bVal = false;
            if (!(rValue >>= bVal))
            {
                throw lang::IllegalArgumentException();
            }
            rPam.GetDoc()->SetNumRuleStart(*rPam.GetPoint(), bVal);
        }
        break;
        case FN_UNO_NUM_RULES:
            SwUnoCursorHelper::setNumberingProperty(rValue, rPam);
        break;
        case RES_PARATR_DROP:
        {
            if (MID_DROPCAP_CHAR_STYLE_NAME == rEntry.nMemberId)
            {
                lcl_setDropcapCharStyle(rPam, rItemSet, rValue);
            }
            else
            {
                bRet = false;
            }
        }
        break;
        case RES_TXTATR_CJK_RUBY:
        {
            if (MID_RUBY_CHARSTYLE == rEntry.nMemberId)
            {
                lcl_setRubyCharstyle(rItemSet, rValue);
            }
            else
            {
                bRet = false;
            }
        }
        break;
        case RES_PAGEDESC:
        {
            if (MID_PAGEDESC_PAGEDESCNAME == rEntry.nMemberId)
            {
                SwUnoCursorHelper::SetPageDesc(
                        rValue, *rPam.GetDoc(), rItemSet);
            }
            else
            {
                bRet = false;
            }
        }
        break;
        default:
            bRet = false;
    }
    return bRet;
}
 
SwFormatColl *
SwUnoCursorHelper::GetCurTextFormatColl(SwPaM & rPaM, const bool bConditional)
{
    static const sal_uLong nMaxLookup = 1000;
    SwFormatColl *pFormat = nullptr;
    bool bError = false;
    SwPaM *pTmpCursor = &rPaM;
    do
    {
        const sal_uLong nSttNd = pTmpCursor->Start()->nNode.GetIndex();
        const sal_uLong nEndNd = pTmpCursor->End()->nNode.GetIndex();
 
        if( nEndNd - nSttNd >= nMaxLookup )
        {
            pFormat = nullptr;
            break;
        }
 
        const SwNodes& rNds = rPaM.GetDoc()->GetNodes();
        for( sal_uLong n = nSttNd; n <= nEndNd; ++n )
        {
            SwTextNode const*const pNd = rNds[ n ]->GetTextNode();
            if( pNd )
            {
                SwFormatColl *const pNdFormat = bConditional
                    ? pNd->GetFormatColl() : &pNd->GetAnyFormatColl();
                if( !pFormat )
                {
                    pFormat = pNdFormat;
                }
                else if( pFormat != pNdFormat )
                {
                    bError = true;
                    break;
                }
            }
        }
 
        pTmpCursor = pTmpCursor->GetNext();
    } while ( pTmpCursor != &rPaM );
    return bError ? nullptr : pFormat;
}
 
class SwXTextCursor::Impl
{
 
public:
    const SfxItemPropertySet &  m_rPropSet;
    const CursorType            m_eType;
    const uno::Reference< text::XText > m_xParentText;
    sw::UnoCursorPointer m_pUnoCursor;
 
    Impl(   SwDoc & rDoc,
            const CursorType eType,
            uno::Reference<text::XText> const & xParent,
            SwPosition const& rPoint, SwPosition const*const pMark)
        : m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR))
        , m_eType(eType)
        , m_xParentText(xParent)
        , m_pUnoCursor(rDoc.CreateUnoCursor(rPoint))
    {
        if (pMark)
        {
            m_pUnoCursor->SetMark();
            *m_pUnoCursor->GetMark() = *pMark;
        }
    }
 
    SwUnoCursor& GetCursorOrThrow() {
        if(!m_pUnoCursor)
            throw uno::RuntimeException("SwXTextCursor: disposed or invalid", nullptr);
        return *m_pUnoCursor;
    }
};
 
SwUnoCursor& SwXTextCursor::GetCursor()
    { return *m_pImpl->m_pUnoCursor; }
 
SwPaM const* SwXTextCursor::GetPaM() const
    { return m_pImpl->m_pUnoCursor.get(); }
 
SwPaM* SwXTextCursor::GetPaM()
    { return m_pImpl->m_pUnoCursor.get(); }
 
SwDoc const* SwXTextCursor::GetDoc() const
    { return m_pImpl->m_pUnoCursor ? m_pImpl->m_pUnoCursor->GetDoc() : nullptr; }
 
SwDoc* SwXTextCursor::GetDoc()
    { return m_pImpl->m_pUnoCursor ? m_pImpl->m_pUnoCursor->GetDoc() : nullptr; }
 
SwXTextCursor::SwXTextCursor(
        SwDoc & rDoc,
        uno::Reference< text::XText > const& xParent,
        const CursorType eType,
        const SwPosition& rPos,
        SwPosition const*const pMark)
    : m_pImpl( new Impl(rDoc, eType, xParent, rPos, pMark) )
{
}
 
SwXTextCursor::SwXTextCursor(uno::Reference< text::XText > const& xParent,
        SwPaM const& rSourceCursor, const CursorType eType)
    : m_pImpl( new Impl(*rSourceCursor.GetDoc(), eType,
                xParent, *rSourceCursor.GetPoint(),
                rSourceCursor.HasMark() ? rSourceCursor.GetMark() : nullptr) )
{
}
 
SwXTextCursor::~SwXTextCursor()
{
}
 
void SwXTextCursor::DeleteAndInsert(const OUString& rText,
        const bool bForceExpandHints)
{
    auto pUnoCursor = static_cast<SwCursor*>(m_pImpl->m_pUnoCursor.get());
    if (pUnoCursor)
    {
        // Start/EndAction
        SwDoc* pDoc = pUnoCursor->GetDoc();
        UnoActionContext aAction(pDoc);
        const sal_Int32 nTextLen = rText.getLength();
        pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr);
        auto pCurrent = pUnoCursor;
        do
        {
            if (pCurrent->HasMark())
            {
                pDoc->getIDocumentContentOperations().DeleteAndJoin(*pCurrent);
            }
            if(nTextLen)
            {
                const bool bSuccess(
                    SwUnoCursorHelper::DocInsertStringSplitCR(
                        *pDoc, *pCurrent, rText, bForceExpandHints ) );
                OSL_ENSURE( bSuccess, "Doc->Insert(Str) failed." );
 
                SwUnoCursorHelper::SelectPam(*pUnoCursor, true);
                pCurrent->Left(rText.getLength());
            }
            pCurrent = pCurrent->GetNext();
        } while (pCurrent != pUnoCursor);
        pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr);
    }
}
 
enum ForceIntoMetaMode { META_CHECK_BOTH, META_INIT_START, META_INIT_END };
 
static bool
lcl_ForceIntoMeta(SwPaM & rCursor,
        uno::Reference<text::XText> const & xParentText,
        const enum ForceIntoMetaMode eMode)
{
    bool bRet( true ); // means not forced in META_CHECK_BOTH
    SwXMeta const * const pXMeta( dynamic_cast<SwXMeta*>(xParentText.get()) );
    OSL_ENSURE(pXMeta, "no parent?");
    if (!pXMeta)
        throw uno::RuntimeException();
    SwTextNode * pTextNode;
    sal_Int32 nStart;
    sal_Int32 nEnd;
    const bool bSuccess( pXMeta->SetContentRange(pTextNode, nStart, nEnd) );
    OSL_ENSURE(bSuccess, "no pam?");
    if (!bSuccess)
        throw uno::RuntimeException();
    // force the cursor back into the meta if it has moved outside
    SwPosition start(*pTextNode, nStart);
    SwPosition end(*pTextNode, nEnd);
    switch (eMode)
    {
        case META_INIT_START:
            *rCursor.GetPoint() = start;
            break;
        case META_INIT_END:
            *rCursor.GetPoint() = end;
            break;
        case META_CHECK_BOTH:
            if (*rCursor.Start() < start)
            {
                *rCursor.Start() = start;
                bRet = false;
            }
            if (*rCursor.End() > end)
            {
                *rCursor.End() = end;
                bRet = false;
            }
            break;
    }
    return bRet;
}
 
bool SwXTextCursor::IsAtEndOfMeta() const
{
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        auto pCursor( m_pImpl->m_pUnoCursor );
        SwXMeta const*const pXMeta(
                dynamic_cast<SwXMeta*>(m_pImpl->m_xParentText.get()) );
        OSL_ENSURE(pXMeta, "no meta?");
        if (pCursor && pXMeta)
        {
            SwTextNode * pTextNode;
            sal_Int32 nStart;
            sal_Int32 nEnd;
            const bool bSuccess(
                    pXMeta->SetContentRange(pTextNode, nStart, nEnd) );
            OSL_ENSURE(bSuccess, "no pam?");
            if (bSuccess)
            {
                const SwPosition end(*pTextNode, nEnd);
                if (   (*pCursor->GetPoint() == end)
                    || (*pCursor->GetMark()  == end))
                {
                    return true;
                }
            }
        }
    }
    return false;
}
 
OUString SwXTextCursor::getImplementationName()
{
    return OUString("SwXTextCursor");
}
 
static char const*const g_ServicesTextCursor[] =
{
    "com.sun.star.text.TextCursor",
    "com.sun.star.style.CharacterProperties",
    "com.sun.star.style.CharacterPropertiesAsian",
    "com.sun.star.style.CharacterPropertiesComplex",
    "com.sun.star.style.ParagraphProperties",
    "com.sun.star.style.ParagraphPropertiesAsian",
    "com.sun.star.style.ParagraphPropertiesComplex",
    "com.sun.star.text.TextSortable",
};
 
static const size_t g_nServicesTextCursor(SAL_N_ELEMENTS(g_ServicesTextCursor));
 
sal_Bool SAL_CALL SwXTextCursor::supportsService(const OUString& rServiceName)
{
    return cppu::supportsService(this, rServiceName);
}
 
uno::Sequence< OUString > SAL_CALL
SwXTextCursor::getSupportedServiceNames()
{
    return ::sw::GetSupportedServiceNamesImpl(
            g_nServicesTextCursor, g_ServicesTextCursor);
}
 
namespace
{
    class theSwXTextCursorUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextCursorUnoTunnelId > {};
}
 
const uno::Sequence< sal_Int8 > & SwXTextCursor::getUnoTunnelId()
{
    return theSwXTextCursorUnoTunnelId::get().getSeq();
}
 
sal_Int64 SAL_CALL
SwXTextCursor::getSomething(const uno::Sequence< sal_Int8 >& rId)
{
    const sal_Int64 nRet( ::sw::UnoTunnelImpl<SwXTextCursor>(rId, this) );
    return nRet ? nRet : OTextCursorHelper::getSomething(rId);
}
 
void SAL_CALL SwXTextCursor::collapseToStart()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (rUnoCursor.HasMark())
    {
        if (*rUnoCursor.GetPoint() > *rUnoCursor.GetMark())
        {
            rUnoCursor.Exchange();
        }
        rUnoCursor.DeleteMark();
    }
}
 
void SAL_CALL SwXTextCursor::collapseToEnd()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (rUnoCursor.HasMark())
    {
        if (*rUnoCursor.GetPoint() < *rUnoCursor.GetMark())
        {
            rUnoCursor.Exchange();
        }
        rUnoCursor.DeleteMark();
    }
}
 
sal_Bool SAL_CALL SwXTextCursor::isCollapsed()
{
    SolarMutexGuard aGuard;
 
    bool bRet = true;
    auto pUnoCursor(m_pImpl->m_pUnoCursor);
    if(pUnoCursor && pUnoCursor->GetMark())
    {
        bRet = (*pUnoCursor->GetPoint() == *pUnoCursor->GetMark());
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::goLeft(sal_Int16 nCount, sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    bool bRet = rUnoCursor.Left( nCount);
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH)
            && bRet;
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::goRight(sal_Int16 nCount, sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    bool bRet = rUnoCursor.Right(nCount);
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH)
            && bRet;
    }
    return bRet;
}
 
void SAL_CALL
SwXTextCursor::gotoStart(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("gotoStart");
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    if (CursorType::Body == m_pImpl->m_eType)
    {
        rUnoCursor.Move( fnMoveBackward, GoInDoc );
        //check, that the cursor is not in a table
        SwTableNode * pTableNode = rUnoCursor.GetNode().FindTableNode();
        SwContentNode * pCNode = nullptr;
        while (pTableNode)
        {
            rUnoCursor.GetPoint()->nNode = *pTableNode->EndOfSectionNode();
            pCNode = GetDoc()->GetNodes().GoNext(&rUnoCursor.GetPoint()->nNode);
            pTableNode = pCNode ? pCNode->FindTableNode() : nullptr;
        }
        if (pCNode)
        {
            rUnoCursor.GetPoint()->nContent.Assign(pCNode, 0);
        }
        SwStartNode const*const pTmp =
            rUnoCursor.GetNode().StartOfSectionNode();
        if (pTmp->IsSectionNode())
        {
            SwSectionNode const*const pSectionStartNode =
                static_cast<SwSectionNode const*>(pTmp);
            if (pSectionStartNode->GetSection().IsHiddenFlag())
            {
                pCNode = GetDoc()->GetNodes().GoNextSection(
                        &rUnoCursor.GetPoint()->nNode, true, false);
                if (pCNode)
                {
                    rUnoCursor.GetPoint()->nContent.Assign(pCNode, 0);
                }
            }
        }
    }
    else if (   (CursorType::Frame   == m_pImpl->m_eType)
            ||  (CursorType::TableText == m_pImpl->m_eType)
            ||  (CursorType::Header  == m_pImpl->m_eType)
            ||  (CursorType::Footer  == m_pImpl->m_eType)
            ||  (CursorType::Footnote== m_pImpl->m_eType)
            ||  (CursorType::Redline == m_pImpl->m_eType))
    {
        rUnoCursor.MoveSection(GoCurrSection, fnSectionStart);
    }
    else if (CursorType::Meta == m_pImpl->m_eType)
    {
        lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, META_INIT_START);
    }
}
 
void SAL_CALL
SwXTextCursor::gotoEnd(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("gotoEnd");
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    if (CursorType::Body == m_pImpl->m_eType)
    {
        rUnoCursor.Move( fnMoveForward, GoInDoc );
    }
    else if (   (CursorType::Frame   == m_pImpl->m_eType)
            ||  (CursorType::TableText == m_pImpl->m_eType)
            ||  (CursorType::Header  == m_pImpl->m_eType)
            ||  (CursorType::Footer  == m_pImpl->m_eType)
            ||  (CursorType::Footnote== m_pImpl->m_eType)
            ||  (CursorType::Redline == m_pImpl->m_eType))
    {
        rUnoCursor.MoveSection( GoCurrSection, fnSectionEnd);
    }
    else if (CursorType::Meta == m_pImpl->m_eType)
    {
        lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText, META_INIT_END);
    }
}
 
void SAL_CALL
SwXTextCursor::gotoRange(
    const uno::Reference< text::XTextRange > & xRange, sal_Bool bExpand)
{
    SolarMutexGuard aGuard;
 
    if (!xRange.is())
    {
        throw uno::RuntimeException();
    }
 
    SwUnoCursor & rOwnCursor( m_pImpl->GetCursorOrThrow() );
 
    uno::Reference<lang::XUnoTunnel> xRangeTunnel( xRange, uno::UNO_QUERY);
    SwXTextRange* pRange = nullptr;
    OTextCursorHelper* pCursor = nullptr;
    if(xRangeTunnel.is())
    {
        pRange  = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel);
        pCursor =
            ::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel);
    }
 
    if (!pRange && !pCursor)
    {
        throw uno::RuntimeException();
    }
 
    SwPaM aPam(GetDoc()->GetNodes());
    const SwPaM * pPam(nullptr);
    if (pCursor)
    {
        pPam = pCursor->GetPaM();
    }
    else if (pRange)
    {
        if (pRange->GetPositions(aPam))
        {
            pPam = & aPam;
        }
    }
 
    if (!pPam)
    {
        throw uno::RuntimeException();
    }
 
    {
        SwStartNodeType eSearchNodeType = SwNormalStartNode;
        switch (m_pImpl->m_eType)
        {
        case CursorType::Frame:      eSearchNodeType = SwFlyStartNode;       break;
        case CursorType::TableText:    eSearchNodeType = SwTableBoxStartNode;  break;
        case CursorType::Footnote:   eSearchNodeType = SwFootnoteStartNode;  break;
        case CursorType::Header:     eSearchNodeType = SwHeaderStartNode;    break;
        case CursorType::Footer:     eSearchNodeType = SwFooterStartNode;    break;
            //case CURSOR_INVALID:
            //case CursorType::Body:
        default:
            ;
        }
 
        const SwStartNode* pOwnStartNode = rOwnCursor.GetNode().FindSttNodeByType(eSearchNodeType);
        while ( pOwnStartNode != nullptr
                && pOwnStartNode->IsSectionNode())
        {
            pOwnStartNode = pOwnStartNode->StartOfSectionNode();
        }
 
        const SwStartNode* pTmp =
            pPam->GetNode().FindSttNodeByType(eSearchNodeType);
        while ( pTmp != nullptr
                && pTmp->IsSectionNode() )
        {
            pTmp = pTmp->StartOfSectionNode();
        }
 
        if ( eSearchNodeType == SwTableBoxStartNode )
        {
            if (!pOwnStartNode || !pTmp)
            {
                throw uno::RuntimeException();
            }
 
            if ( pOwnStartNode->FindTableNode() != pTmp->FindTableNode() )
            {
                throw uno::RuntimeException();
            }
        }
        else
        {
            if ( pOwnStartNode != pTmp )
            {
                throw uno::RuntimeException();
            }
        }
    }
 
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        SwPaM CopyPam(*pPam->GetMark(), *pPam->GetPoint());
        const bool bNotForced( lcl_ForceIntoMeta(
                    CopyPam, m_pImpl->m_xParentText, META_CHECK_BOTH) );
        if (!bNotForced)
        {
            throw uno::RuntimeException(
                "gotoRange: parameter range not contained in nesting"
                    " text content for which this cursor was created",
                static_cast<text::XWordCursor*>(this));
        }
    }
 
    // selection has to be expanded here
    if(bExpand)
    {
        // cursor should include its previous range plus the given range
        const SwPosition aOwnLeft(*rOwnCursor.Start());
        const SwPosition aOwnRight(*rOwnCursor.End());
        SwPosition const& rParamLeft  = *pPam->Start();
        SwPosition const& rParamRight = *pPam->End();
 
        // now there are four SwPositions,
        // two of them are going to be used, but which ones?
        *rOwnCursor.GetPoint() = (aOwnRight > rParamRight)
            ? aOwnRight : *rOwnCursor.GetPoint() = rParamRight;
        rOwnCursor.SetMark();
        *rOwnCursor.GetMark() = (aOwnLeft < rParamLeft)
            ? aOwnLeft : *rOwnCursor.GetMark() = rParamLeft;
    }
    else
    {
        // cursor should be the given range
        *rOwnCursor.GetPoint() = *pPam->GetPoint();
        if (pPam->HasMark())
        {
            rOwnCursor.SetMark();
            *rOwnCursor.GetMark() = *pPam->GetMark();
        }
        else
        {
            rOwnCursor.DeleteMark();
        }
    }
}
 
sal_Bool SAL_CALL SwXTextCursor::isStartOfWord()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const bool bRet =
        rUnoCursor.IsStartWordWT( i18n::WordType::DICTIONARY_WORD );
    return bRet;
}
 
sal_Bool SAL_CALL SwXTextCursor::isEndOfWord()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const bool bRet =
        rUnoCursor.IsEndWordWT( i18n::WordType::DICTIONARY_WORD );
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoNextWord(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    // problems arise when a paragraph starts with something other than a word
    bool bRet = false;
    // remember old position to check if cursor has moved
    // since the called functions are sometimes a bit unreliable
    // in specific cases...
    SwPosition  *const pPoint     = rUnoCursor.GetPoint();
    SwNode      *const pOldNode   = &pPoint->nNode.GetNode();
    sal_Int32 const nOldIndex  = pPoint->nContent.GetIndex();
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    // end of paragraph
    if (rUnoCursor.GetContentNode() &&
            (pPoint->nContent == rUnoCursor.GetContentNode()->Len()))
    {
        rUnoCursor.Right(1);
    }
    else
    {
        const bool bTmp =
            rUnoCursor.GoNextWordWT( i18n::WordType::DICTIONARY_WORD );
        // if there is no next word within the current paragraph
        // try to go to the start of the next paragraph
        if (!bTmp)
        {
            rUnoCursor.MovePara(GoNextPara, fnParaStart);
        }
    }
 
    // return true if cursor has moved
    bRet =  (&pPoint->nNode.GetNode() != pOldNode)  ||
            (pPoint->nContent.GetIndex() != nOldIndex);
    if (bRet && (CursorType::Meta == m_pImpl->m_eType))
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH);
    }
 
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoPreviousWord(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    // white spaces create problems on the paragraph start
    bool bRet = false;
    SwPosition  *const pPoint     = rUnoCursor.GetPoint();
    SwNode      *const pOldNode   = &pPoint->nNode.GetNode();
    sal_Int32 const nOldIndex  = pPoint->nContent.GetIndex();
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    // start of paragraph?
    if (pPoint->nContent == 0)
    {
        rUnoCursor.Left(1);
    }
    else
    {
        rUnoCursor.GoPrevWordWT( i18n::WordType::DICTIONARY_WORD );
        if (pPoint->nContent == 0)
        {
            rUnoCursor.Left(1);
        }
    }
 
    // return true if cursor has moved
    bRet =  (&pPoint->nNode.GetNode() != pOldNode)  ||
            (pPoint->nContent.GetIndex() != nOldIndex);
    if (bRet && (CursorType::Meta == m_pImpl->m_eType))
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH);
    }
 
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoEndOfWord(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    bool bRet = false;
    SwPosition  *const pPoint     = rUnoCursor.GetPoint();
    SwNode      &      rOldNode   = pPoint->nNode.GetNode();
    sal_Int32 const nOldIndex  = pPoint->nContent.GetIndex();
 
    const sal_Int16 nWordType = i18n::WordType::DICTIONARY_WORD;
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    if (!rUnoCursor.IsEndWordWT( nWordType ))
    {
        rUnoCursor.GoEndWordWT( nWordType );
    }
 
    // restore old cursor if we are not at the end of a word by now
    // otherwise use current one
    bRet = rUnoCursor.IsEndWordWT( nWordType );
    if (!bRet)
    {
        pPoint->nNode       = rOldNode;
        pPoint->nContent    = nOldIndex;
    }
    else if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH);
    }
 
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoStartOfWord(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    bool bRet = false;
    SwPosition  *const pPoint     = rUnoCursor.GetPoint();
    SwNode      &      rOldNode   = pPoint->nNode.GetNode();
    sal_Int32 const nOldIndex  = pPoint->nContent.GetIndex();
 
    const sal_Int16 nWordType = i18n::WordType::DICTIONARY_WORD;
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    if (!rUnoCursor.IsStartWordWT( nWordType ))
    {
        rUnoCursor.GoStartWordWT( nWordType );
    }
 
    // restore old cursor if we are not at the start of a word by now
    // otherwise use current one
    bRet = rUnoCursor.IsStartWordWT( nWordType );
    if (!bRet)
    {
        pPoint->nNode       = rOldNode;
        pPoint->nContent    = nOldIndex;
    }
    else if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH);
    }
 
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::isStartOfSentence()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    // start of paragraph?
    bool bRet = rUnoCursor.GetPoint()->nContent == 0;
    // with mark ->no sentence start
    // (check if cursor is no selection, i.e. it does not have
    // a mark or else point and mark are identical)
    if (!bRet && (!rUnoCursor.HasMark() ||
                    *rUnoCursor.GetPoint() == *rUnoCursor.GetMark()))
    {
        SwCursor aCursor(*rUnoCursor.GetPoint(),nullptr);
        SwPosition aOrigPos = *aCursor.GetPoint();
        aCursor.GoSentence(SwCursor::START_SENT );
        bRet = aOrigPos == *aCursor.GetPoint();
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::isEndOfSentence()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    // end of paragraph?
    bool bRet = rUnoCursor.GetContentNode() &&
        (rUnoCursor.GetPoint()->nContent == rUnoCursor.GetContentNode()->Len());
    // with mark->no sentence end
    // (check if cursor is no selection, i.e. it does not have
    // a mark or else point and mark are identical)
    if (!bRet && (!rUnoCursor.HasMark() ||
                    *rUnoCursor.GetPoint() == *rUnoCursor.GetMark()))
    {
        SwCursor aCursor(*rUnoCursor.GetPoint(), nullptr);
        SwPosition aOrigPos = *aCursor.GetPoint();
        aCursor.GoSentence(SwCursor::END_SENT);
        bRet = aOrigPos == *aCursor.GetPoint();
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoNextSentence(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const bool bWasEOS = isEndOfSentence();
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    bool bRet = rUnoCursor.GoSentence(SwCursor::NEXT_SENT);
    if (!bRet)
    {
        bRet = rUnoCursor.MovePara(GoNextPara, fnParaStart);
    }
 
    // if at the end of the sentence (i.e. at the space after the '.')
    // advance to next word in order for GoSentence to work properly
    // next time and have isStartOfSentence return true after this call
    if (!rUnoCursor.IsStartWord(css::i18n::WordType::ANYWORD_IGNOREWHITESPACES))
    {
        const bool bNextWord = rUnoCursor.GoNextWord();
        if (bWasEOS && !bNextWord)
        {
            bRet = false;
        }
    }
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH)
            && bRet;
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoPreviousSentence(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    bool bRet = rUnoCursor.GoSentence(SwCursor::PREV_SENT);
    if (!bRet)
    {
        bRet = rUnoCursor.MovePara(GoPrevPara, fnParaStart);
        if (bRet)
        {
            rUnoCursor.MovePara(GoCurrPara, fnParaEnd);
            // at the end of a paragraph move to the sentence end again
            rUnoCursor.GoSentence(SwCursor::PREV_SENT);
        }
    }
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH)
            && bRet;
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoStartOfSentence(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    // if we're at the para start then we won't move
    // but bRet is also true if GoSentence failed but
    // the start of the sentence is reached
    bool bRet = SwUnoCursorHelper::IsStartOfPara(rUnoCursor)
        || rUnoCursor.GoSentence(SwCursor::START_SENT)
        || SwUnoCursorHelper::IsStartOfPara(rUnoCursor);
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH)
            && bRet;
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoEndOfSentence(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    // bRet is true if GoSentence() succeeded or if the
    // MovePara() succeeded while the end of the para is
    // not reached already
    bool bAlreadyParaEnd = SwUnoCursorHelper::IsEndOfPara(rUnoCursor);
    bool bRet = !bAlreadyParaEnd
            &&  (rUnoCursor.GoSentence(SwCursor::END_SENT)
                 || rUnoCursor.MovePara(GoCurrPara, fnParaEnd));
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        bRet = lcl_ForceIntoMeta(rUnoCursor, m_pImpl->m_xParentText,
                    META_CHECK_BOTH)
            && bRet;
    }
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::isStartOfParagraph()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const bool bRet = SwUnoCursorHelper::IsStartOfPara(rUnoCursor);
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::isEndOfParagraph()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const bool bRet = SwUnoCursorHelper::IsEndOfPara(rUnoCursor);
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoStartOfParagraph(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        return false;
    }
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    bool bRet = SwUnoCursorHelper::IsStartOfPara(rUnoCursor);
    if (!bRet)
    {
        bRet = rUnoCursor.MovePara(GoCurrPara, fnParaStart);
    }
 
    // since MovePara(GoCurrPara, fnParaStart) only returns false
    // if we were already at the start of the paragraph this function
    // should always complete successfully.
    OSL_ENSURE( bRet, "gotoStartOfParagraph failed" );
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoEndOfParagraph(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        return false;
    }
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    bool bRet = SwUnoCursorHelper::IsEndOfPara(rUnoCursor);
    if (!bRet)
    {
        bRet = rUnoCursor.MovePara(GoCurrPara, fnParaEnd);
    }
 
    // since MovePara(GoCurrPara, fnParaEnd) only returns false
    // if we were already at the end of the paragraph this function
    // should always complete successfully.
    OSL_ENSURE( bRet, "gotoEndOfParagraph failed" );
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoNextParagraph(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        return false;
    }
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    const bool bRet = rUnoCursor.MovePara(GoNextPara, fnParaStart);
    return bRet;
}
 
sal_Bool SAL_CALL
SwXTextCursor::gotoPreviousParagraph(sal_Bool Expand)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        return false;
    }
    SwUnoCursorHelper::SelectPam(rUnoCursor, Expand);
    const bool bRet = rUnoCursor.MovePara(GoPrevPara, fnParaStart);
    return bRet;
}
 
uno::Reference< text::XText > SAL_CALL
SwXTextCursor::getText()
{
    SolarMutexGuard g;
 
    return m_pImpl->m_xParentText;
}
 
uno::Reference< text::XTextRange > SAL_CALL
SwXTextCursor::getStart()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    uno::Reference< text::XTextRange > xRet;
    SwPaM aPam(*rUnoCursor.Start());
    const uno::Reference< text::XText >  xParent = getText();
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        // return cursor to prevent modifying SwXTextRange for META
        SwXTextCursor * const pXCursor(
            new SwXTextCursor(*rUnoCursor.GetDoc(), xParent, CursorType::Meta,
                *rUnoCursor.GetPoint()) );
        pXCursor->gotoStart(false);
        xRet = static_cast<text::XWordCursor*>(pXCursor);
    }
    else
    {
        xRet = new SwXTextRange(aPam, xParent);
    }
    return xRet;
}
 
uno::Reference< text::XTextRange > SAL_CALL
SwXTextCursor::getEnd()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    uno::Reference< text::XTextRange >  xRet;
    SwPaM aPam(*rUnoCursor.End());
    const uno::Reference< text::XText >  xParent = getText();
    if (CursorType::Meta == m_pImpl->m_eType)
    {
        // return cursor to prevent modifying SwXTextRange for META
        SwXTextCursor * const pXCursor(
            new SwXTextCursor(*rUnoCursor.GetDoc(), xParent, CursorType::Meta,
                *rUnoCursor.GetPoint()) );
        pXCursor->gotoEnd(false);
        xRet = static_cast<text::XWordCursor*>(pXCursor);
    }
    else
    {
        xRet = new SwXTextRange(aPam, xParent);
    }
    return xRet;
}
 
OUString SAL_CALL SwXTextCursor::getString()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    OUString aText;
    SwUnoCursorHelper::GetTextFromPam(rUnoCursor, aText);
    return aText;
}
 
void SAL_CALL
SwXTextCursor::setString(const OUString& aString)
{
    SolarMutexGuard aGuard;
 
    m_pImpl->GetCursorOrThrow(); // just to check if valid
 
    const bool bForceExpandHints( (CursorType::Meta == m_pImpl->m_eType)
        && dynamic_cast<SwXMeta*>(m_pImpl->m_xParentText.get())
                ->CheckForOwnMemberMeta(*GetPaM(), true) );
    DeleteAndInsert(aString, bForceExpandHints);
}
 
uno::Any SwUnoCursorHelper::GetPropertyValue(
    SwPaM& rPaM, const SfxItemPropertySet& rPropSet,
    const OUString& rPropertyName)
{
    uno::Any aAny;
    SfxItemPropertySimpleEntry const*const pEntry =
        rPropSet.getPropertyMap().getByName(rPropertyName);
 
    if (!pEntry)
    {
        throw beans::UnknownPropertyException(
            "Unknown property: " + rPropertyName,
            static_cast<cppu::OWeakObject *>(nullptr));
    }
 
    beans::PropertyState eTemp;
    const bool bDone = SwUnoCursorHelper::getCursorPropertyValue(
            *pEntry, rPaM, &aAny, eTemp );
 
    if (!bDone)
    {
        SfxItemSet aSet(
            rPaM.GetDoc()->GetAttrPool(),
            svl::Items<
                RES_CHRATR_BEGIN, RES_FRMATR_END - 1,
                RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER>{});
        SwUnoCursorHelper::GetCursorAttr(rPaM, aSet);
 
        rPropSet.getPropertyValue(*pEntry, aSet, aAny);
    }
 
    return aAny;
}
 
void SwUnoCursorHelper::SetPropertyValue(
    SwPaM& rPaM, const SfxItemPropertySet& rPropSet,
    const OUString& rPropertyName,
    const uno::Any& rValue,
    const SetAttrMode nAttrMode)
{
    uno::Sequence< beans::PropertyValue > aValues(1);
    aValues[0].Name = rPropertyName;
    aValues[0].Value = rValue;
    SetPropertyValues(rPaM, rPropSet, aValues, nAttrMode);
}
 
// FN_UNO_PARA_STYLE is known to set attributes for nodes, inside
// SwUnoCursorHelper::SetTextFormatColl, instead of extending item set.
// We need to get them from nodes in next call to GetCursorAttr.
// The rest could cause similar problems in theory, so we just list them here.
inline bool propertyCausesSideEffectsInNodes(sal_uInt16 nWID)
{
    return nWID == FN_UNO_PARA_STYLE ||
           nWID == FN_UNO_CHARFMT_SEQUENCE ||
           nWID == FN_UNO_NUM_START_VALUE ||
           nWID == FN_UNO_NUM_RULES;
}
 
void SwUnoCursorHelper::SetPropertyValues(
    SwPaM& rPaM, const SfxItemPropertySet& rPropSet,
    const uno::Sequence< beans::PropertyValue > &rPropertyValues,
    const SetAttrMode nAttrMode)
{
    if (!rPropertyValues.getLength())
        return;
 
    SwDoc *const pDoc = rPaM.GetDoc();
    OUString aUnknownExMsg, aPropertyVetoExMsg;
 
    // Build set of attributes we want to fetch
    std::vector<sal_uInt16> aWhichPairs;
    std::vector<SfxItemPropertySimpleEntry const*> aEntries;
    aEntries.reserve(rPropertyValues.getLength());
    for (sal_Int32 i = 0; i < rPropertyValues.getLength(); ++i)
    {
        const OUString &rPropertyName = rPropertyValues[i].Name;
 
        SfxItemPropertySimpleEntry const* pEntry =
            rPropSet.getPropertyMap().getByName(rPropertyName);
 
        // Queue up any exceptions until the end ...
        if (!pEntry)
        {
            aUnknownExMsg += "Unknown property: '" + rPropertyName + "' ";
            break;
        }
        else if (pEntry->nFlags & beans::PropertyAttribute::READONLY)
        {
            aPropertyVetoExMsg += "Property is read-only: '" + rPropertyName + "' ";
            break;
        } else {
// FIXME: we should have some nice way of merging ranges surely ?
            aWhichPairs.push_back(pEntry->nWID);
            aWhichPairs.push_back(pEntry->nWID);
        }
        aEntries.push_back(pEntry);
    }
 
    if (!aWhichPairs.empty())
    {
        aWhichPairs.push_back(0); // terminate
        SfxItemSet aItemSet(pDoc->GetAttrPool(), &aWhichPairs[0]);
 
        // Fetch, overwrite, and re-set the attributes from the core
 
        bool bPreviousPropertyCausesSideEffectsInNodes = false;
        for (size_t i = 0; i < aEntries.size(); ++i)
        {
            SfxItemPropertySimpleEntry const*const pEntry = aEntries[i];
            bool bPropertyCausesSideEffectsInNodes =
                propertyCausesSideEffectsInNodes(pEntry->nWID);
 
            // we need to get up-to-date item set from nodes
            if (i == 0 || bPreviousPropertyCausesSideEffectsInNodes)
                SwUnoCursorHelper::GetCursorAttr(rPaM, aItemSet);
 
            const uno::Any &rValue = rPropertyValues[i].Value;
            // this can set some attributes in nodes' mpAttrSet
            if (!SwUnoCursorHelper::SetCursorPropertyValue(*pEntry, rValue, rPaM, aItemSet))
                rPropSet.setPropertyValue(*pEntry, rValue, aItemSet);
 
            if (i + 1 == aEntries.size() || bPropertyCausesSideEffectsInNodes)
                SwUnoCursorHelper::SetCursorAttr(rPaM, aItemSet, nAttrMode, false/*bTableMode*/);
 
            bPreviousPropertyCausesSideEffectsInNodes = bPropertyCausesSideEffectsInNodes;
        }
    }
 
    if (!aUnknownExMsg.isEmpty())
        throw beans::UnknownPropertyException(aUnknownExMsg, static_cast<cppu::OWeakObject *>(nullptr));
    if (!aPropertyVetoExMsg.isEmpty())
        throw beans::PropertyVetoException(aPropertyVetoExMsg, static_cast<cppu::OWeakObject *>(nullptr));
}
 
uno::Sequence< beans::PropertyState >
SwUnoCursorHelper::GetPropertyStates(
            SwPaM& rPaM, const SfxItemPropertySet& rPropSet,
            const uno::Sequence< OUString >& rPropertyNames,
            const SwGetPropertyStatesCaller eCaller)
{
    const OUString* pNames = rPropertyNames.getConstArray();
    uno::Sequence< beans::PropertyState > aRet(rPropertyNames.getLength());
    beans::PropertyState* pStates = aRet.getArray();
    const SfxItemPropertyMap &rMap = rPropSet.getPropertyMap();
    std::unique_ptr<SfxItemSet> pSet;
    std::unique_ptr<SfxItemSet> pSetParent;
 
    for (sal_Int32 i = 0, nEnd = rPropertyNames.getLength(); i < nEnd; i++)
    {
        SfxItemPropertySimpleEntry const*const pEntry =
                rMap.getByName( pNames[i] );
        if(!pEntry)
        {
            if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT ||
                pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
            {
                pStates[i] = beans::PropertyState_DEFAULT_VALUE;
                continue;
            }
            else if (SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT ==
                        eCaller)
            {
                //this values marks the element as unknown property
                pStates[i] = beans::PropertyState::PropertyState_MAKE_FIXED_SIZE;
                continue;
            }
            else
            {
                throw beans::UnknownPropertyException(
                    "Unknown property: " + pNames[i],
                    static_cast<cppu::OWeakObject *>(nullptr));
            }
        }
        if (((SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION == eCaller)  ||
             (SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT == eCaller)) &&
            pEntry->nWID < FN_UNO_RANGE_BEGIN &&
            pEntry->nWID > FN_UNO_RANGE_END  &&
            pEntry->nWID < RES_CHRATR_BEGIN &&
            pEntry->nWID > RES_TXTATR_END )
        {
            pStates[i] = beans::PropertyState_DEFAULT_VALUE;
        }
        else
        {
            if ( pEntry->nWID >= FN_UNO_RANGE_BEGIN &&
                 pEntry->nWID <= FN_UNO_RANGE_END )
            {
                (void)SwUnoCursorHelper::getCursorPropertyValue(
                    *pEntry, rPaM, nullptr, pStates[i] );
            }
            else
            {
                if (!pSet.get())
                {
                    switch ( eCaller )
                    {
                        case SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION_TOLERANT:
                        case SW_PROPERTY_STATE_CALLER_SWX_TEXT_PORTION:
                            pSet.reset(
                                new SfxItemSet( rPaM.GetDoc()->GetAttrPool(),
                                    svl::Items<RES_CHRATR_BEGIN,   RES_TXTATR_END>{} ));
                        break;
                        case SW_PROPERTY_STATE_CALLER_SINGLE_VALUE_ONLY:
                            pSet.reset(
                                new SfxItemSet( rPaM.GetDoc()->GetAttrPool(),
                                    {{pEntry->nWID, pEntry->nWID}} ));
                        break;
                        default:
                            pSet.reset( new SfxItemSet(
                                rPaM.GetDoc()->GetAttrPool(),
                                svl::Items<
                                    RES_CHRATR_BEGIN, RES_FRMATR_END - 1,
                                    RES_UNKNOWNATR_CONTAINER,
                                        RES_UNKNOWNATR_CONTAINER>{}));
                    }
                    // #i63870#
                    SwUnoCursorHelper::GetCursorAttr( rPaM, *pSet );
                }
 
                pStates[i] = ( pSet->Count() )
                    ? rPropSet.getPropertyState( *pEntry, *pSet )
                    : beans::PropertyState_DEFAULT_VALUE;
 
                //try again to find out if a value has been inherited
                if( beans::PropertyState_DIRECT_VALUE == pStates[i] )
                {
                    if (!pSetParent)
                    {
                        pSetParent = pSet->Clone( false );
                        // #i63870#
                        SwUnoCursorHelper::GetCursorAttr(
                                rPaM, *pSetParent, true, false );
                    }
 
                    pStates[i] = ( pSetParent->Count() )
                        ? rPropSet.getPropertyState( *pEntry, *pSetParent )
                        : beans::PropertyState_DEFAULT_VALUE;
                }
            }
        }
    }
    return aRet;
}
 
beans::PropertyState SwUnoCursorHelper::GetPropertyState(
    SwPaM& rPaM, const SfxItemPropertySet& rPropSet,
    const OUString& rPropertyName)
{
    uno::Sequence< OUString > aStrings { rPropertyName };
    uno::Sequence< beans::PropertyState > aSeq =
        GetPropertyStates(rPaM, rPropSet, aStrings,
                SW_PROPERTY_STATE_CALLER_SINGLE_VALUE_ONLY );
    return aSeq[0];
}
 
static void
lcl_SelectParaAndReset( SwPaM &rPaM, SwDoc & rDoc,
                        std::set<sal_uInt16> const &rWhichIds )
{
    // if we are resetting paragraph attributes, we need to select the full paragraph first
    SwPosition aStart = *rPaM.Start();
    SwPosition aEnd = *rPaM.End();
    auto pTemp ( rDoc.CreateUnoCursor(aStart) );
    if(!SwUnoCursorHelper::IsStartOfPara(*pTemp))
    {
        pTemp->MovePara(GoCurrPara, fnParaStart);
    }
    pTemp->SetMark();
    *pTemp->GetPoint() = aEnd;
    SwUnoCursorHelper::SelectPam(*pTemp, true);
    if(!SwUnoCursorHelper::IsEndOfPara(*pTemp))
    {
        pTemp->MovePara(GoCurrPara, fnParaEnd);
    }
    rDoc.ResetAttrs(*pTemp, true, rWhichIds);
}
 
void SwUnoCursorHelper::SetPropertyToDefault(
    SwPaM& rPaM, const SfxItemPropertySet& rPropSet,
    const OUString& rPropertyName)
{
    SwDoc & rDoc = *rPaM.GetDoc();
    SfxItemPropertySimpleEntry const*const pEntry =
        rPropSet.getPropertyMap().getByName(rPropertyName);
    if (!pEntry)
    {
        throw beans::UnknownPropertyException(
            "Unknown property: " + rPropertyName,
            static_cast<cppu::OWeakObject *>(nullptr));
    }
 
    if (pEntry->nFlags & beans::PropertyAttribute::READONLY)
    {
        throw uno::RuntimeException(
                "setPropertyToDefault: property is read-only: "
                + rPropertyName, nullptr);
    }
 
    if (pEntry->nWID < RES_FRMATR_END)
    {
        std::set<sal_uInt16> aWhichIds;
        aWhichIds.insert( pEntry->nWID );
        if (pEntry->nWID < RES_PARATR_BEGIN)
        {
            rDoc.ResetAttrs(rPaM, true, aWhichIds);
        }
        else
        {
            lcl_SelectParaAndReset ( rPaM, rDoc, aWhichIds );
        }
    }
    else
    {
        SwUnoCursorHelper::resetCursorPropertyValue(*pEntry, rPaM);
    }
}
 
uno::Any SwUnoCursorHelper::GetPropertyDefault(
    SwPaM const & rPaM, const SfxItemPropertySet& rPropSet,
    const OUString& rPropertyName)
{
    SfxItemPropertySimpleEntry const*const pEntry =
        rPropSet.getPropertyMap().getByName(rPropertyName);
    if (!pEntry)
    {
        throw beans::UnknownPropertyException(
            "Unknown property: " + rPropertyName, static_cast<cppu::OWeakObject *>(nullptr));
    }
 
    uno::Any aRet;
    if (pEntry->nWID < RES_FRMATR_END)
    {
        SwDoc & rDoc = *rPaM.GetDoc();
        const SfxPoolItem& rDefItem =
            rDoc.GetAttrPool().GetDefaultItem(pEntry->nWID);
        rDefItem.QueryValue(aRet, pEntry->nMemberId);
    }
    return aRet;
}
 
uno::Reference< beans::XPropertySetInfo > SAL_CALL
SwXTextCursor::getPropertySetInfo()
{
    SolarMutexGuard g;
 
    static uno::Reference< beans::XPropertySetInfo >  xRef;
    if(!xRef.is())
    {
        static SfxItemPropertyMapEntry const aCursorExtMap_Impl[] =
        {
            { OUString(UNO_NAME_IS_SKIP_HIDDEN_TEXT), FN_SKIP_HIDDEN_TEXT, cppu::UnoType<bool>::get(), PROPERTY_NONE,     0},
            { OUString(UNO_NAME_IS_SKIP_PROTECTED_TEXT), FN_SKIP_PROTECTED_TEXT, cppu::UnoType<bool>::get(), PROPERTY_NONE,     0},
            { OUString(), 0, css::uno::Type(), 0, 0 }
        };
        const uno::Reference< beans::XPropertySetInfo >  xInfo =
            m_pImpl->m_rPropSet.getPropertySetInfo();
        // extend PropertySetInfo!
        const uno::Sequence<beans::Property> aPropSeq = xInfo->getProperties();
        xRef = new SfxExtItemPropertySetInfo(
            aCursorExtMap_Impl,
            aPropSeq );
    }
    return xRef;
}
 
void SAL_CALL
SwXTextCursor::setPropertyValue(
        const OUString& rPropertyName, const uno::Any& rValue)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (rPropertyName == UNO_NAME_IS_SKIP_HIDDEN_TEXT)
    {
        bool bSet(false);
        if (!(rValue >>= bSet))
        {
            throw lang::IllegalArgumentException();
        }
        rUnoCursor.SetSkipOverHiddenSections(bSet);
    }
    else if (rPropertyName == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
    {
        bool bSet(false);
        if (!(rValue >>= bSet))
        {
            throw lang::IllegalArgumentException();
        }
        rUnoCursor.SetSkipOverProtectSections(bSet);
    }
    else
    {
        SwUnoCursorHelper::SetPropertyValue(rUnoCursor,
                m_pImpl->m_rPropSet, rPropertyName, rValue);
    }
}
 
uno::Any SAL_CALL
SwXTextCursor::getPropertyValue(const OUString& rPropertyName)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    uno::Any aAny;
    if (rPropertyName == UNO_NAME_IS_SKIP_HIDDEN_TEXT)
    {
        const bool bSet = rUnoCursor.IsSkipOverHiddenSections();
        aAny <<= bSet;
    }
    else if (rPropertyName == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
    {
        const bool bSet = rUnoCursor.IsSkipOverProtectSections();
        aAny <<= bSet;
    }
    else
    {
        aAny = SwUnoCursorHelper::GetPropertyValue(rUnoCursor,
                m_pImpl->m_rPropSet, rPropertyName);
    }
    return aAny;
}
 
void SAL_CALL
SwXTextCursor::addPropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextCursor::addPropertyChangeListener(): not implemented");
}
 
void SAL_CALL
SwXTextCursor::removePropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextCursor::removePropertyChangeListener(): not implemented");
}
 
void SAL_CALL
SwXTextCursor::addVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextCursor::addVetoableChangeListener(): not implemented");
}
 
void SAL_CALL
SwXTextCursor::removeVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextCursor::removeVetoableChangeListener(): not implemented");
}
 
beans::PropertyState SAL_CALL
SwXTextCursor::getPropertyState(const OUString& rPropertyName)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const beans::PropertyState eRet = SwUnoCursorHelper::GetPropertyState(
            rUnoCursor, m_pImpl->m_rPropSet, rPropertyName);
    return eRet;
}
 
uno::Sequence< beans::PropertyState > SAL_CALL
SwXTextCursor::getPropertyStates(
        const uno::Sequence< OUString >& rPropertyNames)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    return SwUnoCursorHelper::GetPropertyStates(
            rUnoCursor, m_pImpl->m_rPropSet, rPropertyNames);
}
 
void SAL_CALL
SwXTextCursor::setPropertyToDefault(const OUString& rPropertyName)
{
    // forward: need no solar mutex here
    uno::Sequence < OUString > aSequence ( &rPropertyName, 1 );
    setPropertiesToDefault ( aSequence );
}
 
uno::Any SAL_CALL
SwXTextCursor::getPropertyDefault(const OUString& rPropertyName)
{
    // forward: need no solar mutex here
    const uno::Sequence < OUString > aSequence ( &rPropertyName, 1 );
    return getPropertyDefaults ( aSequence ).getConstArray()[0];
}
 
void SAL_CALL SwXTextCursor::setPropertyValues(
    const uno::Sequence< OUString >& aPropertyNames,
    const uno::Sequence< uno::Any >& aValues )
{
    if( aValues.getLength() != aPropertyNames.getLength() )
    {
        OSL_FAIL( "mis-matched property value sequences" );
        throw lang::IllegalArgumentException();
    }
 
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    // a little lame to have to copy into this.
    uno::Sequence< beans::PropertyValue > aPropertyValues( aValues.getLength() );
    for ( sal_Int32 i = 0; i < aPropertyNames.getLength(); i++ )
    {
        if ( aPropertyNames[ i ] == UNO_NAME_IS_SKIP_HIDDEN_TEXT ||
             aPropertyNames[ i ] == UNO_NAME_IS_SKIP_PROTECTED_TEXT )
        {
            // the behaviour of these is hard to model in a group
            OSL_FAIL("invalid property name for batch setting");
            throw lang::IllegalArgumentException();
        }
        aPropertyValues[ i ].Name = aPropertyNames[ i ];
        aPropertyValues[ i ].Value = aValues[ i ];
    }
    try
    {
        SwUnoCursorHelper::SetPropertyValues( rUnoCursor, m_pImpl->m_rPropSet, aPropertyValues );
    }
    catch (const css::beans::UnknownPropertyException& e)
    {
        uno::Any a(cppu::getCaughtException());
        throw lang::WrappedTargetException(
            "wrapped Exception " + e.Message,
            uno::Reference<uno::XInterface>(), a);
    }
}
 
uno::Sequence< uno::Any > SAL_CALL
SwXTextCursor::getPropertyValues( const uno::Sequence< OUString >& aPropertyNames )
{
    // a banal implementation for now
    uno::Sequence< uno::Any > aValues( aPropertyNames.getLength() );
    for (sal_Int32 i = 0; i < aPropertyNames.getLength(); i++)
        aValues[i] = getPropertyValue( aPropertyNames[ i ] );
    return aValues;
}
 
void SAL_CALL SwXTextCursor::addPropertiesChangeListener(
        const uno::Sequence< OUString >& /* aPropertyNames */,
        const uno::Reference< css::beans::XPropertiesChangeListener >& /* xListener */ )
{
    OSL_FAIL("SwXTextCursor::addPropertiesChangeListener(): not implemented");
}
void SAL_CALL SwXTextCursor::removePropertiesChangeListener(
        const uno::Reference< css::beans::XPropertiesChangeListener >& /* xListener */ )
{
    OSL_FAIL("SwXTextCursor::removePropertiesChangeListener(): not implemented");
}
 
void SAL_CALL SwXTextCursor::firePropertiesChangeEvent(
        const uno::Sequence< OUString >& /* aPropertyNames */,
        const uno::Reference< css::beans::XPropertiesChangeListener >& /* xListener */ )
{
    OSL_FAIL("SwXTextCursor::firePropertiesChangeEvent(): not implemented");
}
 
// para specific attribute ranges
static sal_uInt16 g_ParaResetableSetRange[] = {
    RES_FRMATR_BEGIN, RES_FRMATR_END-1,
    RES_PARATR_BEGIN, RES_PARATR_END-1,
    RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1,
    RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1,
    0
};
 
// selection specific attribute ranges
static sal_uInt16 g_ResetableSetRange[] = {
    RES_CHRATR_BEGIN, RES_CHRATR_END-1,
    RES_TXTATR_INETFMT, RES_TXTATR_INETFMT,
    RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT,
    RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY,
    RES_TXTATR_UNKNOWN_CONTAINER, RES_TXTATR_UNKNOWN_CONTAINER,
    0
};
 
static void
lcl_EnumerateIds(sal_uInt16 const* pIdRange, std::set<sal_uInt16> &rWhichIds)
{
    while (*pIdRange)
    {
        const sal_uInt16 nStart = *pIdRange++;
        const sal_uInt16 nEnd   = *pIdRange++;
        for (sal_uInt16 nId = nStart + 1;  nId <= nEnd;  ++nId)
        {
            rWhichIds.insert( rWhichIds.end(), nId );
        }
    }
}
 
void SAL_CALL
SwXTextCursor::setAllPropertiesToDefault()
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    std::set<sal_uInt16> aParaWhichIds;
    std::set<sal_uInt16> aWhichIds;
    lcl_EnumerateIds(g_ParaResetableSetRange, aParaWhichIds);
    lcl_EnumerateIds(g_ResetableSetRange, aWhichIds);
    if (!aParaWhichIds.empty())
    {
        lcl_SelectParaAndReset(rUnoCursor, *rUnoCursor.GetDoc(),
            aParaWhichIds);
    }
    if (!aWhichIds.empty())
    {
        rUnoCursor.GetDoc()->ResetAttrs(rUnoCursor, true, aWhichIds);
    }
}
 
void SAL_CALL
SwXTextCursor::setPropertiesToDefault(
        const uno::Sequence< OUString >& rPropertyNames)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const sal_Int32 nCount = rPropertyNames.getLength();
    if ( nCount )
    {
        SwDoc & rDoc = *rUnoCursor.GetDoc();
        const OUString * pNames = rPropertyNames.getConstArray();
        std::set<sal_uInt16> aWhichIds;
        std::set<sal_uInt16> aParaWhichIds;
        for (sal_Int32 i = 0; i < nCount; i++)
        {
            SfxItemPropertySimpleEntry const*const  pEntry =
                m_pImpl->m_rPropSet.getPropertyMap().getByName( pNames[i] );
            if (!pEntry)
            {
                if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT ||
                    pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
                {
                    continue;
                }
                throw beans::UnknownPropertyException(
                    "Unknown property: " + pNames[i],
                    static_cast<cppu::OWeakObject *>(this));
            }
            if (pEntry->nFlags & beans::PropertyAttribute::READONLY)
            {
                throw uno::RuntimeException(
                    "setPropertiesToDefault: property is read-only: " + pNames[i],
                    static_cast<cppu::OWeakObject *>(this));
            }
 
            if (pEntry->nWID < RES_FRMATR_END)
            {
                if (pEntry->nWID < RES_PARATR_BEGIN)
                {
                    aWhichIds.insert( pEntry->nWID );
                }
                else
                {
                    aParaWhichIds.insert( pEntry->nWID );
                }
            }
            else if (pEntry->nWID == FN_UNO_NUM_START_VALUE)
            {
                SwUnoCursorHelper::resetCursorPropertyValue(*pEntry, rUnoCursor);
            }
        }
 
        if (!aParaWhichIds.empty())
        {
            lcl_SelectParaAndReset(rUnoCursor, rDoc, aParaWhichIds);
        }
        if (!aWhichIds.empty())
        {
            rDoc.ResetAttrs(rUnoCursor, true, aWhichIds);
        }
    }
}
 
uno::Sequence< uno::Any > SAL_CALL
SwXTextCursor::getPropertyDefaults(
        const uno::Sequence< OUString >& rPropertyNames)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const sal_Int32 nCount = rPropertyNames.getLength();
    uno::Sequence< uno::Any > aRet(nCount);
    if ( nCount )
    {
        SwDoc & rDoc = *rUnoCursor.GetDoc();
        const OUString *pNames = rPropertyNames.getConstArray();
        uno::Any *pAny = aRet.getArray();
        for (sal_Int32 i = 0; i < nCount; i++)
        {
            SfxItemPropertySimpleEntry const*const pEntry =
                m_pImpl->m_rPropSet.getPropertyMap().getByName( pNames[i] );
            if (!pEntry)
            {
                if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT ||
                    pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
                {
                    continue;
                }
                throw beans::UnknownPropertyException(
                    "Unknown property: " + pNames[i],
                    static_cast<cppu::OWeakObject *>(nullptr));
            }
            if (pEntry->nWID < RES_FRMATR_END)
            {
                const SfxPoolItem& rDefItem =
                    rDoc.GetAttrPool().GetDefaultItem(pEntry->nWID);
                rDefItem.QueryValue(pAny[i], pEntry->nMemberId);
            }
        }
    }
    return aRet;
}
 
void SAL_CALL SwXTextCursor::invalidateMarkings(::sal_Int32 nType)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwNode& node = rUnoCursor.GetNode();
 
    SwTextNode* txtNode = node.GetTextNode();
 
    if (txtNode == nullptr) return;
 
    if ( text::TextMarkupType::SPELLCHECK == nType )
    {
        txtNode->SetWrongDirty(SwTextNode::WrongState::TODO);
        txtNode->SetWrong(nullptr);
    }
    else if( text::TextMarkupType::PROOFREADING == nType )
    {
        txtNode->SetGrammarCheckDirty(true);
        txtNode->SetGrammarCheck(nullptr);
    }
    else if ( text::TextMarkupType::SMARTTAG == nType )
    {
        txtNode->SetSmartTagDirty(true);
        txtNode->SetSmartTags(nullptr);
    }
    else return;
 
    SwFormatColl* fmtColl=txtNode->GetFormatColl();
 
    if (fmtColl == nullptr) return;
 
    SwFormatChg aNew( fmtColl );
    txtNode->NotifyClients( nullptr, &aNew );
}
 
void SAL_CALL
SwXTextCursor::makeRedline(
    const OUString& rRedlineType,
    const uno::Sequence< beans::PropertyValue >& rRedlineProperties)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::makeRedline(rUnoCursor, rRedlineType, rRedlineProperties);
}
 
void SAL_CALL SwXTextCursor::insertDocumentFromURL(const OUString& rURL,
    const uno::Sequence< beans::PropertyValue >& rOptions)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    SwUnoCursorHelper::InsertFile(&rUnoCursor, rURL, rOptions);
}
 
uno::Sequence< beans::PropertyValue >
SwUnoCursorHelper::CreateSortDescriptor(const bool bFromTable)
{
    uno::Sequence< beans::PropertyValue > aRet(5);
    beans::PropertyValue* pArray = aRet.getArray();
 
    uno::Any aVal;
    aVal <<= bFromTable;
    pArray[0] = beans::PropertyValue("IsSortInTable", -1, aVal,
                    beans::PropertyState_DIRECT_VALUE);
 
    aVal <<= u' ';
    pArray[1] = beans::PropertyValue("Delimiter", -1, aVal,
                    beans::PropertyState_DIRECT_VALUE);
 
    aVal <<= false;
    pArray[2] = beans::PropertyValue("IsSortColumns", -1, aVal,
                    beans::PropertyState_DIRECT_VALUE);
 
    aVal <<= sal_Int32(3);
    pArray[3] = beans::PropertyValue("MaxSortFieldsCount", -1, aVal,
                    beans::PropertyState_DIRECT_VALUE);
 
    uno::Sequence< table::TableSortField > aFields(3);
    table::TableSortField* pFields = aFields.getArray();
 
    lang::Locale aLang( SvtSysLocale().GetLanguageTag().getLocale());
    // get collator algorithm to be used for the locale
    uno::Sequence< OUString > aSeq(
            GetAppCollator().listCollatorAlgorithms( aLang ) );
    const sal_Int32 nLen = aSeq.getLength();
    OSL_ENSURE( nLen > 0, "list of collator algorithms is empty!");
    OUString aCollAlg;
    if (nLen > 0)
    {
        aCollAlg = aSeq.getConstArray()[0];
    }
 
    pFields[0].Field = 1;
    pFields[0].IsAscending = true;
    pFields[0].IsCaseSensitive = false;
    pFields[0].FieldType = table::TableSortFieldType_ALPHANUMERIC;
    pFields[0].CollatorLocale = aLang;
    pFields[0].CollatorAlgorithm = aCollAlg;
 
    pFields[1].Field = 1;
    pFields[1].IsAscending = true;
    pFields[1].IsCaseSensitive = false;
    pFields[1].FieldType = table::TableSortFieldType_ALPHANUMERIC;
    pFields[1].CollatorLocale = aLang;
    pFields[1].CollatorAlgorithm = aCollAlg;
 
    pFields[2].Field = 1;
    pFields[2].IsAscending = true;
    pFields[2].IsCaseSensitive = false;
    pFields[2].FieldType = table::TableSortFieldType_ALPHANUMERIC;
    pFields[2].CollatorLocale = aLang;
    pFields[2].CollatorAlgorithm = aCollAlg;
 
    aVal <<= aFields;
    pArray[4] = beans::PropertyValue("SortFields", -1, aVal,
                    beans::PropertyState_DIRECT_VALUE);
 
    return aRet;
}
 
uno::Sequence< beans::PropertyValue > SAL_CALL
SwXTextCursor::createSortDescriptor()
{
    SolarMutexGuard aGuard;
 
    return SwUnoCursorHelper::CreateSortDescriptor(false);
}
 
bool SwUnoCursorHelper::ConvertSortProperties(
    const uno::Sequence< beans::PropertyValue >& rDescriptor,
    SwSortOptions& rSortOpt)
{
    bool bRet = true;
    const beans::PropertyValue* pProperties = rDescriptor.getConstArray();
 
    rSortOpt.bTable = false;
    rSortOpt.cDeli = ' ';
    rSortOpt.eDirection = SRT_COLUMNS;  //!! UI text may be contrary though !!
 
    std::unique_ptr<SwSortKey> pKey1(new SwSortKey);
    pKey1->nColumnId = USHRT_MAX;
    pKey1->bIsNumeric = true;
    pKey1->eSortOrder = SRT_ASCENDING;
 
    std::unique_ptr<SwSortKey> pKey2(new SwSortKey);
    pKey2->nColumnId = USHRT_MAX;
    pKey2->bIsNumeric = true;
    pKey2->eSortOrder = SRT_ASCENDING;
 
    std::unique_ptr<SwSortKey> pKey3(new SwSortKey);
    pKey3->nColumnId = USHRT_MAX;
    pKey3->bIsNumeric = true;
    pKey3->eSortOrder = SRT_ASCENDING;
    SwSortKey* aKeys[3] = {pKey1.get(), pKey2.get(), pKey3.get()};
 
    bool bOldSortdescriptor(false);
    bool bNewSortdescriptor(false);
 
    for (sal_Int32 n = 0; n < rDescriptor.getLength(); ++n)
    {
        uno::Any aValue( pProperties[n].Value );
        const OUString& rPropName = pProperties[n].Name;
 
        // old and new sortdescriptor
        if ( rPropName == "IsSortInTable" )
        {
            if (auto b = o3tl::tryAccess<bool>(aValue))
            {
                rSortOpt.bTable = *b;
            }
            else
            {
                bRet = false;
            }
        }
        else if ( rPropName == "Delimiter" )
        {
            sal_Unicode uChar;
            sal_uInt16 nChar;
            if (aValue >>= uChar)
            {
                rSortOpt.cDeli = uChar;
            }
            else if (aValue >>= nChar)
            {
                // For compatibility with BASIC, also accept an ANY containing
                // an UNSIGNED SHORT:
                rSortOpt.cDeli = nChar;
            }
            else
            {
                bRet = false;
            }
        }
        // old sortdescriptor
        else if ( rPropName == "SortColumns" )
        {
            bOldSortdescriptor = true;
            bool bTemp(false);
            if (aValue >>= bTemp)
            {
                rSortOpt.eDirection = bTemp ? SRT_COLUMNS : SRT_ROWS;
            }
            else
            {
                bRet = false;
            }
        }
        else if ( rPropName == "IsCaseSensitive" )
        {
            bOldSortdescriptor = true;
            bool bTemp(false);
            if (aValue >>= bTemp)
            {
                rSortOpt.bIgnoreCase = !bTemp;
            }
            else
            {
                bRet = false;
            }
        }
        else if ( rPropName == "CollatorLocale" )
        {
            bOldSortdescriptor = true;
            lang::Locale aLocale;
            if (aValue >>= aLocale)
            {
                rSortOpt.nLanguage = LanguageTag::convertToLanguageType( aLocale);
            }
            else
            {
                bRet = false;
            }
        }
        else if (rPropName.startsWith("CollatorAlgorithm") &&
            rPropName.getLength() == 18 &&
            (rPropName[17] >= '0' && rPropName[17] <= '9'))
        {
            bOldSortdescriptor = true;
            sal_uInt16 nIndex = rPropName[17];
            nIndex -= '0';
            OUString aText;
            if ((aValue >>= aText) && nIndex < 3)
            {
                aKeys[nIndex]->sSortType = aText;
            }
            else
            {
                bRet = false;
            }
        }
        else if (rPropName.startsWith("SortRowOrColumnNo") &&
            rPropName.getLength() == 18 &&
            (rPropName[17] >= '0' && rPropName[17] <= '9'))
        {
            bOldSortdescriptor = true;
            sal_uInt16 nIndex = rPropName[17];
            nIndex -= '0';
            sal_Int16 nCol = -1;
            if (aValue.getValueType() == ::cppu::UnoType<sal_Int16>::get()
                && nIndex < 3)
            {
                aValue >>= nCol;
            }
            if (nCol >= 0)
            {
                aKeys[nIndex]->nColumnId = nCol;
            }
            else
            {
                bRet = false;
            }
        }
        else if (rPropName.startsWith("IsSortNumeric") &&
            rPropName.getLength() == 14 &&
            (rPropName[13] >= '0' && rPropName[13] <= '9'))
        {
            bOldSortdescriptor = true;
            sal_uInt16 nIndex = rPropName[13];
            nIndex = nIndex - '0';
            auto bTemp = o3tl::tryAccess<bool>(aValue);
            if (bTemp && nIndex < 3)
            {
                aKeys[nIndex]->bIsNumeric = *bTemp;
            }
            else
            {
                bRet = false;
            }
        }
        else if (rPropName.startsWith("IsSortAscending") &&
            rPropName.getLength() == 16 &&
            (rPropName[15] >= '0' && rPropName[15] <= '9'))
        {
            bOldSortdescriptor = true;
            sal_uInt16 nIndex = rPropName[15];
            nIndex -= '0';
            auto bTemp = o3tl::tryAccess<bool>(aValue);
            if (bTemp && nIndex < 3)
            {
                aKeys[nIndex]->eSortOrder = (*bTemp)
                    ? SRT_ASCENDING : SRT_DESCENDING;
            }
            else
            {
                bRet = false;
            }
        }
        // new sortdescriptor
        else if ( rPropName == "IsSortColumns" )
        {
            bNewSortdescriptor = true;
            if (auto bTemp = o3tl::tryAccess<bool>(aValue))
            {
                rSortOpt.eDirection = *bTemp ? SRT_COLUMNS : SRT_ROWS;
            }
            else
            {
                bRet = false;
            }
        }
        else if ( rPropName == "SortFields" )
        {
            bNewSortdescriptor = true;
            uno::Sequence < table::TableSortField > aFields;
            if (aValue >>= aFields)
            {
                sal_Int32 nCount(aFields.getLength());
                if (nCount <= 3)
                {
                    table::TableSortField* pFields = aFields.getArray();
                    for (sal_Int32 i = 0; i < nCount; ++i)
                    {
                        rSortOpt.bIgnoreCase = !pFields[i].IsCaseSensitive;
                        rSortOpt.nLanguage =
                            LanguageTag::convertToLanguageType( pFields[i].CollatorLocale );
                        aKeys[i]->sSortType = pFields[i].CollatorAlgorithm;
                        aKeys[i]->nColumnId =
                            static_cast<sal_uInt16>(pFields[i].Field);
                        aKeys[i]->bIsNumeric = (pFields[i].FieldType ==
                                table::TableSortFieldType_NUMERIC);
                        aKeys[i]->eSortOrder = (pFields[i].IsAscending)
                            ? SRT_ASCENDING : SRT_DESCENDING;
                    }
                }
                else
                {
                    bRet = false;
                }
            }
            else
            {
                bRet = false;
            }
        }
    }
 
    if (bNewSortdescriptor && bOldSortdescriptor)
    {
        OSL_FAIL("someone tried to set the old deprecated and "
            "the new sortdescriptor");
        bRet = false;
    }
 
    if (pKey1->nColumnId != USHRT_MAX)
    {
        rSortOpt.aKeys.push_back(std::move(pKey1));
    }
    if (pKey2->nColumnId != USHRT_MAX)
    {
        rSortOpt.aKeys.push_back(std::move(pKey2));
    }
    if (pKey3->nColumnId != USHRT_MAX)
    {
        rSortOpt.aKeys.push_back(std::move(pKey3));
    }
 
    return bRet && !rSortOpt.aKeys.empty();
}
 
void SAL_CALL
SwXTextCursor::sort(const uno::Sequence< beans::PropertyValue >& rDescriptor)
{
    SolarMutexGuard aGuard;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    if (!rUnoCursor.HasMark())
        return;
 
    SwSortOptions aSortOpt;
    if (!SwUnoCursorHelper::ConvertSortProperties(rDescriptor, aSortOpt))
    {
        throw uno::RuntimeException("Bad sort properties");
    }
    UnoActionContext aContext( rUnoCursor.GetDoc() );
 
    SwPosition & rStart = *rUnoCursor.Start();
    SwPosition & rEnd   = *rUnoCursor.End();
 
    SwNodeIndex aPrevIdx( rStart.nNode, -1 );
    const sal_uLong nOffset = rEnd.nNode.GetIndex() - rStart.nNode.GetIndex();
    const sal_Int32 nCntStt  = rStart.nContent.GetIndex();
 
    rUnoCursor.GetDoc()->SortText(rUnoCursor, aSortOpt);
 
    // update selection
    rUnoCursor.DeleteMark();
    rUnoCursor.GetPoint()->nNode.Assign( aPrevIdx.GetNode(), +1 );
    SwContentNode *const pCNd = rUnoCursor.GetContentNode();
    sal_Int32 nLen = pCNd->Len();
    if (nLen > nCntStt)
    {
        nLen = nCntStt;
    }
    rUnoCursor.GetPoint()->nContent.Assign(pCNd, nLen );
    rUnoCursor.SetMark();
 
    rUnoCursor.GetPoint()->nNode += nOffset;
    SwContentNode *const pCNd2 = rUnoCursor.GetContentNode();
    rUnoCursor.GetPoint()->nContent.Assign( pCNd2, pCNd2->Len() );
 
}
 
uno::Reference< container::XEnumeration > SAL_CALL
SwXTextCursor::createContentEnumeration(const OUString& rServiceName)
{
    SolarMutexGuard g;
    if (rServiceName != "com.sun.star.text.TextContent")
        throw uno::RuntimeException();
    SwUnoCursor& rUnoCursor( m_pImpl->GetCursorOrThrow() );
    return SwXParaFrameEnumeration::Create(rUnoCursor, PARAFRAME_PORTION_TEXTRANGE);
}
 
uno::Reference< container::XEnumeration > SAL_CALL
SwXTextCursor::createEnumeration()
{
    SolarMutexGuard g;
 
    SwUnoCursor & rUnoCursor( m_pImpl->GetCursorOrThrow() );
 
    const uno::Reference<lang::XUnoTunnel> xTunnel(
            m_pImpl->m_xParentText, uno::UNO_QUERY);
    SwXText* pParentText = nullptr;
    if (xTunnel.is())
    {
        pParentText = ::sw::UnoTunnelGetImplementation<SwXText>(xTunnel);
    }
    OSL_ENSURE(pParentText, "parent is not a SwXText");
    if (!pParentText)
    {
        throw uno::RuntimeException();
    }
 
    auto pNewCursor(rUnoCursor.GetDoc()->CreateUnoCursor(*rUnoCursor.GetPoint()) );
    if (rUnoCursor.HasMark())
    {
        pNewCursor->SetMark();
        *pNewCursor->GetMark() = *rUnoCursor.GetMark();
    }
    const CursorType eSetType = (CursorType::TableText == m_pImpl->m_eType)
            ? CursorType::SelectionInTable : CursorType::Selection;
    SwTableNode const*const pStartNode( (CursorType::TableText == m_pImpl->m_eType)
            ? rUnoCursor.GetPoint()->nNode.GetNode().FindTableNode()
            : nullptr);
    SwTable const*const pTable(
            pStartNode ? & pStartNode->GetTable() : nullptr );
    return SwXParagraphEnumeration::Create(pParentText, pNewCursor, eSetType, pStartNode, pTable);
}
 
uno::Type SAL_CALL
SwXTextCursor::getElementType()
{
    return cppu::UnoType<text::XTextRange>::get();
}
 
sal_Bool SAL_CALL SwXTextCursor::hasElements()
{
    return true;
}
 
uno::Sequence< OUString > SAL_CALL
SwXTextCursor::getAvailableServiceNames()
{
    uno::Sequence<OUString> aRet { "com.sun.star.text.TextContent" };
    return aRet;
}
 
IMPLEMENT_FORWARD_REFCOUNT( SwXTextCursor,SwXTextCursor_Base )
 
uno::Any SAL_CALL
SwXTextCursor::queryInterface(const uno::Type& rType)
{
    return (rType == cppu::UnoType<lang::XUnoTunnel>::get())
        ? OTextCursorHelper::queryInterface(rType)
        : SwXTextCursor_Base::queryInterface(rType);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V590 Consider inspecting this expression. The expression is excessive or contains a misprint.

V560 A part of conditional expression is always false: pEntry->nWID > ((20000 + 2400) + 199).