/* -*- 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 <unocrsrhelper.hxx>
 
#include <map>
#include <algorithm>
#include <memory>
 
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/PropertyState.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/io/IOException.hpp>
#include <com/sun/star/text/XTextSection.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
 
#include <svx/unoshape.hxx>
 
#include <cmdid.h>
#include <hintids.hxx>
#include <unotextrange.hxx>
#include <unodraw.hxx>
#include <unofootnote.hxx>
#include <unobookmark.hxx>
#include <unomap.hxx>
#include <unorefmark.hxx>
#include <unoidx.hxx>
#include <unofield.hxx>
#include <unotbl.hxx>
#include <unosection.hxx>
#include <unosett.hxx>
#include <unoframe.hxx>
#include <unocrsr.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <fmtftn.hxx>
#include <charfmt.hxx>
#include <pagedesc.hxx>
#include <docstyle.hxx>
#include <ndtxt.hxx>
#include <docsh.hxx>
#include <section.hxx>
#include <shellio.hxx>
#include <edimp.hxx>
#include <swundo.hxx>
#include <cntfrm.hxx>
#include <pagefrm.hxx>
#include <svl/eitem.hxx>
#include <svl/lngmisc.hxx>
#include <swtable.hxx>
#include <tox.hxx>
#include <doctxm.hxx>
#include <fchrfmt.hxx>
#include <editeng/editids.hrc>
#include <editeng/flstitem.hxx>
#include <editeng/prntitem.hxx>
#include <vcl/metric.hxx>
#include <svtools/ctrltool.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/fcontnr.hxx>
#include <svl/stritem.hxx>
#include <SwStyleNameMapper.hxx>
#include <redline.hxx>
#include <numrule.hxx>
#include <comphelper/storagehelper.hxx>
#include <unotools/mediadescriptor.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <SwNodeNum.hxx>
#include <fmtmeta.hxx>
#include <txtfld.hxx>
#include <unoparagraph.hxx>
#include <poolfmt.hxx>
#include <paratr.hxx>
#include <sal/log.hxx>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::lang;
 
namespace SwUnoCursorHelper
{
 
static void lcl_createPamCopy(std::optional<SwPaM>& o_rpPam, const SwPaM& rPam)
{
    o_rpPam.emplace(*rPam.GetPoint());
    ::sw::DeepCopyPaM(rPam, *o_rpPam);
}
 
void GetSelectableFromAny(uno::Reference<uno::XInterface> const& xIfc,
        SwDoc & rTargetDoc,
        std::optional<SwPaM>& o_rpPaM, std::pair<OUString, FlyCntType> & o_rFrame,
        OUString & o_rTableName, SwUnoTableCursor const*& o_rpTableCursor,
        ::sw::mark::MarkBase const*& o_rpMark,
        std::vector<SdrObject *> & o_rSdrObjects)
{
    uno::Reference<drawing::XShapes> const xShapes(xIfc, UNO_QUERY);
    if (xShapes.is())
    {
        sal_Int32 nShapes(xShapes->getCount());
        for (sal_Int32 i = 0; i < nShapes; ++i)
        {
            uno::Reference<lang::XUnoTunnel> xShape;
            xShapes->getByIndex(i) >>= xShape;
            if (xShape.is())
            {
                SvxShape *const pSvxShape(
                        comphelper::getFromUnoTunnel<SvxShape>(xShape));
                if (pSvxShape)
                {
                    SdrObject *const pSdrObject = pSvxShape->GetSdrObject();
                    if (pSdrObject)
                    {   // hmm... needs view to verify it's in right doc...
                        o_rSdrObjects.push_back(pSdrObject);
                    }
                }
            }
        }
        return;
    }
 
    uno::Reference<lang::XUnoTunnel> const xTunnel(xIfc, UNO_QUERY);
 
    SwXShape *const pShape(comphelper::getFromUnoTunnel<SwXShape>(xTunnel));
    if (pShape)
    {
        if (SvxShape * pSvxShape = pShape->GetSvxShape())
        {
            SdrObject *const pSdrObject = pSvxShape->GetSdrObject();
            if (pSdrObject)
            {   // hmm... needs view to verify it's in right doc...
                o_rSdrObjects.push_back(pSdrObject);
            }
        }
        return;
    }
 
    OTextCursorHelper *const pCursor(
        dynamic_cast<OTextCursorHelper*>(xIfc.get()));
    if (pCursor)
    {
        if (pCursor->GetDoc() == &rTargetDoc)
        {
            lcl_createPamCopy(o_rpPaM, *pCursor->GetPaM());
        }
        return;
    }
 
    SwXTextRanges* const pRanges = dynamic_cast<SwXTextRanges*>(xIfc.get());
    if (pRanges)
    {
        SwUnoCursor const* pUnoCursor = pRanges->GetCursor();
        if (pUnoCursor && &pUnoCursor->GetDoc() == &rTargetDoc)
        {
            lcl_createPamCopy(o_rpPaM, *pUnoCursor);
        }
        return;
    }
 
    // check these before Range to prevent misinterpretation of text frames
    // and cells also implement XTextRange
    SwXFrame *const pFrame = dynamic_cast<SwXFrame*>(xIfc.get());
    if (pFrame)
    {
        const SwFrameFormat *const pFrameFormat(pFrame->GetFrameFormat());
        if (pFrameFormat && pFrameFormat->GetDoc() == &rTargetDoc)
        {
            o_rFrame = std::make_pair(pFrameFormat->GetName(), pFrame->GetFlyCntType());
        }
        return;
    }
 
    SwXTextTable *const pTextTable = dynamic_cast<SwXTextTable*>(xIfc.get());
    if (pTextTable)
    {
        SwFrameFormat *const pFrameFormat(pTextTable->GetFrameFormat());
        if (pFrameFormat && pFrameFormat->GetDoc() == &rTargetDoc)
        {
            o_rTableName = pFrameFormat->GetName();
        }
        return;
    }
 
    SwXCell *const pCell = dynamic_cast<SwXCell*>(xIfc.get());
    if (pCell)
    {
        SwFrameFormat *const pFrameFormat(pCell->GetFrameFormat());
        if (pFrameFormat && pFrameFormat->GetDoc() == &rTargetDoc)
        {
            SwTableBox * pBox = pCell->GetTableBox();
            SwTable *const pTable = SwTable::FindTable(pFrameFormat);
            // ??? what's the benefit of setting pBox in this convoluted way?
            pBox = pCell->FindBox(pTable, pBox);
            if (pBox)
            {
                SwPaM aPam(*pBox->GetSttNd());
                aPam.Move(fnMoveForward, GoInNode);
                lcl_createPamCopy(o_rpPaM, aPam);
            }
        }
        return;
    }
 
    uno::Reference<text::XTextRange> const xTextRange(xIfc, UNO_QUERY);
    if (xTextRange.is())
    {
        SwUnoInternalPaM aPam(rTargetDoc);
        if (::sw::XTextRangeToSwPaM(aPam, xTextRange))
        {
            lcl_createPamCopy(o_rpPaM, aPam);
        }
        return;
    }
 
    SwXCellRange *const pCellRange = dynamic_cast<SwXCellRange*>(xIfc.get());
    if (pCellRange)
    {
        SwUnoCursor const*const pUnoCursor(pCellRange->GetTableCursor());
        if (pUnoCursor && &pUnoCursor->GetDoc() == &rTargetDoc)
        {
            // probably can't copy it to o_rpPaM for this since it's
            // a SwTableCursor
            o_rpTableCursor = dynamic_cast<SwUnoTableCursor const*>(pUnoCursor);
        }
        return;
    }
 
    ::sw::mark::MarkBase const*const pMark =
            SwXBookmark::GetBookmarkInDoc(& rTargetDoc, xIfc);
    if (pMark)
    {
        o_rpMark = pMark;
        return;
    }
}
 
uno::Reference<text::XTextContent>
GetNestedTextContent(SwTextNode const & rTextNode, sal_Int32 const nIndex,
        bool const bParent)
{
    // these should be unambiguous because of the dummy character
    auto const eMode( bParent
        ? ::sw::GetTextAttrMode::Parent : ::sw::GetTextAttrMode::Expand );
    SwTextAttr *const pMetaTextAttr =
        rTextNode.GetTextAttrAt(nIndex, RES_TXTATR_META, eMode);
    SwTextAttr *const pMetaFieldTextAttr =
        rTextNode.GetTextAttrAt(nIndex, RES_TXTATR_METAFIELD, eMode);
    // which is innermost?
    SwTextAttr *const pTextAttr = pMetaTextAttr
        ? (pMetaFieldTextAttr
            ? ((pMetaFieldTextAttr->GetStart() >
                    pMetaTextAttr->GetStart())
                ? pMetaFieldTextAttr : pMetaTextAttr)
            : pMetaTextAttr)
        : pMetaFieldTextAttr;
    uno::Reference<XTextContent> xRet;
    if (pTextAttr)
    {
        ::sw::Meta *const pMeta(
            static_cast<SwFormatMeta &>(pTextAttr->GetAttr()).GetMeta());
        assert(pMeta);
        xRet.set(pMeta->MakeUnoObject(), uno::UNO_QUERY);
    }
    return xRet;
}
 
static uno::Any GetParaListAutoFormat(SwTextNode const& rNode)
{
    SwFormatAutoFormat const*const pFormat(
        rNode.GetSwAttrSet().GetItem<SwFormatAutoFormat>(RES_PARATR_LIST_AUTOFMT, false));
    if (!pFormat)
    {
        return uno::Any();
    }
    const auto pSet(pFormat->GetStyleHandle());
    if (!pSet)
        return {};
    SfxItemPropertySet const& rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE));
    SfxItemPropertyMap const& rMap(rPropSet.getPropertyMap());
    std::vector<beans::NamedValue> props;
    // have to iterate the map, not the item set?
    for (auto const & rPair : rMap.getPropertyEntries())
    {
        const SfxItemPropertyMapEntry* pEntry = rPair.second;
        if (SfxItemPropertySet::getPropertyState(*pEntry, *pSet) == PropertyState_DIRECT_VALUE)
        {
            Any value;
            SfxItemPropertySet::getPropertyValue(*pEntry, *pSet, value);
            props.emplace_back(pEntry->aName, value);
        }
    }
    return uno::Any(comphelper::containerToSequence(props));
}
 
// Read the special properties of the cursor
bool getCursorPropertyValue(const SfxItemPropertyMapEntry& rEntry
                                        , SwPaM& rPam
                                        , Any *pAny
                                        , PropertyState& eState
                                        , const SwTextNode* pNode  )
{
    PropertyState eNewState = PropertyState_DIRECT_VALUE;
    bool bDone = true;
    switch(rEntry.nWID)
    {
        case FN_UNO_PARA_CONT_PREV_SUBTREE:
            if (pAny)
            {
                const SwTextNode * pTmpNode = pNode;
 
                if (!pTmpNode)
                    pTmpNode = rPam.GetPointNode().GetTextNode();
 
                bool bRet = false;
 
                if ( pTmpNode &&
                     pTmpNode->GetNum() &&
                     pTmpNode->GetNum()->IsContinueingPreviousSubTree() )
                {
                    bRet = true;
                }
 
                *pAny <<= bRet;
            }
        break;
        case FN_UNO_PARA_NUM_STRING:
            if (pAny)
            {
                const SwTextNode * pTmpNode = pNode;
 
                if (!pTmpNode)
                    pTmpNode = rPam.GetPointNode().GetTextNode();
 
                OUString sRet;
                if ( pTmpNode && pTmpNode->GetNum() )
                {
                    sRet = pTmpNode->GetNumString();
                }
 
                *pAny <<= sRet;
            }
        break;
        case RES_PARATR_OUTLINELEVEL:
            if (pAny)
            {
                const SwTextNode * pTmpNode = pNode;
 
                if (!pTmpNode)
                    pTmpNode = rPam.GetPointNode().GetTextNode();
 
                sal_Int16 nRet = -1;
                if ( pTmpNode )
                    nRet = sal::static_int_cast< sal_Int16 >( pTmpNode->GetAttrOutlineLevel() );
 
                *pAny <<= nRet;
            }
        break;
        case FN_UNO_PARA_CONDITIONAL_STYLE_NAME:
        case FN_UNO_PARA_STYLE :
        {
            SwFormatColl* pFormat = nullptr;
            if(pNode)
                pFormat = FN_UNO_PARA_CONDITIONAL_STYLE_NAME == rEntry.nWID
                            ? pNode->GetFormatColl() : &pNode->GetAnyFormatColl();
            else
            {
                pFormat = SwUnoCursorHelper::GetCurTextFormatColl(rPam,
                        FN_UNO_PARA_CONDITIONAL_STYLE_NAME == rEntry.nWID);
            }
            if(pFormat)
            {
                if( pAny )
                {
                    OUString sVal;
                    SwStyleNameMapper::FillProgName(pFormat->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
                    *pAny <<= sVal;
                }
            }
            else
                eNewState = PropertyState_AMBIGUOUS_VALUE;
        }
        break;
        case FN_UNO_PAGE_STYLE :
        {
            OUString sVal;
            GetCurPageStyle(rPam, sVal);
            if( pAny )
                *pAny <<= sVal;
            if(sVal.isEmpty())
                eNewState = PropertyState_AMBIGUOUS_VALUE;
        }
        break;
        case FN_UNO_NUM_START_VALUE  :
            if( pAny )
            {
                sal_Int16 nValue = IsNodeNumStart(rPam, eNewState);
                *pAny <<= nValue;
            }
        break;
        case FN_UNO_NUM_LEVEL  :
        case FN_UNO_IS_NUMBER  :
        // #i91601#
        case FN_UNO_LIST_ID:
        case FN_NUMBER_NEWSTART:
        case FN_UNO_PARA_NUM_AUTO_FORMAT:
        {
            if (!pAny)
            {
                break;
            }
            // a multi selection is not considered
            const SwTextNode* pTextNd = rPam.GetPointNode().GetTextNode();
            if ( pTextNd && pTextNd->IsInList() )
            {
                switch (rEntry.nWID)
                {
                    case FN_UNO_NUM_LEVEL:
                    {
                        *pAny <<= static_cast<sal_Int16>(pTextNd->GetActualListLevel());
                        break;
                    }
                    case FN_UNO_IS_NUMBER:
                    {
                        *pAny <<= pTextNd->IsCountedInList();
                        break;
                    }
                    // #i91601#
                    case FN_UNO_LIST_ID:
                    {
                        *pAny <<= pTextNd->GetListId();
                        break;
                    }
                    case FN_NUMBER_NEWSTART:
                    {
                        *pAny <<= pTextNd->IsListRestart();
                        break;
                    }
                    case FN_UNO_PARA_NUM_AUTO_FORMAT:
                    {
                        *pAny = GetParaListAutoFormat(*pTextNd);
                        break;
                    }
                    default:
                        assert(false);
                }
            }
            else
            {
                eNewState = PropertyState_DEFAULT_VALUE;
 
                    // #i30838# set default values for default properties
                switch (rEntry.nWID)
                {
                    case FN_UNO_NUM_LEVEL:
                    {
                        *pAny <<= static_cast<sal_Int16>( 0 );
                        break;
                    }
                    case FN_UNO_IS_NUMBER:
                    {
                        *pAny <<= false;
                        break;
                    }
                    // #i91601#
                    case FN_UNO_LIST_ID:
                    {
                        *pAny <<= OUString();
                        break;
                    }
                    case FN_NUMBER_NEWSTART:
                    {
                        *pAny <<= false;
                        break;
                    }
                    case FN_UNO_PARA_NUM_AUTO_FORMAT:
                    {
                        break; // void
                    }
                    default:
                        assert(false);
                }
            }
            //PROPERTY_MAYBEVOID!
        }
        break;
        case FN_UNO_NUM_RULES  :
            if( pAny )
                getNumberingProperty(rPam, eNewState, pAny);
            else
            {
                if( !SwDoc::GetNumRuleAtPos( *rPam.GetPoint() ) )
                    eNewState = PropertyState_DEFAULT_VALUE;
            }
            break;
        case FN_UNO_DOCUMENT_INDEX_MARK:
        {
            std::vector<SwTextAttr *> marks;
            if (rPam.GetPointNode().IsTextNode())
            {
                marks = rPam.GetPointNode().GetTextNode()->GetTextAttrsAt(
                    rPam.GetPoint()->GetContentIndex(), RES_TXTATR_TOXMARK);
            }
            if (!marks.empty())
            {
                if( pAny )
                {   // hmm... can only return 1 here
                    SwTOXMark & rMark =
                        static_cast<SwTOXMark &>((*marks.begin())->GetAttr());
                    const uno::Reference< text::XDocumentIndexMark > xRef =
                        SwXDocumentIndexMark::CreateXDocumentIndexMark(
                            rPam.GetDoc(), &rMark);
                    (*pAny) <<= xRef;
                }
            }
            else
                //also here - indistinguishable
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_DOCUMENT_INDEX:
        {
            SwTOXBase* pBase = SwDoc::GetCurTOX(
                                                    *rPam.Start() );
            if( pBase )
            {
                if( pAny )
                {
                    const uno::Reference< text::XDocumentIndex > xRef =
                        SwXDocumentIndex::CreateXDocumentIndex(rPam.GetDoc(),
                            static_cast<SwTOXBaseSection *>(pBase));
                    (*pAny) <<= xRef;
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_TEXT_FIELD:
        {
            const SwPosition *pPos = rPam.Start();
            const SwTextNode *pTextNd =
                rPam.GetDoc().GetNodes()[pPos->GetNodeIndex()]->GetTextNode();
            const SwTextAttr* pTextAttr = pTextNd
                ? pTextNd->GetFieldTextAttrAt(pPos->GetContentIndex(), ::sw::GetTextAttrMode::Default)
                : nullptr;
            if ( pTextAttr != nullptr )
            {
                if( pAny )
                {
                    uno::Reference<text::XTextField> const xField(
                        SwXTextField::CreateXTextField(&rPam.GetDoc(),
                           &pTextAttr->GetFormatField()));
                    *pAny <<= xField;
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_TEXT_TABLE:
        case FN_UNO_CELL:
        {
            SwStartNode* pSttNode = rPam.GetPointNode().StartOfSectionNode();
            SwStartNodeType eType = pSttNode->GetStartNodeType();
            if(SwTableBoxStartNode == eType)
            {
                if( pAny )
                {
                    const SwTableNode* pTableNode = pSttNode->FindTableNode();
                    SwFrameFormat* pTableFormat = pTableNode->GetTable().GetFrameFormat();
                    //SwTable& rTable = static_cast<SwTableNode*>(pSttNode)->GetTable();
                    if(FN_UNO_TEXT_TABLE == rEntry.nWID)
                    {
                        uno::Reference< XTextTable >  xTable = SwXTextTables::GetObject(*pTableFormat);
                        *pAny <<= xTable;
                    }
                    else
                    {
                        SwTableBox* pBox = pSttNode->GetTableBox();
                        uno::Reference< XCell >  xCell = SwXCell::CreateXCell(pTableFormat, pBox);
                        *pAny <<= xCell;
                    }
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_TEXT_FRAME:
        {
            SwStartNode* pSttNode = rPam.GetPointNode().StartOfSectionNode();
            SwStartNodeType eType = pSttNode->GetStartNodeType();
 
            SwFrameFormat* pFormat;
            if(eType == SwFlyStartNode && nullptr != (pFormat = pSttNode->GetFlyFormat()))
            {
                // Create a wrapper only for text frames, not for graphic or OLE nodes.
                if (pAny && !rPam.GetPointNode().IsNoTextNode())
                {
                    uno::Reference<XTextFrame> const xFrame(
                        SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat));
                    (*pAny) <<= xFrame;
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_TEXT_SECTION:
        {
            SwSection* pSect = SwDoc::GetCurrSection(*rPam.GetPoint());
            if(pSect)
            {
                if( pAny )
                {
                    rtl::Reference< SwXTextSection > xSect = SwXTextSections::GetObject( *pSect->GetFormat() );
                    *pAny <<= uno::Reference< XTextSection >(xSect);
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_TEXT_PARAGRAPH:
        {
            SwTextNode* pTextNode = rPam.GetPoint()->GetNode().GetTextNode();
            if (pTextNode)
            {
                if (pAny)
                {
                    uno::Reference<text::XTextContent> xParagraph = SwXParagraph::CreateXParagraph(pTextNode->GetDoc(), pTextNode, nullptr);
                    *pAny <<= xParagraph;
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_SORTED_TEXT_ID:
        {
            if( pAny )
            {
                sal_Int32 nIndex = -1;
                SwTextNode* pTextNode = rPam.GetPoint()->GetNode().GetTextNode();
                if ( pTextNode )
                    nIndex = pTextNode->GetIndex().get();
                *pAny <<= nIndex;
            }
        }
        break;
        case FN_UNO_ENDNOTE:
        case FN_UNO_FOOTNOTE:
        {
            SwTextAttr *const pTextAttr = rPam.GetPointNode().IsTextNode() ?
                rPam.GetPointNode().GetTextNode()->GetTextAttrForCharAt(
                    rPam.GetPoint()->GetContentIndex(), RES_TXTATR_FTN) : nullptr;
            if(pTextAttr)
            {
                const SwFormatFootnote& rFootnote = pTextAttr->GetFootnote();
                if(rFootnote.IsEndNote() == (FN_UNO_ENDNOTE == rEntry.nWID))
                {
                    if( pAny )
                    {
                        const uno::Reference< text::XFootnote > xFootnote =
                            SwXFootnote::CreateXFootnote(rPam.GetDoc(),
                                    &const_cast<SwFormatFootnote&>(rFootnote));
                        *pAny <<= xFootnote;
                    }
                }
                else
                    eNewState = PropertyState_DEFAULT_VALUE;
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_REFERENCE_MARK:
        {
            std::vector<SwTextAttr *> marks;
            if (rPam.GetPointNode().IsTextNode())
            {
                marks = rPam.GetPointNode().GetTextNode()->GetTextAttrsAt(
                            rPam.GetPoint()->GetContentIndex(), RES_TXTATR_REFMARK);
            }
            if (!marks.empty())
            {
                if( pAny )
                {   // hmm... can only return 1 here
                    const SwFormatRefMark& rRef = (*marks.begin())->GetRefMark();
                    uno::Reference<XTextContent> const xRef =
                        SwXReferenceMark::CreateXReferenceMark(rPam.GetDoc(),
                                const_cast<SwFormatRefMark*>(&rRef));
                    *pAny <<= xRef;
                }
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case FN_UNO_NESTED_TEXT_CONTENT:
        {
            uno::Reference<XTextContent> const xRet(rPam.GetPointNode().IsTextNode()
                ? GetNestedTextContent(*rPam.GetPointNode().GetTextNode(),
                    rPam.GetPoint()->GetContentIndex(), false)
                : nullptr);
            if (xRet.is())
            {
                if (pAny)
                {
                    (*pAny) <<= xRet;
                }
            }
            else
            {
                eNewState = PropertyState_DEFAULT_VALUE;
            }
        }
        break;
        case FN_UNO_CHARFMT_SEQUENCE:
        {
 
            SwTextNode *const pTextNode = rPam.GetPointNode().GetTextNode();
            if (&rPam.GetPointNode() == &rPam.GetMarkNode()
                && pTextNode && pTextNode->GetpSwpHints())
            {
                sal_Int32 nPaMStart = rPam.Start()->GetContentIndex();
                sal_Int32 nPaMEnd = rPam.End()->GetContentIndex();
                Sequence< OUString> aCharStyles;
                SwpHints* pHints = pTextNode->GetpSwpHints();
                for( size_t nAttr = 0; nAttr < pHints->Count(); ++nAttr )
                {
                    SwTextAttr* pAttr = pHints->Get( nAttr );
                    if(pAttr->Which() != RES_TXTATR_CHARFMT)
                        continue;
                    const sal_Int32 nAttrStart = pAttr->GetStart();
                    const sal_Int32 nAttrEnd = *pAttr->GetEnd();
                    //check if the attribute touches the selection
                    if( ( nAttrEnd > nPaMStart && nAttrStart < nPaMEnd ) ||
                        ( !nAttrStart && !nAttrEnd && !nPaMStart && !nPaMEnd ) )
                    {
                        //check for overlapping
                        if(nAttrStart > nPaMStart ||
                                    nAttrEnd < nPaMEnd)
                        {
                            aCharStyles.realloc(0);
                            break;
                        }
                        else
                        {
                            //now the attribute should start before or at the selection
                            //and it should end at the end of the selection or behind
                            OSL_ENSURE(nAttrStart <= nPaMStart && nAttrEnd >=nPaMEnd,
                                    "attribute overlaps or is outside");
                            //now the name of the style has to be added to the sequence
                            aCharStyles.realloc(aCharStyles.getLength() + 1);
                            OSL_ENSURE(pAttr->GetCharFormat().GetCharFormat(), "no character format set");
                            aCharStyles.getArray()[aCharStyles.getLength() - 1] =
                                        SwStyleNameMapper::GetProgName(
                                            pAttr->GetCharFormat().GetCharFormat()->GetName(), SwGetPoolIdFromName::ChrFmt);
                        }
                    }
 
                }
                eNewState =
                    aCharStyles.hasElements() ?
                        PropertyState_DIRECT_VALUE : PropertyState_DEFAULT_VALUE;
                if(pAny)
                    (*pAny) <<= aCharStyles;
            }
            else
                eNewState = PropertyState_DEFAULT_VALUE;
        }
        break;
        case RES_TXTATR_CHARFMT:
        // no break here!
        default: bDone = false;
    }
    if( bDone )
        eState = eNewState;
    return bDone;
};
 
sal_Int16 IsNodeNumStart(SwPaM const & rPam, PropertyState& eState)
{
    const SwTextNode* pTextNd = rPam.GetPointNode().GetTextNode();
    // correction: check, if restart value is set at the text node and use
    // new method <SwTextNode::GetAttrListRestartValue()> to retrieve the value
    if ( pTextNd && pTextNd->GetNumRule() && pTextNd->IsListRestart() &&
         pTextNd->HasAttrListRestartValue() )
    {
        eState = PropertyState_DIRECT_VALUE;
        sal_Int16 nTmp = sal::static_int_cast< sal_Int16 >(pTextNd->GetAttrListRestartValue());
        return nTmp;
    }
    eState = PropertyState_DEFAULT_VALUE;
    return -1;
}
 
void setNumberingProperty(const Any& rValue, SwPaM& rPam)
{
    uno::Reference<XIndexReplace> xIndexReplace;
    if(rValue >>= xIndexReplace)
    {
        auto pSwNum = dynamic_cast<SwXNumberingRules*>(xIndexReplace.get());
        if(pSwNum)
        {
            SwDoc& rDoc = rPam.GetDoc();
            if(pSwNum->GetNumRule())
            {
                SwNumRule aRule(*pSwNum->GetNumRule());
                const OUString* pNewCharStyles =  pSwNum->GetNewCharStyleNames();
                const OUString* pBulletFontNames = pSwNum->GetBulletFontNames();
                for(sal_uInt16 i = 0; i < MAXLEVEL; i++)
                {
                    SwNumFormat aFormat(aRule.Get( i ));
                    if (!pNewCharStyles[i].isEmpty() &&
                        !SwXNumberingRules::isInvalidStyle(pNewCharStyles[i]) &&
                        (!aFormat.GetCharFormat() || pNewCharStyles[i] != aFormat.GetCharFormat()->GetName()))
                    {
                        if (pNewCharStyles[i].isEmpty())
                        {
                            // FIXME
                            // Is something missing/wrong here?
                            // if condition is always false due to outer check!
                            aFormat.SetCharFormat(nullptr);
                        }
                        else
                        {
 
                            // get CharStyle and set the rule
                            const size_t nChCount = rDoc.GetCharFormats()->size();
                            SwCharFormat* pCharFormat = nullptr;
                            for(size_t nCharFormat = 0; nCharFormat < nChCount; ++nCharFormat)
                            {
                                SwCharFormat& rChFormat = *((*(rDoc.GetCharFormats()))[nCharFormat]);
                                if(rChFormat.GetName() == pNewCharStyles[i])
                                {
                                    pCharFormat = &rChFormat;
                                    break;
                                }
                            }
 
                            if(!pCharFormat)
                            {
                                if (SwDocShell* pShell = rDoc.GetDocShell())
                                {
                                    SfxStyleSheetBasePool* pPool = pShell->GetStyleSheetPool();
                                    SfxStyleSheetBase* pBase;
                                    pBase = pPool->Find(pNewCharStyles[i], SfxStyleFamily::Char);
                                    // shall it really be created?
                                    if(!pBase)
                                        pBase = &pPool->Make(pNewCharStyles[i], SfxStyleFamily::Page);
                                    pCharFormat = static_cast<SwDocStyleSheet*>(pBase)->GetCharFormat();
                                }
                            }
                            if(pCharFormat)
                                aFormat.SetCharFormat(pCharFormat);
                        }
                    }
                    //Now again for fonts
                    if(
                       !pBulletFontNames[i].isEmpty() &&
                       !SwXNumberingRules::isInvalidStyle(pBulletFontNames[i]) &&
                       (!aFormat.GetBulletFont() || aFormat.GetBulletFont()->GetFamilyName() != pBulletFontNames[i])
                      )
                    {
                        if (SwDocShell* pShell = rDoc.GetDocShell())
                        {
                            const SvxFontListItem* pFontListItem =
                                    static_cast<const SvxFontListItem* >(
                                                        pShell->GetItem( SID_ATTR_CHAR_FONTLIST ));
                            const FontList* pList = pFontListItem->GetFontList();
 
                            vcl::Font aFont(pList->Get(
                                pBulletFontNames[i],WEIGHT_NORMAL, ITALIC_NONE));
                            aFormat.SetBulletFont(&aFont);
                        }
                    }
                    aRule.Set( i, aFormat );
                }
                UnoActionContext aAction(&rDoc);
 
                if( rPam.GetNext() != &rPam )           // Multiple selection?
                {
                    rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
                    SwPamRanges aRangeArr( rPam );
                    SwPaM aPam( *rPam.GetPoint() );
                    for ( size_t n = 0; n < aRangeArr.Count(); ++n )
                    {
                        // no start of a new list
                        rDoc.SetNumRule(aRangeArr.SetPam( n, aPam ), aRule, SwDoc::SetNumRuleMode::Default);
                    }
                    rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
                }
                else
                {
                    // no start of a new list
                    rDoc.SetNumRule(rPam, aRule, SwDoc::SetNumRuleMode::Default);
                }
 
            }
            else if(!pSwNum->GetCreatedNumRuleName().isEmpty())
            {
                UnoActionContext aAction( &rDoc );
                SwNumRule* pRule = rDoc.FindNumRulePtr( pSwNum->GetCreatedNumRuleName() );
                if ( !pRule )
                    throw RuntimeException();
                // no start of a new list
                rDoc.SetNumRule(rPam, *pRule, SwDoc::SetNumRuleMode::Default);
            }
            else
            {
                // #i103817#
                // outline numbering
                UnoActionContext aAction(&rDoc);
                SwNumRule* pRule = rDoc.GetOutlineNumRule();
                if(!pRule)
                    throw RuntimeException();
                rDoc.SetNumRule(rPam, *pRule, SwDoc::SetNumRuleMode::Default);
            }
        }
    }
    else if ( rValue.getValueType() == cppu::UnoType<void>::get() )
    {
        rPam.GetDoc().DelNumRules(rPam);
    }
}
 
void  getNumberingProperty(SwPaM& rPam, PropertyState& eState, Any * pAny )
{
    const SwNumRule* pNumRule = SwDoc::GetNumRuleAtPos( *rPam.GetPoint() );
    if(pNumRule)
    {
        uno::Reference< XIndexReplace >  xNum = new SwXNumberingRules(*pNumRule);
        if ( pAny )
            *pAny <<= xNum;
        eState = PropertyState_DIRECT_VALUE;
    }
    else
        eState = PropertyState_DEFAULT_VALUE;
}
 
void GetCurPageStyle(SwPaM const & rPaM, OUString &rString)
{
    if (!rPaM.GetPointContentNode())
        return; // TODO: is there an easy way to get it for tables/sections?
    SwRootFrame* pLayout = rPaM.GetDoc().getIDocumentLayoutAccess().GetCurrentLayout();
    // Consider the position inside the content node, since the node may span over multiple pages
    // with different page styles.
    SwContentFrame* pFrame = rPaM.GetPointContentNode()->getLayoutFrame(pLayout, rPaM.GetPoint());
    if(pFrame)
    {
        const SwPageFrame* pPage = pFrame->FindPageFrame();
        if(pPage)
        {
            SwStyleNameMapper::FillProgName(pPage->GetPageDesc()->GetName(),
                rString, SwGetPoolIdFromName::PageDesc);
        }
    }
}
 
// reset special properties of the cursor
void resetCursorPropertyValue(const SfxItemPropertyMapEntry& rEntry, SwPaM& rPam)
{
    SwDoc& rDoc = rPam.GetDoc();
    switch(rEntry.nWID)
    {
        case FN_UNO_PARA_STYLE :
        break;
        case FN_UNO_PAGE_STYLE :
        break;
        case FN_UNO_NUM_START_VALUE  :
        {
            UnoActionContext aAction(&rDoc);
 
            if( rPam.GetNext() != &rPam )           // Multiple selection?
            {
                rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
                SwPamRanges aRangeArr( rPam );
                SwPaM aPam( *rPam.GetPoint() );
                for( size_t n = 0; n < aRangeArr.Count(); ++n )
                    rDoc.SetNodeNumStart( *aRangeArr.SetPam( n, aPam ).GetPoint(), 1 );
                rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
            }
            else
                rDoc.SetNodeNumStart( *rPam.GetPoint(), 0 );
        }
 
        break;
        case FN_UNO_NUM_LEVEL  :
        break;
        case FN_UNO_NUM_RULES:
        break;
        case FN_UNO_CHARFMT_SEQUENCE:
        {
            rDoc.ResetAttrs(rPam, true, { RES_TXTATR_CHARFMT });
        }
        break;
    }
}
 
void InsertFile(SwUnoCursor* pUnoCursor, const OUString& rURL,
    const uno::Sequence< beans::PropertyValue >& rOptions)
{
    if (SwTextNode const*const pTextNode = pUnoCursor->GetPoint()->GetNode().GetTextNode())
    {
        // TODO: check meta field here too in case it ever grows a 2nd char
        if (pTextNode->GetTextAttrAt(pUnoCursor->GetPoint()->GetContentIndex(),
                RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent))
        {
            throw uno::RuntimeException(u"cannot insert file inside input field"_ustr);
        }
 
        if (pTextNode->GetTextAttrAt(pUnoCursor->GetPoint()->GetContentIndex(),
                RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent))
        {
            throw uno::RuntimeException(u"cannot insert file inside content controls"_ustr);
        }
    }
 
    std::unique_ptr<SfxMedium> pMed;
    SwDoc& rDoc = pUnoCursor->GetDoc();
    utl::MediaDescriptor aMediaDescriptor( rOptions );
    OUString sFileName = rURL;
    OUString sFilterName, sFilterOptions, sPassword, sBaseURL;
    uno::Reference < io::XStream > xStream;
    uno::Reference < io::XInputStream > xInputStream;
 
    if( sFileName.isEmpty() )
        aMediaDescriptor[utl::MediaDescriptor::PROP_URL] >>= sFileName;
    if( sFileName.isEmpty() )
        aMediaDescriptor[utl::MediaDescriptor::PROP_FILENAME] >>= sFileName;
    aMediaDescriptor[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream;
    aMediaDescriptor[utl::MediaDescriptor::PROP_STREAM] >>= xStream;
    aMediaDescriptor[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream;
    aMediaDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] >>= sFilterName;
    aMediaDescriptor[utl::MediaDescriptor::PROP_FILTEROPTIONS] >>= sFilterOptions;
    aMediaDescriptor[utl::MediaDescriptor::PROP_PASSWORD] >>= sPassword;
    aMediaDescriptor[utl::MediaDescriptor::PROP_DOCUMENTBASEURL ] >>= sBaseURL;
    if ( !xInputStream.is() && xStream.is() )
        xInputStream = xStream->getInputStream();
 
    SwDocShell* pDocSh = rDoc.GetDocShell();
    if(!pDocSh || (sFileName.isEmpty() && !xInputStream.is()))
        return;
 
    SfxObjectFactory& rFact = pDocSh->GetFactory();
    std::shared_ptr<const SfxFilter> pFilter = rFact.GetFilterContainer()->GetFilter4FilterName( sFilterName );
    uno::Reference < embed::XStorage > xReadStorage;
    if( xInputStream.is() )
    {
        uno::Sequence< uno::Any > aArgs{ uno::Any(xInputStream),
                                         uno::Any(embed::ElementModes::READ) };
        try
        {
            xReadStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ),
                              uno::UNO_QUERY );
        }
        catch( const io::IOException&) {}
    }
    if ( !pFilter )
    {
        if( xInputStream.is() && !xReadStorage.is())
        {
            pMed.reset(new SfxMedium);
            pMed->setStreamToLoadFrom(xInputStream, true );
        }
        else
            pMed.reset(xReadStorage.is() ?
                new SfxMedium(xReadStorage, sBaseURL ) :
                new SfxMedium(sFileName, StreamMode::READ ));
        if( !sBaseURL.isEmpty() )
            pMed->GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, sBaseURL ) );
 
        SfxFilterMatcher aMatcher( rFact.GetFilterContainer()->GetName() );
        ErrCode nErr = aMatcher.GuessFilter(*pMed, pFilter, SfxFilterFlags::NONE);
        if ( nErr || !pFilter)
            return;
        pMed->SetFilter( pFilter );
    }
    else
    {
        if( xInputStream.is() && !xReadStorage.is())
        {
            pMed.reset(new SfxMedium);
            pMed->setStreamToLoadFrom(xInputStream, true );
            pMed->SetFilter( pFilter );
        }
        else
        {
            if( xReadStorage.is() )
            {
                pMed.reset(new SfxMedium(xReadStorage, sBaseURL ));
                pMed->SetFilter( pFilter );
            }
            else
                pMed.reset(new SfxMedium(sFileName, StreamMode::READ, std::move(pFilter), nullptr));
        }
        if(!sFilterOptions.isEmpty())
            pMed->GetItemSet().Put( SfxStringItem( SID_FILE_FILTEROPTIONS, sFilterOptions ) );
        if(!sBaseURL.isEmpty())
            pMed->GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, sBaseURL ) );
    }
 
    // this sourcecode is not responsible for the lifetime of the shell, SfxObjectShellLock should not be used
    SfxObjectShellRef aRef( pDocSh );
 
    pMed->Download();   // if necessary: start the download
    if( !(aRef.is() && 1 < aRef->GetRefCount()) )  // Ref still valid?
        return;
 
    SwReaderPtr pRdr;
    SfxItemSet& rSet =  pMed->GetItemSet();
    rSet.Put(SfxBoolItem(FN_API_CALL, true));
    if(!sPassword.isEmpty())
        rSet.Put(SfxStringItem(SID_PASSWORD, sPassword));
    Reader *pRead = pDocSh->StartConvertFrom( *pMed, pRdr, nullptr, pUnoCursor);
    if( !pRead )
        return;
 
    UnoActionContext aContext(&rDoc);
 
    if(pUnoCursor->HasMark())
        rDoc.getIDocumentContentOperations().DeleteAndJoin(*pUnoCursor);
 
    SwNodeIndex aSave(  pUnoCursor->GetPoint()->GetNode(), -1 );
    sal_Int32 nContent = pUnoCursor->GetPoint()->GetContentIndex();
 
    ErrCodeMsg nErrno = pRdr->Read( *pRead );   // and paste the document
 
    if(!nErrno)
    {
        ++aSave;
        pUnoCursor->SetMark();
        pUnoCursor->GetMark()->Assign( aSave );
 
        SwContentNode* pCntNode = aSave.GetNode().GetContentNode();
        if( !pCntNode )
            nContent = 0;
        pUnoCursor->GetMark()->SetContent( nContent );
    }
}
 
// insert text and scan for CR characters in order to insert
// paragraph breaks at those positions by calling SplitNode
bool DocInsertStringSplitCR(
        SwDoc &rDoc,
        const SwPaM &rNewCursor,
        std::u16string_view rText,
        const bool bForceExpandHints )
{
    bool bOK = true;
 
    for (size_t i = 0; i < rText.size(); ++i)
    {
        sal_Unicode const ch(rText[i]);
        if (linguistic::IsControlChar(ch)
            && ch != '\r' && ch != '\n' && ch != '\t')
        {
            SAL_WARN("sw.uno", "DocInsertStringSplitCR: refusing to insert control character " << int(ch));
            return false;
        }
    }
 
    const SwInsertFlags nInsertFlags =
            bForceExpandHints
            ? ( SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND)
            : SwInsertFlags::EMPTYEXPAND;
 
    // grouping done in InsertString is intended for typing, not API calls
    ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
    SwTextNode* const pTextNd =
        rNewCursor.GetPoint()->GetNode().GetTextNode();
    if (!pTextNd)
    {
        SAL_INFO("sw.uno", "DocInsertStringSplitCR: need a text node");
        return false;
    }
    OUString aText;
    sal_Int32 nStartIdx = 0;
    const sal_Int32 nMaxLength = COMPLETE_STRING - pTextNd->GetText().getLength();
 
    size_t nIdx = rText.find( '\r', nStartIdx );
    if( ( nIdx == std::u16string_view::npos && nMaxLength < sal_Int32(rText.size()) ) ||
        ( nIdx != std::u16string_view::npos && nMaxLength < sal_Int32(nIdx) ) )
    {
        nIdx = nMaxLength;
    }
    while (nIdx != std::u16string_view::npos )
    {
        OSL_ENSURE( sal_Int32(nIdx) - nStartIdx >= 0, "index negative!" );
        aText = rText.substr( nStartIdx, nIdx - nStartIdx );
        if (!aText.isEmpty() &&
            !rDoc.getIDocumentContentOperations().InsertString( rNewCursor, aText, nInsertFlags ))
        {
            OSL_FAIL( "Doc->Insert(Str) failed." );
            bOK = false;
        }
        if (!rDoc.getIDocumentContentOperations().SplitNode( *rNewCursor.GetPoint(), false ) )
        {
            OSL_FAIL( "SplitNode failed" );
            bOK = false;
        }
        nStartIdx = nIdx + 1;
        nIdx = rText.find( '\r', nStartIdx );
    }
    aText = rText.substr( nStartIdx );
    if (!aText.isEmpty() &&
        !rDoc.getIDocumentContentOperations().InsertString( rNewCursor, aText, nInsertFlags ))
    {
        OSL_FAIL( "Doc->Insert(Str) failed." );
        bOK = false;
    }
 
    return bOK;
}
 
void makeRedline( SwPaM const & rPaM,
    std::u16string_view rRedlineType,
    const uno::Sequence< beans::PropertyValue >& rRedlineProperties )
{
    IDocumentRedlineAccess& rRedlineAccess = rPaM.GetDoc().getIDocumentRedlineAccess();
 
    RedlineType eType;
    if      ( rRedlineType == u"Insert" )
        eType = RedlineType::Insert;
    else if ( rRedlineType == u"Delete" )
        eType = RedlineType::Delete;
    else if ( rRedlineType == u"Format" )
        eType = RedlineType::Format;
    else if ( rRedlineType == u"TextTable" )
        eType = RedlineType::Table;
    else if ( rRedlineType == u"ParagraphFormat" )
        eType = RedlineType::ParagraphFormat;
    else
        throw lang::IllegalArgumentException();
 
    //todo: what about REDLINE_FMTCOLL?
    std::size_t nAuthor = 0;
    OUString sAuthor;
    OUString sComment;
    ::util::DateTime aStamp;
    uno::Sequence< beans::PropertyValue > aRevertProperties;
    sal_uInt32 nMovedID = 0;
    bool bFoundComment = false;
    bool bFoundStamp = false;
    bool bFoundRevertProperties = false;
    for (const css::beans::PropertyValue & rProp : rRedlineProperties )
    {
        if (rProp.Name == "RedlineAuthor")
        {
            if( rProp.Value >>= sAuthor )
                nAuthor = rRedlineAccess.InsertRedlineAuthor(sAuthor);
        }
        else if (rProp.Name == "RedlineComment")
            bFoundComment = rProp.Value >>= sComment;
        else if (rProp.Name == "RedlineDateTime")
            bFoundStamp = rProp.Value >>= aStamp;
        else if (rProp.Name == "RedlineRevertProperties")
            bFoundRevertProperties = rProp.Value >>= aRevertProperties;
        else if (rProp.Name == "RedlineMoved")
            rProp.Value >>= nMovedID;
    }
 
    SwRedlineData aRedlineData( eType, nAuthor );
    if( bFoundComment )
        aRedlineData.SetComment( sComment );
    if( bFoundStamp )
        aRedlineData.SetTimeStamp( DateTime( aStamp));
 
    std::unique_ptr<SwRedlineExtraData_FormatColl> xRedlineExtraData;
 
    // Read the 'Redline Revert Properties' from the parameters
    // Check if the value exists
    if ( bFoundRevertProperties )
    {
        int nMap = 0;
        // Make sure that paragraph format gets its own map, otherwise e.g. fill attributes are not preserved.
        if (eType == RedlineType::ParagraphFormat)
        {
            nMap = PROPERTY_MAP_PARAGRAPH;
            if (!aRevertProperties.hasElements())
            {
                // to reject the paragraph style change, use standard style
                xRedlineExtraData.reset(new SwRedlineExtraData_FormatColl( u""_ustr,  RES_POOLCOLL_STANDARD, nullptr ));
            }
        }
        else
            nMap = PROPERTY_MAP_TEXTPORTION_EXTENSIONS;
        SfxItemPropertySet const& rPropSet = *aSwMapProvider.GetPropertySet(nMap);
 
        // Check if there are any properties
        if (aRevertProperties.hasElements())
        {
            SwDoc& rDoc = rPaM.GetDoc();
 
            // Build set of attributes we want to fetch
            WhichRangesContainer aWhichPairs;
            std::vector<SfxItemPropertyMapEntry const*> aEntries;
            std::vector<uno::Any> aValues;
            aEntries.reserve(aRevertProperties.getLength());
            sal_uInt16 nStyleId = USHRT_MAX;
            sal_uInt16 nNumId = USHRT_MAX;
            for (const auto& rRevertProperty : aRevertProperties)
            {
                const OUString &rPropertyName = rRevertProperty.Name;
                SfxItemPropertyMapEntry const* pEntry = rPropSet.getPropertyMap().getByName(rPropertyName);
 
                if (!pEntry)
                {
                    // unknown property
                    break;
                }
                else if (pEntry->nFlags & beans::PropertyAttribute::READONLY)
                {
                    break;
                }
                else if (rPropertyName == "NumberingRules")
                {
                    aWhichPairs = aWhichPairs.MergeRange(RES_PARATR_NUMRULE, RES_PARATR_NUMRULE);
                    nNumId = aEntries.size();
                }
                else
                {
                    aWhichPairs = aWhichPairs.MergeRange(pEntry->nWID, pEntry->nWID);
                    if (rPropertyName == "ParaStyleName")
                        nStyleId = aEntries.size();
                }
                aEntries.push_back(pEntry);
                aValues.push_back(rRevertProperty.Value);
            }
 
            if (!aWhichPairs.empty())
            {
                sal_uInt16 nStylePoolId = USHRT_MAX;
                OUString sParaStyleName, sUIStyle;
                SfxItemSet aItemSet(rDoc.GetAttrPool(), std::move(aWhichPairs));
 
                for (size_t i = 0; i < aEntries.size(); ++i)
                {
                    const uno::Any &rValue = aValues[i];
                    if (i == nNumId)
                    {
                        uno::Reference<container::XNamed> xNumberingRules;
                        rValue >>= xNumberingRules;
                        if (xNumberingRules.is())
                        {
                            aItemSet.Put( SwNumRuleItem( xNumberingRules->getName() ));
                            // keep it during export
                            SwNumRule* pRule = rDoc.FindNumRulePtr(
                                        xNumberingRules->getName());
                            if (pRule)
                                pRule->SetUsedByRedline(true);
                        }
                    }
                    else
                    {
                        SfxItemPropertyMapEntry const*const pEntry = aEntries[i];
                        SfxItemPropertySet::setPropertyValue(*pEntry, rValue, aItemSet);
                        if (i == nStyleId)
                            rValue >>= sParaStyleName;
                    }
                }
 
                if (eType == RedlineType::ParagraphFormat && sParaStyleName.isEmpty())
                    nStylePoolId = RES_POOLCOLL_STANDARD;
 
                // tdf#149747 Get UI style name from programmatic style name
                SwStyleNameMapper::FillUIName(sParaStyleName, sUIStyle,
                                              SwGetPoolIdFromName::TxtColl);
                xRedlineExtraData.reset(new SwRedlineExtraData_FormatColl(
                    sUIStyle.isEmpty() ? sParaStyleName : sUIStyle, nStylePoolId, &aItemSet));
            }
            else if (eType == RedlineType::ParagraphFormat)
                xRedlineExtraData.reset(new SwRedlineExtraData_FormatColl( u""_ustr, RES_POOLCOLL_STANDARD, nullptr ));
        }
    }
 
    SwRangeRedline* pRedline = new SwRangeRedline( aRedlineData, rPaM );
 
    // set IsMoved bit of the redline to show and handle moved text
    if ( nMovedID > 0 )
    {
        pRedline->SetMoved( nMovedID );
        rRedlineAccess.GetRedlineTable().setMovedIDIfNeeded(nMovedID);
    }
 
    RedlineFlags nPrevMode = rRedlineAccess.GetRedlineFlags( );
    // xRedlineExtraData is copied here
    pRedline->SetExtraData( xRedlineExtraData.get() );
 
    rRedlineAccess.SetRedlineFlags_intern(RedlineFlags::On);
    auto const result(rRedlineAccess.AppendRedline(pRedline, false));
    rRedlineAccess.SetRedlineFlags_intern( nPrevMode );
    if (IDocumentRedlineAccess::AppendResult::IGNORED == result)
        throw lang::IllegalArgumentException();
}
 
void makeTableRowRedline( SwTableLine& rTableLine,
    std::u16string_view rRedlineType,
    const uno::Sequence< beans::PropertyValue >& rRedlineProperties )
{
    SwDoc* pDoc = rTableLine.GetFrameFormat()->GetDoc();
    IDocumentRedlineAccess* pRedlineAccess = &pDoc->getIDocumentRedlineAccess();
 
    RedlineType eType;
    if ( rRedlineType == u"TableRowInsert" )
    {
        eType = RedlineType::TableRowInsert;
    }
    else if ( rRedlineType == u"TableRowDelete" )
    {
        eType = RedlineType::TableRowDelete;
    }
    else
    {
        throw lang::IllegalArgumentException();
    }
 
    // set table row property "HasTextChangesOnly" to false
    // to handle tracked deletion or insertion of the table row on the UI
    const SvxPrintItem *pHasTextChangesOnlyProp =
         rTableLine.GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
    if ( !pHasTextChangesOnlyProp || pHasTextChangesOnlyProp->GetValue() )
    {
        SvxPrintItem aSetTracking(RES_PRINT, false);
        SwNodeIndex aInsPos( *(rTableLine.GetTabBoxes()[0]->GetSttNd()), 1 );
        // as a workaround for the rows without text content,
        // add a redline with invisible text CH_TXT_TRACKED_DUMMY_CHAR
        if ( rTableLine.IsEmpty() )
        {
            SwPaM aPaM(aInsPos);
            pDoc->getIDocumentContentOperations().InsertString( aPaM,
                    OUStringChar(CH_TXT_TRACKED_DUMMY_CHAR) );
            aPaM.SetMark();
            aPaM.GetMark()->SetContent(0);
            makeRedline(aPaM, RedlineType::TableRowInsert == eType
                    ? u"Insert"
                    : u"Delete", rRedlineProperties);
        }
        SwCursor aCursor( SwPosition(aInsPos), nullptr );
        pDoc->SetRowNotTracked( aCursor, aSetTracking );
    }
 
    comphelper::SequenceAsHashMap aPropMap( rRedlineProperties );
    std::size_t nAuthor = 0;
    OUString sAuthor;
    if( aPropMap.getValue(u"RedlineAuthor"_ustr) >>= sAuthor )
        nAuthor = pRedlineAccess->InsertRedlineAuthor(sAuthor);
 
    OUString sComment;
    SwRedlineData aRedlineData( eType, nAuthor );
    if( aPropMap.getValue(u"RedlineComment"_ustr) >>= sComment )
        aRedlineData.SetComment( sComment );
 
    ::util::DateTime aStamp;
    if( aPropMap.getValue(u"RedlineDateTime"_ustr) >>= aStamp )
    {
       aRedlineData.SetTimeStamp(
        DateTime( Date( aStamp.Day, aStamp.Month, aStamp.Year ), tools::Time( aStamp.Hours, aStamp.Minutes, aStamp.Seconds ) ) );
    }
 
    SwTableRowRedline* pRedline = new SwTableRowRedline( aRedlineData, rTableLine );
    RedlineFlags nPrevMode = pRedlineAccess->GetRedlineFlags( );
    pRedline->SetExtraData( nullptr );
 
    pRedlineAccess->SetRedlineFlags_intern(RedlineFlags::On);
    bool bRet = pRedlineAccess->AppendTableRowRedline( pRedline );
    pRedlineAccess->SetRedlineFlags_intern( nPrevMode );
    if( !bRet )
        throw lang::IllegalArgumentException();
}
 
void makeTableCellRedline( SwTableBox& rTableBox,
    std::u16string_view rRedlineType,
    const uno::Sequence< beans::PropertyValue >& rRedlineProperties )
{
    SwDoc* pDoc = rTableBox.GetFrameFormat()->GetDoc();
    IDocumentRedlineAccess* pRedlineAccess = &pDoc->getIDocumentRedlineAccess();
 
    RedlineType eType;
    if ( rRedlineType == u"TableCellInsert" )
    {
        eType = RedlineType::TableCellInsert;
    }
    else if ( rRedlineType == u"TableCellDelete" )
    {
        eType = RedlineType::TableCellDelete;
    }
    else
    {
        throw lang::IllegalArgumentException();
    }
 
    // set table row property "HasTextChangesOnly" to false
    // to handle tracked deletion or insertion of the table row on the UI
    const SvxPrintItem *pHasTextChangesOnlyProp =
         rTableBox.GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT);
    if ( !pHasTextChangesOnlyProp || pHasTextChangesOnlyProp->GetValue() )
    {
        SvxPrintItem aSetTracking(RES_PRINT, false);
        SwNodeIndex aInsPos( *rTableBox.GetSttNd(), 1 );
        // as a workaround for the cells without text content,
        // add a redline with invisible text CH_TXT_TRACKED_DUMMY_CHAR
        if ( rTableBox.IsEmpty() )
        {
            SwPaM aPaM(aInsPos);
            pDoc->getIDocumentContentOperations().InsertString( aPaM,
                    OUStringChar(CH_TXT_TRACKED_DUMMY_CHAR) );
            aPaM.SetMark();
            aPaM.GetMark()->SetContent(0);
            makeRedline(aPaM, RedlineType::TableCellInsert == eType
                    ? u"Insert"
                    : u"Delete", rRedlineProperties);
        }
        SwCursor aCursor( SwPosition(aInsPos), nullptr );
        pDoc->SetBoxAttr( aCursor, aSetTracking );
    }
 
    comphelper::SequenceAsHashMap aPropMap( rRedlineProperties );
    std::size_t nAuthor = 0;
    OUString sAuthor;
    if( aPropMap.getValue(u"RedlineAuthor"_ustr) >>= sAuthor )
        nAuthor = pRedlineAccess->InsertRedlineAuthor(sAuthor);
 
    OUString sComment;
    SwRedlineData aRedlineData( eType, nAuthor );
    if( aPropMap.getValue(u"RedlineComment"_ustr) >>= sComment )
        aRedlineData.SetComment( sComment );
 
    ::util::DateTime aStamp;
    if( aPropMap.getValue(u"RedlineDateTime"_ustr) >>= aStamp )
    {
       aRedlineData.SetTimeStamp(
        DateTime( Date( aStamp.Day, aStamp.Month, aStamp.Year ), tools::Time( aStamp.Hours, aStamp.Minutes, aStamp.Seconds ) ) );
    }
 
    SwTableCellRedline* pRedline = new SwTableCellRedline( aRedlineData, rTableBox );
    RedlineFlags nPrevMode = pRedlineAccess->GetRedlineFlags( );
    pRedline->SetExtraData( nullptr );
 
    pRedlineAccess->SetRedlineFlags_intern(RedlineFlags::On);
    bool bRet = pRedlineAccess->AppendTableCellRedline( pRedline );
    pRedlineAccess->SetRedlineFlags_intern( nPrevMode );
    if( !bRet )
        throw lang::IllegalArgumentException();
}
 
void SwAnyMapHelper::SetValue( sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any& rAny )
{
    sal_uInt32 nKey = (nWhichId << 16) + nMemberId;
    m_Map[nKey] = rAny;
}
 
bool    SwAnyMapHelper::FillValue( sal_uInt16 nWhichId, sal_uInt16 nMemberId, const uno::Any*& pAny )
{
    bool bRet = false;
    sal_uInt32 nKey = (nWhichId << 16) + nMemberId;
    auto aIt = m_Map.find( nKey );
    if (aIt != m_Map.end())
    {
        pAny = & aIt->second;
        bRet = true;
    }
    return bRet;
}
 
}//namespace SwUnoCursorHelper
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'SetNumRule' is required to be utilized.

V530 The return value of function 'SetNumRule' is required to be utilized.

V530 The return value of function 'SetNumRule' is required to be utilized.

V530 The return value of function 'SetNumRule' is required to be utilized.

V547 Expression 'nMovedID > 0' is always false.

V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 1053, 1055.

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

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

V1037 Two or more case-branches perform the same actions. Check lines: 485, 496