1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 #include <DocumentContentOperationsManager.hxx>
20 #include <DocumentRedlineManager.hxx>
21 #include <wrtsh.hxx>
22 #include <doc.hxx>
23 #include <IDocumentUndoRedo.hxx>
24 #include <IDocumentMarkAccess.hxx>
25 #include <IDocumentState.hxx>
26 #include <IDocumentLayoutAccess.hxx>
27 #include <IDocumentRedlineAccess.hxx>
28 #include <IDocumentStylePoolAccess.hxx>
29 #include <IDocumentSettingAccess.hxx>
30 #include <UndoManager.hxx>
31 #include <docary.hxx>
32 #include <textboxhelper.hxx>
33 #include <dcontact.hxx>
34 #include <grfatr.hxx>
35 #include <numrule.hxx>
36 #include <charfmt.hxx>
37 #include <ndgrf.hxx>
38 #include <ndnotxt.hxx>
39 #include <ndole.hxx>
40 #include <breakit.hxx>
41 #include <frmfmt.hxx>
42 #include <fmtanchr.hxx>
43 #include <fmtcntnt.hxx>
44 #include <fmtinfmt.hxx>
45 #include <fmtpdsc.hxx>
46 #include <fmtcnct.hxx>
47 #include <SwStyleNameMapper.hxx>
48 #include <redline.hxx>
49 #include <txtfrm.hxx>
50 #include <rootfrm.hxx>
51 #include <frmtool.hxx>
52 #include <unocrsr.hxx>
53 #include <mvsave.hxx>
54 #include <ndtxt.hxx>
55 #include <poolfmt.hxx>
56 #include <paratr.hxx>
57 #include <txatbase.hxx>
58 #include <UndoRedline.hxx>
59 #include <undobj.hxx>
60 #include <UndoBookmark.hxx>
61 #include <UndoDelete.hxx>
62 #include <UndoSplitMove.hxx>
63 #include <UndoOverwrite.hxx>
64 #include <UndoInsert.hxx>
65 #include <UndoAttribute.hxx>
66 #include <rolbck.hxx>
67 #include <acorrect.hxx>
68 #include <bookmark.hxx>
69 #include <ftnidx.hxx>
70 #include <txtftn.hxx>
71 #include <hints.hxx>
72 #include <fmtflcnt.hxx>
73 #include <docedt.hxx>
74 #include <frameformats.hxx>
75 #include <formatflysplit.hxx>
76 #include <o3tl/safeint.hxx>
77 #include <sal/log.hxx>
78 #include <unotools/charclass.hxx>
79 #include <unotools/configmgr.hxx>
80 #include <unotools/transliterationwrapper.hxx>
81 #include <i18nutil/transliteration.hxx>
82 #include <sfx2/Metadatable.hxx>
83 #include <sot/exchange.hxx>
84 #include <svl/stritem.hxx>
85 #include <svl/itemiter.hxx>
86 #include <svx/svdobj.hxx>
87 #include <svx/svdouno.hxx>
88 #include <tools/globname.hxx>
89 #include <editeng/formatbreakitem.hxx>
90 #include <com/sun/star/i18n/Boundary.hpp>
91 #include <com/sun/star/i18n/WordType.hpp>
92 #include <com/sun/star/i18n/XBreakIterator.hpp>
93 #include <com/sun/star/embed/XEmbeddedObject.hpp>
94 
95 #include <tuple>
96 #include <memory>
97 
98 using namespace ::com::sun::star::i18n;
99 
100 namespace
101 {
102     // Copy method from SwDoc
103     // Prevent copying into Flys that are anchored in the range
104     bool lcl_ChkFlyFly( SwDoc& rDoc, SwNodeOffset nSttNd, SwNodeOffset nEndNd,
105                         SwNodeOffset nInsNd )
106     {
107 
108         for(sw::SpzFrameFormat* pFormat: *rDoc.GetSpzFrameFormats())
109         {
110             SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
111             SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
112             if (pAnchorNode &&
113                 ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) ||
114                  (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) ||
115                  (RndStdIds::FLY_AT_FLY  == pAnchor->GetAnchorId()) ||
116                  (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) &&
117                 nSttNd <= pAnchorNode->GetIndex() &&
118                 pAnchorNode->GetIndex() < nEndNd )
119             {
120                 const SwFormatContent& rContent = pFormat->GetContent();
121                 SwStartNode* pSNd;
122                 if( !rContent.GetContentIdx() ||
123                     nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ))
124                     continue;
125 
126                 if( pSNd->GetIndex() < nInsNd &&
127                     nInsNd < pSNd->EndOfSectionIndex() )
128                     // Do not copy !
129                     return true;
130 
131                 if( lcl_ChkFlyFly( rDoc, pSNd->GetIndex(),
132                             pSNd->EndOfSectionIndex(), nInsNd ) )
133                     // Do not copy !
134                     return true;
135             }
136         }
137 
138         return false;
139     }
140 
141     SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, SwNodeOffset & rDelCount)
142     {
143         SwPosition const& rStart(*rSourcePaM.Start());
144         // Special handling for SwDoc::AppendDoc
145         if (rSourcePaM.GetDoc().GetNodes().GetEndOfExtras().GetIndex() + 1
146                 == rStart.GetNodeIndex())
147         {
148             rDelCount = SwNodeOffset(1);
149             return SwNodeIndex(rStart.GetNode(), +1);
150         }
151         else
152         {
153             rDelCount = SwNodeOffset(0);
154             return SwNodeIndex(rStart.GetNode());
155         }
156     }
157 
158     /*
159         The CopyBookmarks function has to copy bookmarks from the source to the destination nodes
160         array. It is called after a call of the CopyNodes(..) function. But this function does not copy
161         every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied
162         if the corresponding end/start node is outside the copied pam.
163         The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node
164         index inside the pam.
165         rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number
166         of "non-copy" nodes between rPam.Start() and rLastIdx.
167         nNewIdx is the new position of interest.
168     */
169     void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const SwNodeOffset nNewIdx, SwNodeOffset& rDelCount )
170     {
171         SwNodeOffset nStart = rPam.Start()->GetNodeIndex();
172         SwNodeOffset nEnd = rPam.End()->GetNodeIndex();
173         if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward?
174         {
175             // We never copy the StartOfContent node
176             do // count "non-copy" nodes
177             {
178                 SwNode& rNode = rLastIdx.GetNode();
179                 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
180                     || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
181                 {
182                     ++rDelCount;
183                 }
184                 ++rLastIdx;
185             }
186             while( rLastIdx.GetIndex() < nNewIdx );
187         }
188         else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now,
189                              // no move backward needed
190         {
191             while( rLastIdx.GetIndex() > nNewIdx )
192             {
193                 SwNode& rNode = rLastIdx.GetNode();
194                 if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd )
195                     || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) )
196                 {
197                     --rDelCount;
198                 }
199                 --rLastIdx;
200             }
201         }
202     }
203 
204     void lcl_SetCpyPos( const SwPosition& rOrigPos,
205                         const SwPosition& rOrigStt,
206                         const SwPosition& rCpyStt,
207                         SwPosition& rChgPos,
208                         SwNodeOffset nDelCount )
209     {
210         SwNodeOffset nNdOff = rOrigPos.GetNodeIndex();
211         nNdOff -= rOrigStt.GetNodeIndex();
212         nNdOff -= nDelCount;
213         sal_Int32 nContentPos = rOrigPos.GetContentIndex();
214 
215         // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos>
216         rChgPos.Assign( nNdOff + rCpyStt.GetNodeIndex() );
217         if (!rChgPos.GetNode().GetContentNode())
218             return;
219         if( !nNdOff )
220         {
221             // just adapt the content index
222             if( nContentPos > rOrigStt.GetContentIndex() )
223                 nContentPos -= rOrigStt.GetContentIndex();
224             else
225                 nContentPos = 0;
226             nContentPos += rCpyStt.GetContentIndex();
227         }
228         rChgPos.SetContent( nContentPos );
229     }
230 
231 }
232 
233 namespace sw
234 {
235     // TODO: use SaveBookmark (from DelBookmarks)
236     void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam, SwCopyFlags flags)
237     {
238         const SwDoc& rSrcDoc = rPam.GetDoc();
239         SwDoc& rDestDoc =  rCpyPam.GetDoc();
240         const IDocumentMarkAccess* const pSrcMarkAccess = rSrcDoc.getIDocumentMarkAccess();
241         ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
242 
243         const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
244         SwPosition const*const pCpyStt = &rCpyPam;
245 
246         std::vector< const ::sw::mark::IMark* > vMarksToCopy;
247         for ( IDocumentMarkAccess::const_iterator_t ppMark = pSrcMarkAccess->getAllMarksBegin();
248               ppMark != pSrcMarkAccess->getAllMarksEnd();
249               ++ppMark )
250         {
251             const ::sw::mark::IMark* const pMark = *ppMark;
252 
253             const SwPosition& rMarkStart = pMark->GetMarkStart();
254             const SwPosition& rMarkEnd = pMark->GetMarkEnd();
255             // only include marks that are in the range and not touching both start and end
256             // - not for annotation or checkbox marks.
257             bool const isIncludeStart(
258                    (rStt.GetContentIndex() == 0 // paragraph start selected?
259                     // also: only if inserting at the start - cross reference
260                     // marks require index to be 0, and there could be one
261                     // on the target node already
262                     && rCpyPam.GetContentIndex() == 0)
263                 || rMarkStart != rStt);
264             bool const isIncludeEnd(
265                    (rEnd.GetNode().IsTextNode() // paragraph end selected?
266                     && rEnd.GetContentIndex() == rEnd.GetNode().GetTextNode()->Len())
267                 || rMarkEnd != rEnd);
268             const bool bIsNotOnBoundary =
269                 pMark->IsExpanded()
270                 ? (isIncludeStart || isIncludeEnd)  // rMarkStart != rMarkEnd
271                 : (isIncludeStart && isIncludeEnd); // rMarkStart == rMarkEnd
272             const IDocumentMarkAccess::MarkType aMarkType = IDocumentMarkAccess::GetType(*pMark);
273             if ( rMarkStart >= rStt && rMarkEnd <= rEnd
274                  && ( bIsNotOnBoundary
275                       || aMarkType == IDocumentMarkAccess::MarkType::ANNOTATIONMARK
276                       || aMarkType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK
277                       || aMarkType == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK
278                       || aMarkType == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK
279                       || aMarkType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))
280             {
281                 vMarksToCopy.push_back(pMark);
282             }
283         }
284         // We have to count the "non-copied" nodes...
285         SwNodeOffset nDelCount;
286         SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
287         for(const sw::mark::IMark* const pMark : vMarksToCopy)
288         {
289             SwPaM aTmpPam(*pCpyStt);
290             lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().GetNodeIndex(), nDelCount);
291             lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount);
292             if(pMark->IsExpanded())
293             {
294                 aTmpPam.SetMark();
295                 lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().GetNodeIndex(), nDelCount);
296                 lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount);
297             }
298 
299             OUString sRequestedName = pMark->GetName();
300             if (flags & SwCopyFlags::IsMoveToFly)
301             {
302                 assert(&rSrcDoc == &rDestDoc);
303                 // Ensure the name can be given to NewMark, since this is ultimately a move
304                 auto pSoonToBeDeletedMark = const_cast<sw::mark::IMark*>(pMark);
305                 rDestDoc.getIDocumentMarkAccess()->renameMark(pSoonToBeDeletedMark,
306                                                               sRequestedName + "COPY_IS_MOVE");
307             }
308 
309             ::sw::mark::IMark* const pNewMark = rDestDoc.getIDocumentMarkAccess()->makeMark(
310                 aTmpPam,
311                 sRequestedName,
312                 IDocumentMarkAccess::GetType(*pMark),
313                 ::sw::mark::InsertMode::CopyText);
314             // Explicitly try to get exactly the same name as in the source
315             // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name
316             if (pNewMark == nullptr)
317             {
318                 assert(IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK
319                     || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK);
320                 continue; // can't insert duplicate cross reference mark
321             }
322             rDestDoc.getIDocumentMarkAccess()->renameMark(pNewMark, sRequestedName);
323 
324             // copying additional attributes for bookmarks or fieldmarks
325             ::sw::mark::IBookmark* const pNewBookmark =
326                 dynamic_cast< ::sw::mark::IBookmark* const >(pNewMark);
327             const ::sw::mark::IBookmark* const pOldBookmark =
328                 dynamic_cast< const ::sw::mark::IBookmark* >(pMark);
329             if (pNewBookmark && pOldBookmark)
330             {
331                 pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode());
332                 pNewBookmark->SetShortName(pOldBookmark->GetShortName());
333                 pNewBookmark->Hide(pOldBookmark->IsHidden());
334                 pNewBookmark->SetHideCondition(pOldBookmark->GetHideCondition());
335             }
336             ::sw::mark::IFieldmark* const pNewFieldmark =
337                 dynamic_cast< ::sw::mark::IFieldmark* const >(pNewMark);
338             const ::sw::mark::IFieldmark* const pOldFieldmark =
339                 dynamic_cast< const ::sw::mark::IFieldmark* >(pMark);
340             if (pNewFieldmark && pOldFieldmark)
341             {
342                 pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname());
343                 pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext());
344                 ::sw::mark::IFieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters();
345                 const ::sw::mark::IFieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters();
346                 for (const auto& rEntry : *pOldParams )
347                 {
348                     pNewParams->insert( rEntry );
349                 }
350             }
351 
352             ::sfx2::Metadatable const*const pMetadatable(
353                     dynamic_cast< ::sfx2::Metadatable const* >(pMark));
354             ::sfx2::Metadatable      *const pNewMetadatable(
355                     dynamic_cast< ::sfx2::Metadatable      * >(pNewMark));
356             if (pMetadatable && pNewMetadatable)
357             {
358                 pNewMetadatable->RegisterAsCopyOf(*pMetadatable);
359             }
360         }
361     }
362 } // namespace sw
363 
364 namespace
365 {
366     void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam )
367     {
368         const SwDoc& rSrcDoc = rPam.GetDoc();
369         const SwRedlineTable& rTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable();
370         if( rTable.empty() )
371             return;
372 
373         SwDoc& rDestDoc = rCpyPam.GetDoc();
374         SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End();
375         std::unique_ptr<SwPaM> pDelPam;
376         auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
377         // We have to count the "non-copied" nodes
378         SwNodeOffset nDelCount;
379         SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount));
380 
381         SwRedlineTable::size_type n = 0;
382         rSrcDoc.getIDocumentRedlineAccess().GetRedline( *pStt, &n );
383         for( ; n < rTable.size(); ++n )
384         {
385             const SwRangeRedline* pRedl = rTable[ n ];
386             if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() )
387             {
388                 auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition*
389 
390                 SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd );
391                 switch( eCmpPos )
392                 {
393                 case SwComparePosition::CollideEnd:
394                 case SwComparePosition::Before:
395                     // Pos1 is before Pos2
396                     break;
397 
398                 case SwComparePosition::CollideStart:
399                 case SwComparePosition::Behind:
400                     // Pos1 is after Pos2
401                     n = rTable.size();
402                     break;
403 
404                 default:
405                     {
406                         pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() ));
407                         if( *pStt < *pRStt )
408                         {
409                             lcl_NonCopyCount( rPam, aCorrIdx, pRStt->GetNodeIndex(), nDelCount );
410                             lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt,
411                                             *pDelPam->GetPoint(), nDelCount );
412                         }
413                         pDelPam->SetMark();
414 
415                         if( *pEnd < *pREnd )
416                             *pDelPam->GetPoint() = *pCpyEnd;
417                         else
418                         {
419                             lcl_NonCopyCount( rPam, aCorrIdx, pREnd->GetNodeIndex(), nDelCount );
420                             lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt,
421                                             *pDelPam->GetPoint(), nDelCount );
422                         }
423 
424                         if (pDelPam->GetNext() != pDelPam.get()
425                             && *pDelPam->GetNext()->End() == *pDelPam->Start())
426                         {
427                             *pDelPam->GetNext()->End() = *pDelPam->End();
428                             pDelPam.reset(pDelPam->GetNext());
429                         }
430                     }
431                 }
432             }
433         }
434 
435         if( !pDelPam )
436             return;
437 
438         RedlineFlags eOld = rDestDoc.getIDocumentRedlineAccess().GetRedlineFlags();
439         rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore );
440 
441         ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo());
442 
443         // At this point, pDelPam points to the last of maybe several disjoint selections, organized
444         // in reverse order in document (so every GetNext() returns a PaM closer to document start,
445         // until wrap to pDelPam). Removal of the selections must be from last in document to first,
446         // to avoid situations when another PaM in chain points into the node that will be destroyed
447         // (joined to previous) by removal of the currently processed PaM.
448         do {
449             rDestDoc.getIDocumentContentOperations().DeleteAndJoin(*pDelPam);
450             if( !pDelPam->IsMultiSelection() )
451                 break;
452             pDelPam.reset(pDelPam->GetNext());
453         } while( true );
454 
455         rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
456     }
457 
458     void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg )
459     {
460         SwDoc& rSrcDoc = rRg.aStart.GetNode().GetDoc();
461         if( !rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
462         {
463             SwPaM aRgTmp( rRg.aStart, rRg.aEnd );
464             SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd );
465             lcl_DeleteRedlines( aRgTmp, aCpyTmp );
466         }
467     }
468 
469     void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest )
470     {
471         SwFormatChain aSrc( pSrc->GetChain() );
472         if ( !aSrc.GetNext() )
473         {
474             aSrc.SetNext( pDest );
475             pSrc->SetFormatAttr( aSrc );
476         }
477         SwFormatChain aDest( pDest->GetChain() );
478         if ( !aDest.GetPrev() )
479         {
480             aDest.SetPrev( pSrc );
481             pDest->SetFormatAttr( aDest );
482         }
483     }
484 
485     // #i86492#
486     bool lcl_ContainsOnlyParagraphsInList( const SwPaM& rPam )
487     {
488         bool bRet = false;
489 
490         const SwTextNode* pTextNd = rPam.Start()->GetNode().GetTextNode();
491         const SwTextNode* pEndTextNd = rPam.End()->GetNode().GetTextNode();
492         if ( pTextNd && pTextNd->IsInList() &&
493              pEndTextNd && pEndTextNd->IsInList() )
494         {
495             bRet = true;
496             SwNodeIndex aIdx(rPam.Start()->GetNode());
497 
498             do
499             {
500                 ++aIdx;
501                 pTextNd = aIdx.GetNode().GetTextNode();
502 
503                 if ( !pTextNd || !pTextNd->IsInList() )
504                 {
505                     bRet = false;
506                     break;
507                 }
508             } while (pTextNd != pEndTextNd);
509         }
510 
511         return bRet;
512     }
513 
514     bool lcl_MarksWholeNode(const SwPaM & rPam)
515     {
516         bool bResult = false;
517         auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
518 
519         if (nullptr != pStt && nullptr != pEnd)
520         {
521             const SwTextNode* pSttNd = pStt->GetNode().GetTextNode();
522             const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
523 
524             if (nullptr != pSttNd && nullptr != pEndNd &&
525                 pStt->GetContentIndex() == 0 &&
526                 pEnd->GetContentIndex() == pEndNd->Len())
527             {
528                 bResult = true;
529             }
530         }
531 
532         return bResult;
533     }
534 }
535 
536 //local functions originally from sw/source/core/doc/docedt.cxx
537 namespace sw
538 {
539     void CalcBreaks(std::vector<std::pair<SwNodeOffset, sal_Int32>> & rBreaks,
540             SwPaM const & rPam, bool const isOnlyFieldmarks)
541     {
542         SwNodeOffset const nStartNode(rPam.Start()->GetNodeIndex());
543         SwNodeOffset const nEndNode(rPam.End()->GetNodeIndex());
544         SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
545         IDocumentMarkAccess const& rIDMA(*rPam.GetDoc().getIDocumentMarkAccess());
546 
547         std::stack<std::tuple<sw::mark::IFieldmark const*, bool, SwNodeOffset, sal_Int32>> startedFields;
548 
549         for (SwNodeOffset n = nStartNode; n <= nEndNode; ++n)
550         {
551             SwNode *const pNode(rNodes[n]);
552             if (pNode->IsTextNode())
553             {
554                 SwTextNode & rTextNode(*pNode->GetTextNode());
555                 sal_Int32 const nStart(n == nStartNode
556                         ? rPam.Start()->GetContentIndex()
557                         : 0);
558                 sal_Int32 const nEnd(n == nEndNode
559                         ? rPam.End()->GetContentIndex()
560                         : rTextNode.Len());
561                 for (sal_Int32 i = nStart; i < nEnd; ++i)
562                 {
563                     const sal_Unicode c(rTextNode.GetText()[i]);
564                     switch (c)
565                     {
566                         // note: CH_TXT_ATR_FORMELEMENT does not need handling
567                         // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled
568                         case CH_TXTATR_INWORD:
569                         case CH_TXTATR_BREAKWORD:
570                         {
571                             // META hints only have dummy char at the start, not
572                             // at the end, so no need to check in nStartNode
573                             if (n == nEndNode && !isOnlyFieldmarks)
574                             {
575                                 SwTextAttr const* pAttr(rTextNode.GetTextAttrForCharAt(i));
576                                 if (pAttr && pAttr->End() && (nEnd  < *pAttr->End()))
577                                 {
578                                     assert(pAttr->HasDummyChar());
579                                     rBreaks.emplace_back(n, i);
580                                 }
581 
582                                 if (!pAttr)
583                                 {
584                                     // See if this is an end dummy character for a content control.
585                                     pAttr = rTextNode.GetTextAttrForEndCharAt(i, RES_TXTATR_CONTENTCONTROL);
586                                     if (pAttr && (nStart > pAttr->GetStart()))
587                                     {
588                                         rBreaks.emplace_back(n, i);
589                                     }
590                                 }
591                             }
592                             break;
593                         }
594                         case CH_TXT_ATR_FIELDSTART:
595                         {
596                             auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
597                             startedFields.emplace(pFieldMark, false, 0, 0);
598                             break;
599                         }
600                         case CH_TXT_ATR_FIELDSEP:
601                         {
602                             if (startedFields.empty())
603                             {
604                                 rBreaks.emplace_back(n, i);
605                             }
606                             else
607                             {   // no way to find the field via MarkManager...
608                                 assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i)));
609                                 std::get<1>(startedFields.top()) = true;
610                                 std::get<2>(startedFields.top()) = n;
611                                 std::get<3>(startedFields.top()) = i;
612                             }
613                             break;
614                         }
615                         case CH_TXT_ATR_FIELDEND:
616                         {
617                             if (startedFields.empty())
618                             {
619                                 rBreaks.emplace_back(n, i);
620                             }
621                             else
622                             {   // fieldmarks must not overlap => stack
623                                 assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i)));
624                                 startedFields.pop();
625                             }
626                             break;
627                         }
628                     }
629                 }
630             }
631             else if (pNode->IsStartNode())
632             {
633                 if (pNode->EndOfSectionIndex() <= nEndNode)
634                 {   // fieldmark cannot overlap node section
635                     n = pNode->EndOfSectionIndex();
636                 }
637             }
638             else
639             {   // EndNode can actually happen with sections :(
640                 assert(pNode->IsEndNode() || pNode->IsNoTextNode());
641             }
642         }
643         while (!startedFields.empty())
644         {
645             if (const sw::mark::IFieldmark* pMark = std::get<0>(startedFields.top()))
646             {
647                 SwPosition const& rStart(pMark->GetMarkStart());
648                 std::pair<SwNodeOffset, sal_Int32> const pos(
649                         rStart.GetNodeIndex(), rStart.GetContentIndex());
650                 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos);
651                 assert(it == rBreaks.end() || *it != pos);
652                 rBreaks.insert(it, pos);
653             }
654             if (std::get<1>(startedFields.top()))
655             {
656                 std::pair<SwNodeOffset, sal_Int32> const posSep(
657                     std::get<2>(startedFields.top()),
658                     std::get<3>(startedFields.top()));
659                 auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep);
660                 assert(it == rBreaks.end() || *it != posSep);
661                 rBreaks.insert(it, posSep);
662             }
663             startedFields.pop();
664         }
665     }
666 }
667 
668 namespace
669 {
670 
671     bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations,
672             SwPaM & rPam, SwDeleteFlags const flags,
673             bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, SwDeleteFlags))
674     {
675         std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
676 
677         sw::CalcBreaks(Breaks, rPam);
678 
679         if (Breaks.empty())
680         {
681             return (rDocumentContentOperations.*pFunc)(rPam, flags);
682         }
683 
684         // Deletion must be split into several parts if the text node
685         // contains a text attribute with end and with dummy character
686         // and the selection does not contain the text attribute completely,
687         // but overlaps its start (left), where the dummy character is.
688 
689         SwPosition const & rSelectionEnd( *rPam.End() );
690 
691         bool bRet( true );
692         // iterate from end to start, to avoid invalidating the offsets!
693         auto iter( Breaks.rbegin() );
694         SwNodeOffset nOffset(0);
695         SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
696         SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
697         SwPosition & rEnd( *aPam.End() );
698         SwPosition & rStart( *aPam.Start() );
699 
700         while (iter != Breaks.rend())
701         {
702             rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
703             if (rStart < rEnd) // check if part is empty
704             {
705                 bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
706                 nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
707             }
708             rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
709             ++iter;
710         }
711 
712         rStart = *rPam.Start(); // set to original start
713         if (rStart < rEnd) // check if part is empty
714         {
715             bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags);
716         }
717 
718         return bRet;
719     }
720 
721     bool lcl_StrLenOverflow( const SwPaM& rPam )
722     {
723         // If we try to merge two paragraphs we have to test if afterwards
724         // the string doesn't exceed the allowed string length
725         if( rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode() )
726         {
727             auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
728             const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode();
729             if( (nullptr != pEndNd) && pStt->GetNode().IsTextNode() )
730             {
731                 const sal_uInt64 nSum = pStt->GetContentIndex() +
732                     pEndNd->GetText().getLength() - pEnd->GetContentIndex();
733                 return nSum > o3tl::make_unsigned(SAL_MAX_INT32);
734             }
735         }
736         return false;
737     }
738 
739     struct SaveRedline
740     {
741         SwRangeRedline* pRedl;
742         SwNodeOffset nStt, nEnd;
743         sal_Int32 nSttCnt;
744         sal_Int32 nEndCnt;
745 
746         SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx )
747             : pRedl(pR)
748             , nEnd(0)
749             , nEndCnt(0)
750         {
751             auto [pStt, pEnd] = pR->StartEnd(); // SwPosition*
752             SwNodeOffset nSttIdx = rSttIdx.GetIndex();
753             nStt = pStt->GetNodeIndex() - nSttIdx;
754             nSttCnt = pStt->GetContentIndex();
755             if( pR->HasMark() )
756             {
757                 nEnd = pEnd->GetNodeIndex() - nSttIdx;
758                 nEndCnt = pEnd->GetContentIndex();
759             }
760 
761             pRedl->GetPoint()->Assign( SwNodeOffset(0) );
762             pRedl->GetMark()->Assign( SwNodeOffset(0) );
763         }
764 
765         SaveRedline( SwRangeRedline* pR, const SwPosition& rPos )
766             : pRedl(pR)
767             , nEnd(0)
768             , nEndCnt(0)
769         {
770             auto [pStt, pEnd] = pR->StartEnd(); // SwPosition*
771             SwNodeOffset nSttIdx = rPos.GetNodeIndex();
772             nStt = pStt->GetNodeIndex() - nSttIdx;
773             nSttCnt = pStt->GetContentIndex();
774             if( nStt == SwNodeOffset(0) )
775                 nSttCnt = nSttCnt - rPos.GetContentIndex();
776             if( pR->HasMark() )
777             {
778                 nEnd = pEnd->GetNodeIndex() - nSttIdx;
779                 nEndCnt = pEnd->GetContentIndex();
780                 if( nEnd == SwNodeOffset(0) )
781                     nEndCnt = nEndCnt - rPos.GetContentIndex();
782             }
783 
784             pRedl->GetPoint()->Assign( SwNodeOffset(0) );
785             pRedl->GetMark()->Assign( SwNodeOffset(0) );
786         }
787 
788         void SetPos( SwNodeOffset nInsPos )
789         {
790             pRedl->GetPoint()->Assign( nInsPos + nStt, nSttCnt );
791             if( pRedl->HasMark() )
792             {
793                 pRedl->GetMark()->Assign( nInsPos + nEnd, nEndCnt );
794             }
795         }
796 
797         void SetPos( const SwPosition& aPos )
798         {
799             pRedl->GetPoint()->Assign( aPos.GetNodeIndex() + nStt,
800                                 nSttCnt + ( nStt == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
801             if( pRedl->HasMark() )
802             {
803                 pRedl->GetMark()->Assign( aPos.GetNodeIndex() + nEnd,
804                                     nEndCnt + ( nEnd == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) );
805             }
806         }
807     };
808 
809     typedef std::vector< SaveRedline > SaveRedlines_t;
810 
811     void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr)
812     {
813         SwDoc& rDoc = aPam.GetPointNode().GetDoc();
814 
815         auto [pStart, pEnd] = aPam.StartEnd(); // SwPosition*
816 
817         // get first relevant redline
818         SwRedlineTable::size_type nCurrentRedline;
819         rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline );
820         if( nCurrentRedline > 0)
821             nCurrentRedline--;
822 
823         // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode
824         RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
825         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
826 
827         // iterate over relevant redlines and decide for each whether it should
828         // be saved, or split + saved
829         SwRedlineTable& rRedlineTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
830         for( ; nCurrentRedline < rRedlineTable.size(); nCurrentRedline++ )
831         {
832             SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ];
833             SwComparePosition eCompare =
834                 ComparePosition( *pCurrent->Start(), *pCurrent->End(),
835                                  *pStart, *pEnd);
836 
837             // we must save this redline if it overlaps aPam
838             // (we may have to split it, too)
839             if( eCompare == SwComparePosition::OverlapBehind  ||
840                 eCompare == SwComparePosition::OverlapBefore  ||
841                 eCompare == SwComparePosition::Outside ||
842                 eCompare == SwComparePosition::Inside ||
843                 eCompare == SwComparePosition::Equal )
844             {
845                 rRedlineTable.Remove( nCurrentRedline-- );
846 
847                 // split beginning, if necessary
848                 if( eCompare == SwComparePosition::OverlapBefore  ||
849                     eCompare == SwComparePosition::Outside )
850                 {
851                     SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
852                     *pNewRedline->End() = *pStart;
853                     *pCurrent->Start() = *pStart;
854                     rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
855                 }
856 
857                 // split end, if necessary
858                 if( eCompare == SwComparePosition::OverlapBehind  ||
859                     eCompare == SwComparePosition::Outside )
860                 {
861                     SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent );
862                     *pNewRedline->Start() = *pEnd;
863                     *pCurrent->End() = *pEnd;
864                     rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
865                 }
866 
867                 // save the current redline
868                 rArr.emplace_back( pCurrent, *pStart );
869             }
870         }
871 
872         // restore old redline mode
873         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
874     }
875 
876     void lcl_RestoreRedlines(SwDoc& rDoc, const SwPosition& rPos, SaveRedlines_t& rArr)
877     {
878         RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
879         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
880 
881         for(SaveRedline & rSvRedLine : rArr)
882         {
883             rSvRedLine.SetPos( rPos );
884             rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true );
885         }
886 
887         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
888     }
889 
890     void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr)
891     {
892         SwDoc& rDoc = rRg.aStart.GetNode().GetDoc();
893         SwRedlineTable::size_type nRedlPos;
894         SwPosition aSrchPos( rRg.aStart );
895         aSrchPos.Adjust(SwNodeOffset(-1));
896         if( rDoc.getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos )
897             --nRedlPos;
898         else if( nRedlPos >= rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() )
899             return ;
900 
901         RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
902         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
903         SwRedlineTable& rRedlTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
904 
905         do {
906             SwRangeRedline* pTmp = rRedlTable[ nRedlPos ];
907 
908             auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
909 
910             if( pRStt->GetNode() < rRg.aStart.GetNode() )
911             {
912                 if( pREnd->GetNode() > rRg.aStart.GetNode() && pREnd->GetNode() < rRg.aEnd.GetNode() )
913                 {
914                     // Create a copy and set the end of the original to the end of the MoveArea.
915                     // The copy is moved too.
916                     SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
917                     SwPosition* pTmpPos = pNewRedl->Start();
918                     pTmpPos->Assign(rRg.aStart);
919 
920                     rArr.emplace_back(pNewRedl, rRg.aStart);
921 
922                     pTmpPos = pTmp->End();
923                     pTmpPos->Assign(rRg.aEnd);
924                 }
925                 else if( pREnd->GetNode() == rRg.aStart.GetNode() )
926                 {
927                     SwPosition* pTmpPos = pTmp->End();
928                     pTmpPos->Assign(rRg.aEnd);
929                 }
930             }
931             else if( pRStt->GetNode() < rRg.aEnd.GetNode() )
932             {
933                 rRedlTable.Remove( nRedlPos-- );
934                 if( pREnd->GetNode() < rRg.aEnd.GetNode() ||
935                     ( pREnd->GetNode() == rRg.aEnd.GetNode() && !pREnd->GetContentIndex()) )
936                 {
937                     // move everything
938                     rArr.emplace_back( pTmp, rRg.aStart );
939                 }
940                 else
941                 {
942                     // split
943                     SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp );
944                     SwPosition* pTmpPos = pNewRedl->End();
945                     pTmpPos->Assign(rRg.aEnd);
946 
947                     rArr.emplace_back( pNewRedl, rRg.aStart );
948 
949                     pTmpPos = pTmp->Start();
950                     pTmpPos->Assign(rRg.aEnd);
951                     rDoc.getIDocumentRedlineAccess().AppendRedline( pTmp, true );
952                 }
953             }
954             else
955                 break;
956 
957         } while( ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() );
958         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
959     }
960 
961     void lcl_RestoreRedlines(SwDoc& rDoc, SwNodeOffset const nInsPos, SaveRedlines_t& rArr)
962     {
963         RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
964         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On );
965 
966         for(SaveRedline & rSvRedLine : rArr)
967         {
968             rSvRedLine.SetPos( nInsPos );
969             IDocumentRedlineAccess::AppendResult const result(
970                 rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ));
971             if ( IDocumentRedlineAccess::AppendResult::APPENDED == result &&
972                 rSvRedLine.pRedl->GetType() == RedlineType::Delete )
973             {
974                 UpdateFramesForAddDeleteRedline(rDoc, *rSvRedLine.pRedl);
975             }
976         }
977 
978         rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
979     }
980 
981     bool lcl_SaveFootnote( const SwNode& rSttNd, const SwNode& rEndNd,
982                      const SwNode& rInsPos,
983                      SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr,
984                      std::optional<sal_Int32> oSttCnt = std::nullopt, std::optional<sal_Int32> oEndCnt = std::nullopt )
985     {
986         bool bUpdateFootnote = false;
987         const SwNodes& rNds = rInsPos.GetNodes();
988         const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() &&
989                     rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex();
990         const bool bSaveFootnote = !bDelFootnote &&
991                         rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex();
992         if( !rFootnoteArr.empty() )
993         {
994 
995             size_t nPos = 0;
996             rFootnoteArr.SeekEntry( rSttNd, &nPos );
997             SwTextFootnote* pSrch;
998             const SwNode* pFootnoteNd;
999 
1000             // Delete/save all that come after it
1001             while( nPos < rFootnoteArr.size() && ( pFootnoteNd =
1002                 &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex()
1003                         <= rEndNd.GetIndex() )
1004             {
1005                 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1006                 if( ( oEndCnt && oSttCnt )
1007                     ? (( &rSttNd == pFootnoteNd &&
1008                          *oSttCnt > nFootnoteSttIdx) ||
1009                        ( &rEndNd == pFootnoteNd &&
1010                         nFootnoteSttIdx >= *oEndCnt ))
1011                     : ( &rEndNd == pFootnoteNd ))
1012                 {
1013                     ++nPos;     // continue searching
1014                 }
1015                 else
1016                 {
1017                     // delete it
1018                     if( bDelFootnote )
1019                     {
1020                         SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1021                         SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1022                         rTextNd.EraseText( aIdx, 1 );
1023                     }
1024                     else
1025                     {
1026                         pSrch->DelFrames(nullptr);
1027                         rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1028                         if( bSaveFootnote )
1029                             rSaveArr.insert( pSrch );
1030                     }
1031                     bUpdateFootnote = true;
1032                 }
1033             }
1034 
1035             while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )->
1036                     GetTextNode())->GetIndex() >= rSttNd.GetIndex() )
1037             {
1038                 const sal_Int32 nFootnoteSttIdx = pSrch->GetStart();
1039                 if( !oEndCnt || !oSttCnt ||
1040                     !  (( &rSttNd == pFootnoteNd &&
1041                         *oSttCnt > nFootnoteSttIdx ) ||
1042                         ( &rEndNd == pFootnoteNd &&
1043                         nFootnoteSttIdx >= *oEndCnt )) )
1044                 {
1045                     if( bDelFootnote )
1046                     {
1047                         // delete it
1048                         SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode());
1049                         SwContentIndex aIdx( &rTextNd, nFootnoteSttIdx );
1050                         rTextNd.EraseText( aIdx, 1 );
1051                     }
1052                     else
1053                     {
1054                         pSrch->DelFrames(nullptr);
1055                         rFootnoteArr.erase( rFootnoteArr.begin() + nPos );
1056                         if( bSaveFootnote )
1057                             rSaveArr.insert( pSrch );
1058                     }
1059                     bUpdateFootnote = true;
1060                 }
1061             }
1062         }
1063         // When moving from redline section into document content section, e.g.
1064         // after loading a document with (delete-)redlines, the footnote array
1065         // has to be adjusted... (#i70572)
1066         if( bSaveFootnote )
1067         {
1068             SwNodeIndex aIdx( rSttNd );
1069             while( aIdx < rEndNd ) // Check the moved section
1070             {
1071                 SwNode* pNode = &aIdx.GetNode();
1072                 if( pNode->IsTextNode() ) // Looking for text nodes...
1073                 {
1074                     SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints();
1075                     if( pHints && pHints->HasFootnote() ) //...with footnotes
1076                     {
1077                         bUpdateFootnote = true; // Heureka
1078                         const size_t nCount = pHints->Count();
1079                         for( size_t i = 0; i < nCount; ++i )
1080                         {
1081                             SwTextAttr *pAttr = pHints->Get( i );
1082                             if ( pAttr->Which() == RES_TXTATR_FTN )
1083                             {
1084                                 rSaveArr.insert( static_cast<SwTextFootnote*>(pAttr) );
1085                             }
1086                         }
1087                     }
1088                 }
1089                 ++aIdx;
1090             }
1091         }
1092         return bUpdateFootnote;
1093     }
1094 
1095     bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos )
1096     {
1097         sal_Unicode const cChr = pNode->GetText()[nPos];
1098         switch (cChr)
1099         {
1100             case CH_TXTATR_BREAKWORD:
1101             case CH_TXTATR_INWORD:
1102                 return !pNode->GetTextAttrForCharAt(nPos);// how could there be none?
1103             case CH_TXT_ATR_INPUTFIELDSTART:
1104             case CH_TXT_ATR_INPUTFIELDEND:
1105             case CH_TXT_ATR_FIELDSTART:
1106             case CH_TXT_ATR_FIELDSEP:
1107             case CH_TXT_ATR_FIELDEND:
1108             case CH_TXT_ATR_FORMELEMENT:
1109                 return false;
1110             default:
1111                 return true;
1112         }
1113     }
1114 
1115     void lcl_SkipAttr( const SwTextNode *pNode, SwPosition &rIdx, sal_Int32 &rStart )
1116     {
1117         if( !lcl_MayOverwrite( pNode, rStart ) )
1118         {
1119             // skip all special attributes
1120             do {
1121                 rIdx.AdjustContent(+1);
1122                 rStart = rIdx.GetContentIndex();
1123             } while (rStart < pNode->GetText().getLength()
1124                    && !lcl_MayOverwrite(pNode, rStart) );
1125         }
1126     }
1127 
1128     bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc )
1129     {
1130         if( bRegExpRplc )
1131         {
1132             sal_Int32 nPos = 0;
1133             static constexpr OUString sPara(u"\\n"_ustr);
1134             for (;;)
1135             {
1136                 nPos = rStr.indexOf( sPara, nPos );
1137                 if (nPos<0)
1138                 {
1139                     break;
1140                 }
1141                 // Has this been escaped?
1142                 if( nPos && '\\' == rStr[nPos-1])
1143                 {
1144                     ++nPos;
1145                     if( nPos >= rStr.getLength() )
1146                     {
1147                         break;
1148                     }
1149                 }
1150                 else
1151                 {
1152                     rRet = rStr.copy( 0, nPos );
1153                     rStr = rStr.copy( nPos + sPara.getLength() );
1154                     return true;
1155                 }
1156             }
1157         }
1158         rRet = rStr;
1159         rStr.clear();
1160         return false;
1161     }
1162 }
1163 
1164 namespace //local functions originally from docfmt.cxx
1165 {
1166 
1167     bool lcl_ApplyOtherSet(
1168             SwContentNode & rNode,
1169             SwHistory *const pHistory,
1170             SfxItemSet const& rOtherSet,
1171             SfxItemSet const& rFirstSet,
1172             SfxItemSet const& rPropsSet,
1173             SwRootFrame const*const pLayout,
1174             SwNodeIndex *const o_pIndex = nullptr)
1175     {
1176         assert(rOtherSet.Count());
1177 
1178         bool ret(false);
1179         SwTextNode *const pTNd = rNode.GetTextNode();
1180         sw::MergedPara const* pMerged(nullptr);
1181         if (pLayout && pLayout->HasMergedParas() && pTNd)
1182         {
1183             SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
1184                 pTNd->getLayoutFrame(pLayout)));
1185             if (pTextFrame)
1186             {
1187                 pMerged = pTextFrame->GetMergedPara();
1188             }
1189             if (pMerged)
1190             {
1191                 if (rFirstSet.Count())
1192                 {
1193                     if (pHistory)
1194                     {
1195                         SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory);
1196                         ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1197                     }
1198                     else
1199                     {
1200                         ret = pMerged->pFirstNode->SetAttr(rFirstSet);
1201                     }
1202                 }
1203                 if (rPropsSet.Count())
1204                 {
1205                     if (pHistory)
1206                     {
1207                         SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory);
1208                         ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1209                     }
1210                     else
1211                     {
1212                         ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret;
1213                     }
1214                 }
1215                 if (o_pIndex)
1216                 {
1217                     *o_pIndex = *pMerged->pLastNode; // skip hidden
1218                 }
1219             }
1220         }
1221 
1222         // input cursor can't be on hidden node, and iteration skips them
1223         assert(!pLayout || !pLayout->HasMergedParas()
1224             || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden);
1225 
1226         if (!pMerged)
1227         {
1228             if (pHistory)
1229             {
1230                 SwRegHistory aRegH(&rNode, rNode, pHistory);
1231                 ret = rNode.SetAttr( rOtherSet );
1232             }
1233             else
1234             {
1235                 ret = rNode.SetAttr( rOtherSet );
1236             }
1237         }
1238         return ret;
1239     }
1240 
1241     #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; }
1242 
1243     // set format redline with extra data for lcl_InsAttr()
1244     void lcl_SetRedline(
1245         SwDoc& rDoc,
1246         const SwPaM &rRg)
1247     {
1248         std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
1249 
1250         // check existing redline on the same range, and use its extra data, if it exists
1251         SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos(
1252                 rRg.Start()->GetNode(), RedlineType::Format );
1253         if( SwRedlineTable::npos != nRedlPos )
1254         {
1255             const SwPosition *pRStt, *pREnd;
1256             do {
1257                 SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
1258                 pRStt = pTmp->Start();
1259                 pREnd = pTmp->End();
1260                 SwComparePosition eCompare = ComparePosition( *rRg.Start(), *rRg.End(), *pRStt, *pREnd );
1261                 if ( eCompare == SwComparePosition::Inside || eCompare == SwComparePosition::Equal )
1262                 {
1263                     if (pTmp->GetExtraData())
1264                     {
1265                         const SwRedlineExtraData* pExtraData = pTmp->GetExtraData();
1266                         const SwRedlineExtraData_FormatColl* pFormattingChanges =
1267                             dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
1268                         // Check if the extra data is of type 'formatting changes'
1269                         if (pFormattingChanges)
1270                         {
1271                             // Get the item set that holds all the changes properties
1272                             const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
1273                             xExtra.reset(new SwRedlineExtraData_FormatColl("", USHRT_MAX, pChangesSet));
1274                             break;
1275                         }
1276                     }
1277                 }
1278             } while( pRStt <= rRg.Start() && ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
1279         }
1280 
1281         SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::Format, rRg );
1282         auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline( pRedline, true));
1283         // store original text attributes to reject formatting change
1284         if (IDocumentRedlineAccess::AppendResult::IGNORED == result)
1285             return;
1286 
1287         // no existing format redline in the range
1288         if (!xExtra)
1289         {
1290             // Apply the first character's attributes to the ReplaceText
1291             SfxItemSetFixed<RES_CHRATR_BEGIN,     RES_TXTATR_WITHEND_END - 1,
1292                         RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>  aSet( rDoc.GetAttrPool() );
1293             SwTextNode * pNode = rRg.Start()->GetNode().GetTextNode();
1294             pNode->GetParaAttr( aSet, rRg.Start()->GetContentIndex() + 1, rRg.End()->GetContentIndex() );
1295 
1296             aSet.ClearItem( RES_TXTATR_REFMARK );
1297             aSet.ClearItem( RES_TXTATR_TOXMARK );
1298             aSet.ClearItem( RES_TXTATR_CJK_RUBY );
1299             aSet.ClearItem( RES_TXTATR_INETFMT );
1300             aSet.ClearItem( RES_TXTATR_META );
1301             aSet.ClearItem( RES_TXTATR_METAFIELD );
1302 
1303             // After GetParaAttr aSet can contain invalid/dontcare items (true == IsInvalidItem,
1304             // DONTCARE == SfxItemState), e.g. RES_TXTATR_CHARFMT and (a copy of) this
1305             // SfxItemSet can be passed to MSWordExportBase::OutputItemSet
1306             // which doesn't handle invalid/dontcare items so clear them here
1307             aSet.ClearInvalidItems();
1308 
1309             xExtra.reset(new SwRedlineExtraData_FormatColl("", USHRT_MAX, &aSet));
1310         }
1311 
1312         if (xExtra)
1313         {
1314             pRedline->SetExtraData(xExtra.get() );
1315         }
1316     }
1317 
1318     // create format redline(s) for the given range:
1319     // to track the original formatting stored in the
1320     // hints, create redlines for all parts of the
1321     // range partitioned by boundaries of the hints.
1322     void lcl_SetRedlines(
1323         SwDoc& rDoc,
1324         const SwPaM &rRg)
1325     {
1326         SwNodeIndex aIdx( rRg.Start()->GetNode() );
1327         const SwNodeIndex aEndNd( rRg.End()->GetNode() );
1328         while( aIdx <= aEndNd )
1329         {
1330             SwTextNode *pNode = aIdx.GetNode().GetTextNode();
1331             if( pNode )
1332             {
1333                 const sal_Int32 nStart = aIdx == rRg.Start()->GetNode()
1334                         ? rRg.Start()->GetContentIndex()
1335                         : 0;
1336                 const sal_Int32 nEnd = aIdx < aEndNd
1337                         ? pNode->GetText().getLength()
1338                         : rRg.End()->GetContentIndex();
1339 
1340                 if( SwpHints *pHints = pNode->GetpSwpHints() )
1341                 {
1342                     const size_t nCount = pHints->Count();
1343                     sal_Int32 nRedEnd = nStart;
1344                     for( size_t i = 0; i < nCount; ++i )
1345                     {
1346                         SwTextAttr *pAttr = pHints->Get( i );
1347 
1348                         if ( pAttr->GetStart() > nEnd )
1349                         {
1350                             break; // after the range
1351                         }
1352 
1353                         if ( !pAttr->GetEnd() || *pAttr->GetEnd() < nStart )
1354                         {
1355                             continue; // before the range
1356                         }
1357 
1358                         // range part before the hint
1359                         if ( nRedEnd < pAttr->GetStart() )
1360                         {
1361                             SwPaM aPam( *pNode, nRedEnd, *pNode, pAttr->GetStart() );
1362                             lcl_SetRedline(rDoc, aPam);
1363                         }
1364 
1365                         // range part at the hint
1366                         sal_Int32 nRedStart = std::max(pAttr->GetStart(), nStart);
1367                         nRedEnd = std::min(*pAttr->GetEnd(), nEnd);
1368                         SwPaM aPam2( *pNode, nRedStart, *pNode, nRedEnd );
1369                         lcl_SetRedline(rDoc, aPam2);
1370                     }
1371 
1372                     // range part after the last hint
1373                     if ( nRedEnd < nEnd )
1374                     {
1375                         SwPaM aPam( *pNode, nRedEnd, *pNode, nEnd );
1376                         lcl_SetRedline(rDoc, aPam);
1377                     }
1378                 }
1379                 else
1380                 {
1381                     SwPaM aPam( *pNode, nStart, *pNode, nEnd );
1382                     lcl_SetRedline(rDoc, aPam);
1383                 }
1384             }
1385             ++aIdx;
1386         }
1387     }
1388 
1389     /// Insert Hints according to content types;
1390     // Is used in SwDoc::Insert(..., SwFormatHint &rHt)
1391 
1392     bool lcl_InsAttr(
1393         SwDoc& rDoc,
1394         const SwPaM &rRg,
1395         const SfxItemSet& rChgSet,
1396         const SetAttrMode nFlags,
1397         SwUndoAttr *const pUndo,
1398         SwRootFrame const*const pLayout,
1399         SwTextAttr **ppNewTextAttr)
1400     {
1401         // Divide the Sets (for selections in Nodes)
1402         const SfxItemSet* pCharSet = nullptr;
1403         const SfxItemSet* pOtherSet = nullptr;
1404         bool bDelete = false;
1405         bool bCharAttr = false;
1406         bool bOtherAttr = false;
1407 
1408         // Check, if we can work with rChgSet or if we have to create additional SfxItemSets
1409         if ( 1 == rChgSet.Count() )
1410         {
1411             SfxItemIter aIter( rChgSet );
1412             const SfxPoolItem* pItem = aIter.GetCurItem();
1413             if (pItem && !IsInvalidItem(pItem))
1414             {
1415                 const sal_uInt16 nWhich = pItem->Which();
1416 
1417                 if ( isCHRATR(nWhich) ||
1418                      (RES_TXTATR_CHARFMT == nWhich) ||
1419                      (RES_TXTATR_INETFMT == nWhich) ||
1420                      (RES_TXTATR_AUTOFMT == nWhich) ||
1421                      (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) )
1422                 {
1423                     pCharSet  = &rChgSet;
1424                     bCharAttr = true;
1425                 }
1426 
1427                 if (    isPARATR(nWhich)
1428                      || isPARATR_LIST(nWhich)
1429                      || isFRMATR(nWhich)
1430                      || isGRFATR(nWhich)
1431                      || isUNKNOWNATR(nWhich)
1432                      || isDrawingLayerAttribute(nWhich) )
1433                 {
1434                     pOtherSet = &rChgSet;
1435                     bOtherAttr = true;
1436                 }
1437             }
1438         }
1439 
1440         // Build new itemset if either
1441         // - rChgSet.Count() > 1 or
1442         // - The attribute in rChgSet does not belong to one of the above categories
1443         if ( !bCharAttr && !bOtherAttr )
1444         {
1445             SfxItemSet* pTmpCharItemSet = new SfxItemSetFixed<
1446                     RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
1447                     RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT,
1448                     RES_TXTATR_UNKNOWN_CONTAINER,
1449                         RES_TXTATR_UNKNOWN_CONTAINER>( rDoc.GetAttrPool() );
1450 
1451             SfxItemSet* pTmpOtherItemSet = new SfxItemSetFixed<
1452                     RES_PARATR_BEGIN, RES_GRFATR_END - 1,
1453                     RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1,
1454                     // FillAttribute support:
1455                     XATTR_FILL_FIRST, XATTR_FILL_LAST>( rDoc.GetAttrPool() );
1456 
1457             pTmpCharItemSet->Put( rChgSet );
1458             pTmpOtherItemSet->Put( rChgSet );
1459 
1460             pCharSet = pTmpCharItemSet;
1461             pOtherSet = pTmpOtherItemSet;
1462 
1463             bDelete = true;
1464         }
1465 
1466         SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr;
1467         bool bRet = false;
1468         const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End();
1469         SwContentNode* pNode = pStt->GetNode().GetContentNode();
1470 
1471         if( pNode && pNode->IsTextNode() )
1472         {
1473             // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol
1474             if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()->
1475                     get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
1476             {
1477                 SwContentNode* pEndNode = pEnd->GetNode().GetContentNode();
1478                 SwContentNode* pCurrentNode = pEndNode;
1479                 auto nStartIndex = pNode->GetIndex();
1480                 auto nEndIndex = pEndNode->GetIndex();
1481                 SwNodeIndex aIdx( pEnd->GetNode() );
1482                 while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() )
1483                 {
1484                     if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) &&
1485                         // remove character formatting only on wholly selected paragraphs
1486                         (nStartIndex < pCurrentNode->GetIndex() || pStt->GetContentIndex() == 0) &&
1487                         (pCurrentNode->GetIndex() < nEndIndex || pEnd->GetContentIndex() == pEndNode->Len()))
1488                     {
1489                         pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT);
1490                         // reset also paragraph marker
1491                         pCurrentNode->GetTextNode()->RstTextAttr(pCurrentNode->Len(), 1);
1492                     }
1493                     pCurrentNode = SwNodes::GoPrevious( &aIdx );
1494                 }
1495             }
1496             // #i27615#
1497             if (rRg.IsInFrontOfLabel())
1498             {
1499                 SwTextNode * pTextNd = pNode->GetTextNode();
1500                 if (pLayout)
1501                 {
1502                     pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd);
1503                 }
1504                 SwNumRule * pNumRule = pTextNd->GetNumRule();
1505 
1506                 if ( !pNumRule )
1507                 {
1508                     OSL_FAIL( "<InsAttr(..)> - PaM in front of label, but text node has no numbering rule set. This is a serious defect." );
1509                     DELETECHARSETS
1510                     return false;
1511                 }
1512 
1513                 int nLevel = pTextNd->GetActualListLevel();
1514 
1515                 if (nLevel < 0)
1516                     nLevel = 0;
1517 
1518                 if (nLevel >= MAXLEVEL)
1519                     nLevel = MAXLEVEL - 1;
1520 
1521                 SwNumFormat aNumFormat = pNumRule->Get(o3tl::narrowing<sal_uInt16>(nLevel));
1522                 SwCharFormat * pCharFormat =
1523                     rDoc.FindCharFormatByName(aNumFormat.GetCharFormatName());
1524 
1525                 if (pCharFormat)
1526                 {
1527                     if (pHistory)
1528                         pHistory->AddCharFormat(pCharFormat->GetAttrSet(), *pCharFormat);
1529 
1530                     if ( pCharSet )
1531                         pCharFormat->SetFormatAttr(*pCharSet);
1532                 }
1533 
1534                 DELETECHARSETS
1535                 return true;
1536             }
1537 
1538             // Attributes without an end do not have a range
1539             if ( !bCharAttr && !bOtherAttr )
1540             {
1541                 SfxItemSetFixed<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1>
1542                     aTextSet( rDoc.GetAttrPool() );
1543                 aTextSet.Put( rChgSet );
1544                 if( aTextSet.Count() )
1545                 {
1546                     SwRegHistory history( pNode, *pNode, pHistory );
1547                     bRet = history.InsertItems(
1548                         aTextSet, pStt->GetContentIndex(), pStt->GetContentIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet;
1549 
1550                     if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1551                                     && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1552                     {
1553                         SwPaM aPam( pStt->GetNode(), pStt->GetContentIndex()-1,
1554                                     pStt->GetNode(), pStt->GetContentIndex() );
1555 
1556                         if( pUndo )
1557                             pUndo->SaveRedlineData( aPam, true );
1558 
1559                         if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1560                             rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
1561                         else
1562                             rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1563                     }
1564                 }
1565             }
1566 
1567             // TextAttributes with an end never expand their range
1568             if ( !bCharAttr && !bOtherAttr )
1569             {
1570                 // CharFormat and URL attributes are treated separately!
1571                 // TEST_TEMP ToDo: AutoFormat!
1572                 SfxItemSetFixed<
1573                         RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD,
1574                         RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY,
1575                         RES_TXTATR_INPUTFIELD, RES_TXTATR_CONTENTCONTROL>
1576                      aTextSet(rDoc.GetAttrPool());
1577 
1578                 aTextSet.Put( rChgSet );
1579                 if( aTextSet.Count() )
1580                 {
1581                     const sal_Int32 nInsCnt = pStt->GetContentIndex();
1582                     const sal_Int32 nEnd = pStt->GetNode() == pEnd->GetNode()
1583                                     ? pEnd->GetContentIndex()
1584                                     : pNode->Len();
1585                     SwRegHistory history( pNode, *pNode, pHistory );
1586                     bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr )
1587                            || bRet;
1588 
1589                     if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
1590                                     && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())))
1591                     {
1592                         // Was text content inserted? (RefMark/TOXMarks without an end)
1593                         bool bTextIns = nInsCnt != pStt->GetContentIndex();
1594                         // Was content inserted or set over the selection?
1595                         SwPaM aPam( pStt->GetNode(), bTextIns ? nInsCnt + 1 : nEnd,
1596                                     pStt->GetNode(), nInsCnt );
1597                         if( pUndo )
1598                             pUndo->SaveRedlineData( aPam, bTextIns );
1599 
1600                         if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1601                             rDoc.getIDocumentRedlineAccess().AppendRedline(
1602                                 new SwRangeRedline(
1603                                     bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ),
1604                                     true);
1605                         else if( bTextIns )
1606                             rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
1607                     }
1608                 }
1609             }
1610         }
1611 
1612         // We always have to set the auto flag for PageDescs that are set at the Node!
1613         if( pOtherSet && pOtherSet->Count() )
1614         {
1615             SwTableNode* pTableNd;
1616             const SwFormatPageDesc* pDesc = pOtherSet->GetItemIfSet( RES_PAGEDESC, false );
1617             if( pDesc )
1618             {
1619                 if( pNode )
1620                 {
1621                     // Set auto flag. Only in the template it's without auto!
1622                     SwFormatPageDesc aNew( *pDesc );
1623 
1624                     // Tables now also know line breaks
1625                     if( !(nFlags & SetAttrMode::APICALL) &&
1626                         nullptr != ( pTableNd = pNode->FindTableNode() ) )
1627                     {
1628                         SwTableNode* pCurTableNd = pTableNd;
1629                         while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1630                             pTableNd = pCurTableNd;
1631 
1632                         // set the table format
1633                         SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1634                         SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1635                         pFormat->SetFormatAttr( aNew );
1636                         bRet = true;
1637                     }
1638                     else
1639                     {
1640                         SwContentNode * pFirstNode(pNode);
1641                         if (pLayout && pLayout->HasMergedParas())
1642                         {
1643                             pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->GetNode()).first;
1644                         }
1645                         SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory );
1646                         bRet = pFirstNode->SetAttr( aNew ) || bRet;
1647                     }
1648                 }
1649 
1650                 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1651                 // we know, that there is only one attribute in pOtherSet. We cannot
1652                 // perform the following operations, instead we return:
1653                 if ( bOtherAttr )
1654                     return bRet;
1655 
1656                 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_PAGEDESC );
1657                 if( !pOtherSet->Count() )
1658                 {
1659                     DELETECHARSETS
1660                     return bRet;
1661                 }
1662             }
1663 
1664             // Tables now also know line breaks
1665             const SvxFormatBreakItem* pBreak;
1666             if( pNode && !(nFlags & SetAttrMode::APICALL) &&
1667                 nullptr != (pTableNd = pNode->FindTableNode() ) &&
1668                 (pBreak = pOtherSet->GetItemIfSet( RES_BREAK, false )) )
1669             {
1670                 SwTableNode* pCurTableNd = pTableNd;
1671                 while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) )
1672                     pTableNd = pCurTableNd;
1673 
1674                  // set the table format
1675                 SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat();
1676                 SwRegHistory aRegH( pFormat, *pTableNd, pHistory );
1677                 pFormat->SetFormatAttr( *pBreak );
1678                 bRet = true;
1679 
1680                 // bOtherAttr = true means that pOtherSet == rChgSet. In this case
1681                 // we know, that there is only one attribute in pOtherSet. We cannot
1682                 // perform the following operations, instead we return:
1683                 if ( bOtherAttr )
1684                     return bRet;
1685 
1686                 const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_BREAK );
1687                 if( !pOtherSet->Count() )
1688                 {
1689                     DELETECHARSETS
1690                     return bRet;
1691                 }
1692             }
1693 
1694             {
1695                 // If we have a PoolNumRule, create it if needed
1696                 sal_uInt16 nPoolId=0;
1697                 const SwNumRuleItem* pRule = pOtherSet->GetItemIfSet( RES_PARATR_NUMRULE, false );
1698                 if( pRule &&
1699                     !rDoc.FindNumRulePtr( pRule->GetValue() ) &&
1700                     USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(),
1701                                     SwGetPoolIdFromName::NumRule )) )
1702                     rDoc.getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId );
1703             }
1704         }
1705 
1706         SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(rDoc.GetAttrPool());
1707         if (pOtherSet && pOtherSet->Count())
1708         {   // actually only RES_BREAK is possible here...
1709             firstSet.Put(*pOtherSet);
1710         }
1711         SfxItemSetFixed
1712             <RES_PARATR_BEGIN, RES_PAGEDESC,
1713                        RES_BREAK+1, RES_FRMATR_END,
1714                        XATTR_FILL_FIRST, XATTR_FILL_LAST+1> propsSet(rDoc.GetAttrPool());
1715         if (pOtherSet && pOtherSet->Count())
1716         {
1717             propsSet.Put(*pOtherSet);
1718         }
1719 
1720         if( !rRg.HasMark() )        // no range
1721         {
1722             if( !pNode )
1723             {
1724                 DELETECHARSETS
1725                 return bRet;
1726             }
1727 
1728             if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1729             {
1730                 SwTextNode* pTextNd = pNode->GetTextNode();
1731                 sal_Int32 nMkPos, nPtPos = pStt->GetContentIndex();
1732                 const OUString& rStr = pTextNd->GetText();
1733 
1734                 // Special case: if the Cursor is located within a URL attribute, we take over it's area
1735                 SwTextAttr const*const pURLAttr(
1736                     pTextNd->GetTextAttrAt(pStt->GetContentIndex(), RES_TXTATR_INETFMT));
1737                 if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty())
1738                 {
1739                     nMkPos = pURLAttr->GetStart();
1740                     nPtPos = *pURLAttr->End();
1741                 }
1742                 else
1743                 {
1744                     assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1745                     Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
1746                                         pTextNd->GetText(), nPtPos,
1747                                         g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
1748                                         WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
1749                                         true);
1750 
1751                     if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos )
1752                     {
1753                         nMkPos = aBndry.startPos;
1754                         nPtPos = aBndry.endPos;
1755                     }
1756                     else
1757                         nPtPos = nMkPos = pStt->GetContentIndex();
1758                 }
1759 
1760                 // Remove the overriding attributes from the SwpHintsArray,
1761                 // if the selection spans across the whole paragraph.
1762                 // These attributes are inserted as FormatAttributes and
1763                 // never override the TextAttributes!
1764                 if( !(nFlags & SetAttrMode::DONTREPLACE ) &&
1765                     pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength())
1766                 {
1767                     if( pHistory )
1768                     {
1769                         // Save all attributes for the Undo.
1770                         SwRegHistory aRHst( *pTextNd, pHistory );
1771                         pTextNd->GetpSwpHints()->Register( &aRHst );
1772                         pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1773                         if( pTextNd->GetpSwpHints() )
1774                             pTextNd->GetpSwpHints()->DeRegister();
1775                     }
1776                     else
1777                         pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet );
1778                 }
1779 
1780                 if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
1781                 {
1782                     SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos );
1783 
1784                     if( pUndo )
1785                         pUndo->SaveRedlineData( aPam, false );
1786 
1787                     lcl_SetRedlines(rDoc, aPam);
1788                 }
1789 
1790                 // the SwRegHistory inserts the attribute into the TextNode!
1791                 SwRegHistory history( pNode, *pNode, pHistory );
1792 
1793                 bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr )
1794                     || bRet;
1795 
1796             }
1797             if( pOtherSet && pOtherSet->Count() )
1798             {
1799                 // Need to check for unique item for DrawingLayer items of type NameOrIndex
1800                 // and evtl. correct that item to ensure unique names for that type. This call may
1801                 // modify/correct entries inside of the given SfxItemSet
1802                 SfxItemSet aTempLocalCopy(*pOtherSet);
1803 
1804                 rDoc.CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy);
1805                 bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet;
1806             }
1807 
1808             DELETECHARSETS
1809             return bRet;
1810         }
1811 
1812         if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() )
1813         {
1814             if( pUndo )
1815                 pUndo->SaveRedlineData( rRg, false );
1816 
1817             lcl_SetRedlines(rDoc, rRg);
1818         }
1819 
1820         /* now if range */
1821         sal_uLong nNodes = 0;
1822 
1823         SwNodeIndex aSt( rDoc.GetNodes() );
1824         SwNodeIndex aEnd( rDoc.GetNodes() );
1825         SwContentIndex aCntEnd( pEnd->GetContentNode(), pEnd->GetContentIndex() );
1826 
1827         if( pNode )
1828         {
1829             const sal_Int32 nLen = pNode->Len();
1830             if( pStt->GetNode() != pEnd->GetNode() )
1831                 aCntEnd.Assign( pNode, nLen );
1832 
1833             if( pStt->GetContentIndex() != 0 || aCntEnd.GetIndex() != nLen )
1834             {
1835                 // the SwRegHistory inserts the attribute into the TextNode!
1836                 if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1837                 {
1838                     SwRegHistory history( pNode, *pNode, pHistory );
1839                     bRet = history.InsertItems(*pCharSet,
1840                             pStt->GetContentIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr)
1841                         || bRet;
1842                 }
1843 
1844                 if( pOtherSet && pOtherSet->Count() )
1845                 {
1846                     bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet;
1847                 }
1848 
1849                 // Only selection in a Node.
1850                 if( pStt->GetNode() == pEnd->GetNode() )
1851                 {
1852                     DELETECHARSETS
1853                     return bRet;
1854                 }
1855                 ++nNodes;
1856                 aSt.Assign( pStt->GetNode(), +1 );
1857             }
1858             else
1859                 aSt = pStt->GetNode();
1860             aCntEnd.Assign(pEnd->GetContentNode(), pEnd->GetContentIndex()); // aEnd was changed!
1861         }
1862         else
1863             aSt.Assign( pStt->GetNode(), +1 );
1864 
1865         // aSt points to the first full Node now
1866 
1867         /*
1868          * The selection spans more than one Node.
1869          */
1870         if( pStt->GetNode() < pEnd->GetNode() )
1871         {
1872             pNode = pEnd->GetNode().GetContentNode();
1873             if(pNode)
1874             {
1875                 if( aCntEnd.GetIndex() != pNode->Len() )
1876                 {
1877                     // the SwRegHistory inserts the attribute into the TextNode!
1878                     if( pNode->IsTextNode() && pCharSet && pCharSet->Count() )
1879                     {
1880                         SwRegHistory history( pNode, *pNode, pHistory );
1881                         (void)history.InsertItems(*pCharSet,
1882                                 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr);
1883                     }
1884 
1885                     if( pOtherSet && pOtherSet->Count() )
1886                     {
1887                         lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout);
1888                     }
1889 
1890                     ++nNodes;
1891                     aEnd = pEnd->GetNode();
1892                 }
1893                 else
1894                     aEnd.Assign( pEnd->GetNode(), +1 );
1895             }
1896             else
1897                 aEnd = pEnd->GetNode();
1898         }
1899         else
1900             aEnd.Assign( pEnd->GetNode(), +1 );
1901 
1902         // aEnd points BEHIND the last full node now
1903 
1904         /* Edit the fully selected Nodes. */
1905         // Reset all attributes from the set!
1906         if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) )
1907         {
1908             ::sw::DocumentContentOperationsManager::ParaRstFormat aPara(
1909                     pStt, pEnd, pHistory, pCharSet, pLayout);
1910             rDoc.GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara );
1911         }
1912 
1913         bool bCreateSwpHints = pCharSet && (
1914             SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) ||
1915             SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) );
1916 
1917         for (SwNodeIndex current = aSt; current < aEnd; ++current)
1918         {
1919             SwTextNode *const pTNd = current.GetNode().GetTextNode();
1920             if (!pTNd)
1921                 continue;
1922 
1923             if (pLayout && pLayout->HasMergedParas()
1924                 && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1925             {   // not really sure what to do here, but applying to hidden
1926                 continue; // nodes doesn't make sense...
1927             }
1928 
1929             if( pHistory )
1930             {
1931                 SwRegHistory aRegH( pTNd, *pTNd, pHistory );
1932 
1933                 if (pCharSet && pCharSet->Count())
1934                 {
1935                     if (SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints()
1936                                                 : pTNd->GetpSwpHints())
1937                     {
1938                         pSwpHints->Register( &aRegH );
1939                     }
1940 
1941                     pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1942 
1943                     // re-fetch as it may be deleted by SetAttr
1944                     if (SwpHints *pSwpHints = pTNd->GetpSwpHints())
1945                         pSwpHints->DeRegister();
1946                 }
1947             }
1948             else
1949             {
1950                 if (pCharSet && pCharSet->Count())
1951                     pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags);
1952             }
1953             ++nNodes;
1954         }
1955 
1956         if (pOtherSet && pOtherSet->Count())
1957         {
1958             for (; aSt < aEnd; ++aSt)
1959             {
1960                 pNode = aSt.GetNode().GetContentNode();
1961                 if (!pNode)
1962                     continue;
1963 
1964                 lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt);
1965                 ++nNodes;
1966             }
1967         }
1968 
1969         DELETECHARSETS
1970         return (nNodes != 0) || bRet;
1971     }
1972 }
1973 
1974 namespace sw
1975 {
1976 
1977 namespace mark
1978 {
1979     bool IsFieldmarkOverlap(SwPaM const& rPaM)
1980     {
1981         std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
1982         sw::CalcBreaks(Breaks, rPaM);
1983         return !Breaks.empty();
1984     }
1985 }
1986 
1987 DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc )
1988 {
1989 }
1990 
1991 /**
1992  * Checks if rStart..rEnd mark a range that makes sense to copy.
1993  *
1994  * IsMoveToFly means the copy is a move to create a fly
1995  * and so existing flys at the edge must not be copied.
1996  */
1997 static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd,
1998         SwCopyFlags const flags)
1999 {
2000     if (rStart == rEnd)
2001     {   // check if a fly anchored there would be copied - then copy...
2002         return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd,
2003                 (flags & SwCopyFlags::IsMoveToFly)
2004                     ? DelContentType::WriterfilterHack|DelContentType::AllMask
2005                     : DelContentType::AllMask);
2006     }
2007     else
2008     {
2009         return rEnd < rStart;
2010     }
2011 }
2012 
2013 // Copy an area into this document or into another document
2014 bool DocumentContentOperationsManager::CopyRange(SwPaM& rPam, SwPosition& rPos,
2015                                                  SwCopyFlags const flags,
2016                                                  sal_uInt32 nMovedID) const
2017 {
2018     const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End();
2019 
2020     SwDoc& rDoc = rPos.GetNode().GetDoc();
2021     bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
2022 
2023     // Catch if there's no copy to do
2024     if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel))
2025         return false;
2026 
2027     // Prevent copying into Flys that are anchored in the source range
2028     if (&rDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly))
2029     {
2030         // Correct the Start-/EndNode
2031         SwNodeOffset nStt = pStt->GetNodeIndex(),
2032                 nEnd = pEnd->GetNodeIndex(),
2033                 nDiff = nEnd - nStt +1;
2034         SwNode* pNd = m_rDoc.GetNodes()[ nStt ];
2035         if( pNd->IsContentNode() && pStt->GetContentIndex() )
2036         {
2037             ++nStt;
2038             --nDiff;
2039         }
2040         if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() &&
2041             static_cast<SwContentNode*>(pNd)->Len() != pEnd->GetContentIndex() )
2042         {
2043             --nEnd;
2044             --nDiff;
2045         }
2046         if( nDiff &&
2047             lcl_ChkFlyFly( rDoc, nStt, nEnd, rPos.GetNodeIndex() ) )
2048         {
2049             return false;
2050         }
2051     }
2052 
2053     SwPaM* pRedlineRange = nullptr;
2054     if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2055         (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) )
2056         pRedlineRange = new SwPaM( rPos );
2057 
2058     RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2059 
2060     bool bRet = false;
2061 
2062     if( &rDoc != &m_rDoc )
2063     {   // ordinary copy
2064         bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange);
2065     }
2066     else if( ! ( *pStt <= rPos && rPos < *pEnd &&
2067             ( pStt->GetNode() != pEnd->GetNode() ||
2068               !pStt->GetNode().IsTextNode() )) )
2069     {
2070         // Copy to a position outside of the area, or copy a single TextNode
2071         // Do an ordinary copy
2072         bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange);
2073     }
2074     else
2075     {
2076         // Copy the range in itself
2077         assert(!"mst: this is assumed to be dead code");
2078     }
2079 
2080     rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
2081     if( pRedlineRange )
2082     {
2083         if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2084             rDoc.getIDocumentRedlineAccess().AppendRedline(
2085                 new SwRangeRedline(RedlineType::Insert, *pRedlineRange, nMovedID), true);
2086         else
2087             rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange );
2088         delete pRedlineRange;
2089     }
2090 
2091     return bRet;
2092 }
2093 
2094 static auto GetCorrPosition(SwPaM const& rPam) -> SwPosition
2095 {
2096     // tdf#152710 target position must be on node that survives deletion
2097     // so that PaMCorrAbs can invalidate SwUnoCursors properly
2098     return rPam.GetPoint()->GetNode().IsContentNode()
2099             ? *rPam.GetPoint()
2100             : rPam.GetMark()->GetNode().IsContentNode()
2101                 ? *rPam.GetMark()
2102                 // this would be the result in SwNodes::RemoveNode()
2103                 : SwPosition(rPam.End()->GetNode(), SwNodeOffset(+1));
2104 }
2105 
2106 /// Delete a full Section of the NodeArray.
2107 /// The passed Node is located somewhere in the designated Section.
2108 void DocumentContentOperationsManager::DeleteSection( SwNode *pNode )
2109 {
2110     assert(pNode && "Didn't pass a Node.");
2111 
2112     SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast<SwStartNode*>(pNode)
2113                                                : pNode->StartOfSectionNode();
2114     SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() );
2115 
2116     // delete all Flys, Bookmarks, ...
2117     DelFlyInRange( aSttIdx.GetNode(), aEndIdx.GetNode() );
2118     m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any );
2119     DelBookmarks(aSttIdx.GetNode(), aEndIdx.GetNode());
2120 
2121     {
2122         // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area
2123         SwPaM const range(aSttIdx, aEndIdx);
2124         SwPosition const pos(GetCorrPosition(range));
2125         ::PaMCorrAbs(range, pos);
2126     }
2127 
2128     m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 );
2129 }
2130 
2131 void DocumentContentOperationsManager::DeleteDummyChar(
2132         SwPosition const& rPos, sal_Unicode const cDummy)
2133 {
2134     SwPaM aPam(rPos, rPos);
2135     aPam.GetPoint()->AdjustContent(+1);
2136     assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy);
2137     (void) cDummy;
2138 
2139     DeleteRangeImpl(aPam, SwDeleteFlags::Default);
2140 
2141     if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2142         && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2143     {
2144         m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2145     }
2146 }
2147 
2148 void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam )
2149 {
2150     // Seek all redlines that are in that PaM to be deleted..
2151     SwRedlineTable::size_type nRedlStart = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos(
2152         rPam.Start()->GetNode(), RedlineType::Any);
2153     SwRedlineTable::size_type nRedlEnd = m_rDoc.getIDocumentRedlineAccess().GetRedlineEndPos(
2154         nRedlStart, rPam.End()->GetNode(), RedlineType::Any);
2155 
2156     lcl_DoWithBreaks(*this, rPam, SwDeleteFlags::Default, &DocumentContentOperationsManager::DeleteRangeImpl);
2157 
2158     // update all redlines was in the Pam that is
2159     m_rDoc.getIDocumentRedlineAccess().UpdateRedlineContentNode(nRedlStart, nRedlEnd);
2160 
2161     if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2162         && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2163     {
2164         m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2165     }
2166 }
2167 
2168 bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam )
2169 {
2170     const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End();
2171     const SwNode* pNd = &rStt.GetNode();
2172     SwNodeOffset nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() -
2173                         pNd->StartOfSectionIndex();
2174     SwNodeOffset nNodeDiff = rEnd.GetNodeIndex() - rStt.GetNodeIndex();
2175 
2176     if ( nSectDiff-SwNodeOffset(2) <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
2177          /* #i9185# Prevent getting the node after the end node (see below) */
2178         rEnd.GetNodeIndex() + 1 == m_rDoc.GetNodes().Count() )
2179     {
2180         return false;
2181     }
2182 
2183     {
2184         SwPaM temp(rPam, nullptr);
2185         if (!temp.HasMark())
2186         {
2187             temp.SetMark();
2188         }
2189         if (SwTextNode *const pNode = temp.Start()->GetNode().GetTextNode())
2190         { // rPam may not have nContent set but IsFieldmarkOverlap requires it
2191             temp.Start()->AssignStartIndex(*pNode);
2192         }
2193         if (SwTextNode *const pNode = temp.End()->GetNode().GetTextNode())
2194         {
2195             temp.End()->AssignEndIndex(*pNode);
2196         }
2197         if (sw::mark::IsFieldmarkOverlap(temp))
2198         {   // a bit of a problem: we want to completely remove the nodes
2199             // but then how can the CH_TXT_ATR survive?
2200             return false;
2201         }
2202     }
2203 
2204     // Move hard page breaks to the following Node.
2205     bool bSavePageBreak = false, bSavePageDesc = false;
2206 
2207     /* #i9185# This would lead to a segmentation fault if not caught above. */
2208     SwNodeOffset nNextNd = rEnd.GetNodeIndex() + 1;
2209     SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode();
2210 
2211     if( pTableNd && pNd->IsContentNode() )
2212     {
2213         SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat();
2214 
2215         {
2216             const SfxPoolItem *pItem;
2217             const SfxItemSet* pSet = static_cast<const SwContentNode*>(pNd)->GetpSwAttrSet();
2218             if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,
2219                 false, &pItem ) )
2220             {
2221                 pTableFormat->SetFormatAttr( *pItem );
2222                 bSavePageDesc = true;
2223             }
2224 
2225             if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK,
2226                 false, &pItem ) )
2227             {
2228                 pTableFormat->SetFormatAttr( *pItem );
2229                 bSavePageBreak = true;
2230             }
2231         }
2232     }
2233 
2234     bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2235     if( bDoesUndo )
2236     {
2237         if( !rPam.HasMark() )
2238             rPam.SetMark();
2239         else if( rPam.GetPoint() == &rStt )
2240             rPam.Exchange();
2241         rPam.GetPoint()->Adjust(SwNodeOffset(1));
2242 
2243         SwContentNode *pTmpNode = rPam.GetPoint()->GetNode().GetContentNode();
2244         bool bGoNext = (nullptr == pTmpNode);
2245 
2246         if (rPam.GetMark()->GetContentNode())
2247             rPam.GetMark()->SetContent( 0 );
2248 
2249         m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2250 
2251         SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
2252         {
2253             SwPosition aTmpPos( *aDelPam.GetPoint() );
2254             if( bGoNext )
2255             {
2256                 SwNodes::GoNext(&aTmpPos);
2257             }
2258             ::PaMCorrAbs( aDelPam, aTmpPos );
2259         }
2260 
2261         std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aDelPam, SwDeleteFlags::Default, true));
2262 
2263         *rPam.GetPoint() = *aDelPam.GetPoint();
2264         pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc );
2265         m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2266         rPam.DeleteMark();
2267     }
2268     else
2269     {
2270         SwNodeRange aRg( rStt.GetNode(), rEnd.GetNode() );
2271         rPam.Normalize(false);
2272 
2273         // Try to move past the End
2274         if( !rPam.Move( fnMoveForward, GoInNode ) )
2275         {
2276             // Fair enough, at the Beginning then
2277             rPam.Exchange();
2278             if( !rPam.Move( fnMoveBackward, GoInNode ))
2279             {
2280                 SAL_WARN("sw.core", "DelFullPara: no more Nodes");
2281                 return false;
2282             }
2283         }
2284 
2285         // must delete all fieldmarks before CorrAbs(), or they'll remain
2286         // moved to wrong node without their CH_TXT_ATR_FIELD*
2287         // (note: deleteMarks() doesn't help here, in case of partially
2288         // selected fieldmarks; let's delete these as re-inserting their chars
2289         // elsewhere looks difficult)
2290         ::std::set<::sw::mark::IFieldmark*> fieldmarks;
2291         for (SwNodeIndex i = aRg.aStart; i <= aRg.aEnd; ++i)
2292         {
2293             if (SwTextNode *const pTextNode = i.GetNode().GetTextNode())
2294             {
2295                 for (sal_Int32 j = 0; j < pTextNode->GetText().getLength(); ++j)
2296                 {
2297                     switch (pTextNode->GetText()[j])
2298                     {
2299                         case CH_TXT_ATR_FIELDSTART:
2300                         case CH_TXT_ATR_FIELDEND:
2301                             fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getFieldmarkAt(SwPosition(*pTextNode, j)));
2302                         break;
2303                         case CH_TXT_ATR_FIELDSEP:
2304                             fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getInnerFieldmarkFor(SwPosition(*pTextNode, j)));
2305                         break;
2306                     }
2307                 }
2308             }
2309         }
2310         for (auto const pFieldMark : fieldmarks)
2311         {
2312             m_rDoc.getIDocumentMarkAccess()->deleteMark(pFieldMark);
2313         }
2314 
2315         // move bookmarks, redlines etc.
2316         if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this
2317         {
2318             m_rDoc.CorrAbs( aRg.aStart.GetNode(), *rPam.GetPoint(), 0, true );
2319         }
2320         else
2321         {
2322             SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true );
2323         }
2324 
2325             // What's with Flys?
2326         {
2327             // If there are FlyFrames left, delete these too
2328             for( size_t n = 0; n < m_rDoc.GetSpzFrameFormats()->size(); ++n )
2329             {
2330                 sw::SpzFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n];
2331                 const SwFormatAnchor* pAnchor = &pFly->GetAnchor();
2332                 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
2333                 if (pAnchorNode &&
2334                     ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
2335                      (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
2336                     // note: here use <= not < like in
2337                     // IsDestroyFrameAnchoredAtChar() because of the increment
2338                     // of rPam in the bDoesUndo path above!
2339                     aRg.aStart <= *pAnchorNode && *pAnchorNode <= aRg.aEnd.GetNode() )
2340                 {
2341                     m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly );
2342                     --n;
2343                 }
2344             }
2345         }
2346 
2347         rPam.DeleteMark();
2348         m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 );
2349     }
2350 
2351     if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
2352         && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2353     {
2354         m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
2355     }
2356 
2357     m_rDoc.getIDocumentState().SetModified();
2358 
2359     return true;
2360 }
2361 
2362 bool DocumentContentOperationsManager::DeleteAndJoin(SwPaM & rPam, SwDeleteFlags const flags)
2363 {
2364     if ( lcl_StrLenOverflow( rPam ) )
2365         return false;
2366 
2367     bool const ret = lcl_DoWithBreaks( *this, rPam, flags, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
2368                 ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl
2369                 : &DocumentContentOperationsManager::DeleteAndJoinImpl );
2370 
2371     return ret;
2372 }
2373 
2374 // It seems that this is mostly used by SwDoc internals; the only
2375 // way to call this from the outside seems to be the special case in
2376 // SwDoc::CopyRange (but I have not managed to actually hit that case).
2377 bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags )
2378 {
2379     // nothing moved: return
2380     const SwPosition *pStt = rPaM.Start(), *pEnd = rPaM.End();
2381     if( !rPaM.HasMark() || *pStt >= *pEnd || (*pStt <= rPos && rPos < *pEnd))
2382         return false;
2383 
2384     assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created?
2385 
2386     // Save the paragraph anchored Flys, so that they can be moved.
2387     SaveFlyArr aSaveFlyArr;
2388     SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) );
2389 
2390     // save redlines (if DOC_MOVEREDLINES is used)
2391     SaveRedlines_t aSaveRedl;
2392     if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2393     {
2394         lcl_SaveRedlines( rPaM, aSaveRedl );
2395 
2396         // #i17764# unfortunately, code below relies on undos being
2397         //          in a particular order, and presence of bookmarks
2398         //          will change this order. Hence, we delete bookmarks
2399         //          here without undo.
2400         ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
2401         DelBookmarks(
2402             pStt->GetNode(),
2403             pEnd->GetNode(),
2404             nullptr,
2405             pStt->GetContentIndex(),
2406             pEnd->GetContentIndex());
2407     }
2408 
2409     bool bUpdateFootnote = false;
2410     SwFootnoteIdxs aTmpFntIdx;
2411 
2412     std::unique_ptr<SwUndoMove> pUndoMove;
2413     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2414     {
2415         m_rDoc.GetIDocumentUndoRedo().ClearRedo();
2416         pUndoMove.reset(new SwUndoMove( rPaM, rPos ));
2417         pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES );
2418     }
2419     else
2420     {
2421         bUpdateFootnote = lcl_SaveFootnote( pStt->GetNode(), pEnd->GetNode(), rPos.GetNode(),
2422                                     m_rDoc.GetFootnoteIdxs(), aTmpFntIdx,
2423                                     pStt->GetContentIndex(), pEnd->GetContentIndex() );
2424     }
2425 
2426     bool bSplit = false;
2427     SwPaM aSavePam( rPos, rPos );
2428 
2429     // Move the SPoint to the beginning of the range
2430     if( rPaM.GetPoint() == pEnd )
2431         rPaM.Exchange();
2432 
2433     // If there is a TextNode before and after the Move, create a JoinNext in the EditShell.
2434     SwTextNode* pSrcNd = rPaM.GetPoint()->GetNode().GetTextNode();
2435     bool bCorrSavePam = pSrcNd && pStt->GetNode() != pEnd->GetNode();
2436 
2437     // If one or more TextNodes are moved, SwNodes::Move will do a SplitNode.
2438     // However, this does not update the cursor. So we create a TextNode to keep
2439     // updating the indices. After the Move the Node is optionally deleted.
2440     SwTextNode * pTNd = rPos.GetNode().GetTextNode();
2441     if( pTNd && rPaM.GetPoint()->GetNode() != rPaM.GetMark()->GetNode() &&
2442         ( rPos.GetContentIndex() || ( pTNd->Len() && bCorrSavePam  )) )
2443     {
2444         bSplit = true;
2445         const sal_Int32 nMkContent = rPaM.GetMark()->GetContentIndex();
2446 
2447         const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
2448         pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
2449 
2450         SwTextNode * pOrigNode = pTNd;
2451         assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2452                *aSavePam.GetPoint() == rPos);
2453         assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2454         assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2455         assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2456 
2457         std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc(
2458             [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool)
2459             {
2460                 if (!pContentStore->Empty())
2461                 {
2462                     pContentStore->Restore(m_rDoc, pOrigNode->GetIndex()-SwNodeOffset(1), 0, true, false, eMode);
2463                 }
2464             });
2465         pTNd->SplitContentNode(rPos, &restoreFunc);
2466 
2467         //A new node was inserted before the orig pTNd and the content up to
2468         //rPos moved into it. The old node is returned with the remainder
2469         //of the content in it.
2470         //
2471         //aSavePam was created with rPos, it continues to point to the
2472         //old node, but with the *original* content index into the node.
2473         //Seeing as all the orignode content before that index has
2474         //been removed, the new index into the original node should now be set
2475         //to 0 and the content index of rPos should also be adapted to the
2476         //truncated node
2477         assert(*aSavePam.GetPoint() == *aSavePam.GetMark() &&
2478                *aSavePam.GetPoint() == rPos);
2479         assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode);
2480         assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode());
2481         assert(rPos.GetNodeIndex() == pOrigNode->GetIndex());
2482         aSavePam.GetPoint()->SetContent(0);
2483         rPos = *aSavePam.GetMark() = *aSavePam.GetPoint();
2484 
2485         // correct the PaM!
2486         if( rPos.GetNode() == rPaM.GetMark()->GetNode() )
2487         {
2488             rPaM.GetMark()->Assign( rPos.GetNodeIndex() - SwNodeOffset(1) );
2489             rPaM.GetMark()->SetContent( nMkContent );
2490         }
2491     }
2492 
2493     // Put back the Pam by one "content"; so that it's always outside of
2494     // the manipulated range.
2495     // tdf#99692 don't Move() back if that would end up in another node
2496     // because moving backward is not necessarily the inverse of forward then.
2497     // (but do Move() back if we have split the node)
2498     const bool bNullContent = !bSplit && aSavePam.GetPoint()->GetContentIndex() == 0;
2499     if( bNullContent )
2500     {
2501         aSavePam.GetPoint()->Adjust(SwNodeOffset(-1));
2502     }
2503     else
2504     {
2505         bool const success(aSavePam.Move(fnMoveBackward, GoInContent));
2506         assert(success);
2507         (void) success;
2508     }
2509 
2510     // Copy all Bookmarks that are within the Move range into an array,
2511     // that saves the position as an offset.
2512     std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2513     DelBookmarks(
2514         pStt->GetNode(),
2515         pEnd->GetNode(),
2516         &aSaveBkmks,
2517         pStt->GetContentIndex(),
2518         pEnd->GetContentIndex());
2519 
2520     // If there is no range anymore due to the above deletions (e.g. the
2521     // footnotes got deleted), it's still a valid Move!
2522     if( *rPaM.GetPoint() != *rPaM.GetMark() )
2523     {
2524         // now do the actual move
2525         m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() );
2526 
2527         // after a MoveRange() the Mark is deleted
2528         if ( rPaM.HasMark() ) // => no Move occurred!
2529         {
2530             return false;
2531         }
2532     }
2533     else
2534         rPaM.DeleteMark();
2535 
2536     OSL_ENSURE( *aSavePam.GetMark() == rPos ||
2537             ( aSavePam.GetMark()->GetNode().GetContentNode() == nullptr ),
2538             "PaM was not moved. Aren't there ContentNodes at the beginning/end?" );
2539     *aSavePam.GetMark() = rPos;
2540 
2541     rPaM.SetMark();         // create a Sel. around the new range
2542     pTNd = aSavePam.GetPointNode().GetTextNode();
2543     assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo());
2544     bool bRemove = true;
2545     // Do two Nodes have to be joined at the SavePam?
2546     if (bSplit && pTNd)
2547     {
2548         if (pTNd->CanJoinNext())
2549         {
2550             // Always join next, because <pTNd> has to stay as it is.
2551             // A join previous from its next would more or less delete <pTNd>
2552             pTNd->JoinNext();
2553             bRemove = false;
2554         }
2555     }
2556     if (bNullContent)
2557     {
2558         aSavePam.GetPoint()->Adjust(SwNodeOffset(1));
2559     }
2560     else if (bRemove) // No move forward after joining with next paragraph
2561     {
2562         aSavePam.Move( fnMoveForward, GoInContent );
2563     }
2564 
2565     // Insert the Bookmarks back into the Document.
2566     *rPaM.GetMark() = *aSavePam.Start();
2567     for(auto& rBkmk : aSaveBkmks)
2568         rBkmk.SetInDoc(
2569             &m_rDoc,
2570             rPaM.GetMark()->GetNode(),
2571             rPaM.GetMark()->GetContentIndex());
2572     *rPaM.GetPoint() = *aSavePam.End();
2573 
2574     // Move the Flys to the new position.
2575     // note: rPos is at the end here; can't really tell flys that used to be
2576     // at the start of rPam from flys that used to be at the end of rPam
2577     // unfortunately, so some of them are going to end up with wrong anchor...
2578     RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &rPos.GetNode() );
2579 
2580     // restore redlines (if DOC_MOVEREDLINES is used)
2581     if( !aSaveRedl.empty() )
2582     {
2583         lcl_RestoreRedlines( m_rDoc, *aSavePam.Start(), aSaveRedl );
2584     }
2585 
2586     if( bUpdateFootnote )
2587     {
2588         if( !aTmpFntIdx.empty() )
2589         {
2590             m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2591             aTmpFntIdx.clear();
2592         }
2593 
2594         m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2595     }
2596 
2597     m_rDoc.getIDocumentState().SetModified();
2598     return true;
2599 }
2600 
2601 bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNode& rDestNd,
2602         SwMoveFlags eMvFlags )
2603 {
2604     // Moves all Nodes to the new position.
2605     // Bookmarks are moved too (currently without Undo support).
2606 
2607     // If footnotes are being moved to the special section, remove them now.
2608 
2609     // Or else delete the Frames for all footnotes that are being moved
2610     // and have it rebuild after the Move (footnotes can change pages).
2611     // Additionally we have to correct the FootnoteIdx array's sorting.
2612     bool bUpdateFootnote = false;
2613     SwFootnoteIdxs aTmpFntIdx;
2614 
2615     std::unique_ptr<SwUndoMove> pUndo;
2616     if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2617     {
2618         pUndo.reset(new SwUndoMove( m_rDoc, rRange, rDestNd ));
2619     }
2620     else
2621     {
2622         bUpdateFootnote = lcl_SaveFootnote( rRange.aStart.GetNode(), rRange.aEnd.GetNode(), rDestNd,
2623                                     m_rDoc.GetFootnoteIdxs(), aTmpFntIdx );
2624     }
2625 
2626     SaveRedlines_t aSaveRedl;
2627     std::vector<SwRangeRedline*> aSavRedlInsPosArr;
2628     if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
2629     {
2630         lcl_SaveRedlines( rRange, aSaveRedl );
2631 
2632         // Find all RedLines that end at the InsPos.
2633         // These have to be moved back to the "old" position after the Move.
2634         SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rDestNd, RedlineType::Any );
2635         if( SwRedlineTable::npos != nRedlPos )
2636         {
2637             const SwPosition *pRStt, *pREnd;
2638             do {
2639                 SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
2640                 pRStt = pTmp->Start();
2641                 pREnd = pTmp->End();
2642                 if( pREnd->GetNode() == rDestNd && pRStt->GetNode() < rDestNd )
2643                 {
2644                     aSavRedlInsPosArr.push_back( pTmp );
2645                 }
2646             } while( pRStt->GetNode() < rDestNd && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size());
2647         }
2648     }
2649 
2650     // Copy all Bookmarks that are within the Move range into an array
2651     // that stores all references to positions as an offset.
2652     // The final mapping happens after the Move.
2653     std::vector< ::sw::mark::SaveBookmark> aSaveBkmks;
2654     DelBookmarks(rRange.aStart.GetNode(), rRange.aEnd.GetNode(), &aSaveBkmks);
2655 
2656     // Save the paragraph-bound Flys, so that they can be moved.
2657     SaveFlyArr aSaveFlyArr;
2658     if( !m_rDoc.GetSpzFrameFormats()->empty() )
2659         SaveFlyInRange( rRange, aSaveFlyArr );
2660 
2661     // Set it to before the Position, so that it cannot be moved further.
2662     SwNodeIndex aIdx( rDestNd, -1 );
2663 
2664     std::optional<SwNodeIndex> oSaveInsPos;
2665     if( pUndo )
2666         oSaveInsPos.emplace(rRange.aStart, -1 );
2667 
2668     // move the Nodes
2669     bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags);
2670     if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rDestNd, !bNoDelFrames ) )
2671     {
2672         ++aIdx;     // again back to old position
2673         if( oSaveInsPos )
2674             ++(*oSaveInsPos);
2675     }
2676     else
2677     {
2678         aIdx = rRange.aStart;
2679         pUndo.reset();
2680     }
2681 
2682     // move the Flys to the new position
2683     if( !aSaveFlyArr.empty() )
2684     {
2685         SwPosition const tmp(aIdx);
2686         RestFlyInRange(aSaveFlyArr, tmp, nullptr);
2687     }
2688 
2689     // Add the Bookmarks back to the Document
2690     for(auto& rBkmk : aSaveBkmks)
2691         rBkmk.SetInDoc(&m_rDoc, aIdx.GetNode());
2692 
2693     if( !aSavRedlInsPosArr.empty() )
2694     {
2695         for(SwRangeRedline* pTmp : aSavRedlInsPosArr)
2696         {
2697             if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) )
2698             {
2699                 SwPosition* pEnd = pTmp->End();
2700                 pEnd->Assign(aIdx);
2701             }
2702         }
2703     }
2704 
2705     if( !aSaveRedl.empty() )
2706         lcl_RestoreRedlines( m_rDoc, aIdx.GetIndex(), aSaveRedl );
2707 
2708     if( pUndo )
2709     {
2710         pUndo->SetDestRange( aIdx.GetNode(), rDestNd, *oSaveInsPos );
2711         m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2712     }
2713 
2714     oSaveInsPos.reset();
2715 
2716     if( bUpdateFootnote )
2717     {
2718         if( !aTmpFntIdx.empty() )
2719         {
2720             m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx );
2721             aTmpFntIdx.clear();
2722         }
2723 
2724         m_rDoc.GetFootnoteIdxs().UpdateAllFootnote();
2725     }
2726 
2727     m_rDoc.getIDocumentState().SetModified();
2728     return true;
2729 }
2730 
2731 void DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos )
2732 {
2733     SwNodeIndex aIdx( rPaM.Start()->GetNode() );
2734     bool bJoinText = aIdx.GetNode().IsTextNode();
2735     bool bOneNode = rPaM.GetPoint()->GetNode() == rPaM.GetMark()->GetNode();
2736     --aIdx;             // in front of the move area!
2737 
2738     bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT );
2739     if( !bRet || bOneNode )
2740         return;
2741 
2742     if( bJoinText )
2743         ++aIdx;
2744     SwTextNode * pTextNd = aIdx.GetNode().GetTextNode();
2745     SwNodeIndex aNxtIdx( aIdx );
2746     if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) )
2747     {
2748         {   // Block so SwContentIndex into node is deleted before Join
2749             m_rDoc.CorrRel( aNxtIdx.GetNode(),
2750                             SwPosition( *pTextNd, pTextNd->GetText().getLength() ),
2751                             0, true );
2752         }
2753         pTextNd->JoinNext();
2754     }
2755 }
2756 
2757 // Overwrite only uses the point of the PaM, the mark is ignored; characters
2758 // are replaced from point until the end of the node; at the end of the node,
2759 // characters are inserted.
2760 bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr )
2761 {
2762     assert(rStr.getLength());
2763     SwPosition& rPt = *const_cast<SwPosition*>(rRg.GetPoint());
2764     if( m_rDoc.GetAutoCorrExceptWord() )                  // Add to AutoCorrect
2765     {
2766         if( 1 == rStr.getLength() )
2767             m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] );
2768         m_rDoc.DeleteAutoCorrExceptWord();
2769     }
2770 
2771     SwTextNode *pNode = rPt.GetNode().GetTextNode();
2772     if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase
2773     {
2774         return false;
2775     }
2776 
2777     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2778     {
2779         m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called
2780     }
2781 
2782     const size_t nOldAttrCnt = pNode->GetpSwpHints()
2783                                 ? pNode->GetpSwpHints()->Count() : 0;
2784     SwDataChanged aTmp( rRg );
2785     sal_Int32 const nActualStart(rPt.GetContentIndex());
2786     sal_Int32 nStart = 0;
2787 
2788     bool bOldExpFlg = pNode->IsIgnoreDontExpand();
2789     pNode->SetIgnoreDontExpand( true );
2790 
2791     for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt )
2792     {
2793         // start behind the characters (to fix the attributes!)
2794         nStart = rPt.GetContentIndex();
2795         if  (nStart < pNode->GetText().getLength())
2796         {
2797             lcl_SkipAttr( pNode, rPt, nStart );
2798         }
2799         sal_Unicode c = rStr[ nCnt ];
2800         if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2801         {
2802             bool bMerged(false);
2803             if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2804             {
2805                 SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo();
2806                 SwUndoOverwrite *const pUndoOW(
2807                     dynamic_cast<SwUndoOverwrite *>(pUndo) );
2808                 if (pUndoOW)
2809                 {
2810                     // if CanGrouping() returns true it's already merged
2811                     bMerged = pUndoOW->CanGrouping(m_rDoc, rPt, c);
2812                 }
2813             }
2814             if (!bMerged)
2815             {
2816                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2817                     std::make_unique<SwUndoOverwrite>(m_rDoc, rPt, c) );
2818             }
2819         }
2820         else
2821         {
2822             // start behind the characters (to fix the attributes!)
2823             if (nStart < pNode->GetText().getLength())
2824                 rPt.AdjustContent(+1);
2825             pNode->InsertText( OUString(c), rPt, SwInsertFlags::EMPTYEXPAND );
2826             if( nStart+1 < rPt.GetContentIndex() )
2827             {
2828                 rPt.SetContent(nStart);
2829                 pNode->EraseText( rPt, 1 );
2830                 rPt.AdjustContent(+1);
2831             }
2832         }
2833     }
2834     pNode->SetIgnoreDontExpand( bOldExpFlg );
2835 
2836     const size_t nNewAttrCnt = pNode->GetpSwpHints()
2837                                 ? pNode->GetpSwpHints()->Count() : 0;
2838     if( nOldAttrCnt != nNewAttrCnt )
2839     {
2840         const SwUpdateAttr aHint(0,0,0);
2841         pNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
2842     }
2843 
2844     if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() &&
2845         !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
2846     {
2847         SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2848         m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any );
2849     }
2850     else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2851     {
2852         // FIXME: this redline is WRONG: there is no DELETE, and the skipped
2853         // characters are also included in aPam
2854         SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex());
2855         m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
2856     }
2857 
2858     m_rDoc.getIDocumentState().SetModified();
2859     return true;
2860 }
2861 
2862 bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr,
2863         const SwInsertFlags nInsertMode )
2864 {
2865     // tdf#119019 accept tracked paragraph formatting to do not hide new insertions
2866     if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2867     {
2868         RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
2869         m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg );
2870         if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags())
2871             m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
2872     }
2873 
2874     // fetching DoesUndo is surprisingly expensive
2875     bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
2876     if (bDoesUndo)
2877         m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called!
2878 
2879     const SwPosition& rPos = *rRg.GetPoint();
2880 
2881     if( m_rDoc.GetAutoCorrExceptWord() )                  // add to auto correction
2882     {
2883         if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() )
2884         {
2885             m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] );
2886         }
2887         m_rDoc.DeleteAutoCorrExceptWord();
2888     }
2889 
2890     SwTextNode *const pNode = rPos.GetNode().GetTextNode();
2891     if(!pNode)
2892         return false;
2893 
2894     SwDataChanged aTmp( rRg );
2895 
2896     if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
2897     {
2898         OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2899         if (bDoesUndo)
2900         {
2901             m_rDoc.GetIDocumentUndoRedo().AppendUndo(
2902                 std::make_unique<SwUndoInsert>(rPos.GetNode(),
2903                         rPos.GetContentIndex(), ins.getLength(), nInsertMode));
2904         }
2905     }
2906     else
2907     {   // if Undo and grouping is enabled, everything changes!
2908         SwUndoInsert * pUndo = nullptr;
2909 
2910         // don't group the start if hints at the start should be expanded
2911         if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND))
2912         {
2913             SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo();
2914             SwUndoInsert *const pUndoInsert(
2915                 dynamic_cast<SwUndoInsert *>(pLastUndo) );
2916             if (pUndoInsert && pUndoInsert->CanGrouping(rPos))
2917             {
2918                 pUndo = pUndoInsert;
2919             }
2920         }
2921 
2922         CharClass const& rCC = GetAppCharClass();
2923         sal_Int32 nInsPos = rPos.GetContentIndex();
2924 
2925         if (!pUndo)
2926         {
2927             pUndo = new SwUndoInsert( rPos.GetNode(), nInsPos, 0, nInsertMode,
2928                             !rCC.isLetterNumeric( rStr, 0 ) );
2929             m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2930         }
2931 
2932         OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode));
2933 
2934         for (sal_Int32 i = 0; i < ins.getLength(); ++i)
2935         {
2936             nInsPos++;
2937             // if CanGrouping() returns true, everything has already been done
2938             if (!pUndo->CanGrouping(ins[i]))
2939             {
2940                 pUndo = new SwUndoInsert(rPos.GetNode(), nInsPos, 1, nInsertMode,
2941                             !rCC.isLetterNumeric(ins, i));
2942                 m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
2943             }
2944         }
2945     }
2946 
2947     // To-Do - add 'SwExtraRedlineTable' also ?
2948     if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
2949     {
2950         SwPaM aPam( rPos.GetNode(), aTmp.GetContent(),
2951                     rPos.GetNode(), rPos.GetContentIndex());
2952         if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
2953         {
2954             m_rDoc.getIDocumentRedlineAccess().AppendRedline(
2955                 new SwRangeRedline( RedlineType::Insert, aPam ), true);
2956         }
2957         else
2958         {
2959             m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
2960         }
2961     }
2962 
2963     m_rDoc.getIDocumentState().SetModified();
2964     return true;
2965 }
2966 
2967 void DocumentContentOperationsManager::SetIME(bool bIME)
2968 {
2969     m_bIME = bIME;
2970 }
2971 
2972 bool DocumentContentOperationsManager::GetIME() const
2973 {
2974     return m_bIME;
2975 }
2976 
2977 void DocumentContentOperationsManager::TransliterateText(
2978     const SwPaM& rPaM,
2979     utl::TransliterationWrapper& rTrans )
2980 {
2981     std::unique_ptr<SwUndoTransliterate> pUndo;
2982     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
2983         pUndo.reset(new SwUndoTransliterate( rPaM, rTrans ));
2984 
2985     auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
2986     SwNodeOffset nSttNd = pStt->GetNodeIndex(),
2987           nEndNd = pEnd->GetNodeIndex();
2988     sal_Int32 nSttCnt = pStt->GetContentIndex();
2989     sal_Int32 nEndCnt = pEnd->GetContentIndex();
2990 
2991     SwTextNode* pTNd = pStt->GetNode().GetTextNode();
2992     bool bNoSelection = (pStt == pEnd) && pTNd;  // no selection?
2993     if ( bNoSelection )
2994     {
2995         /* Check if cursor is inside of a word */
2996         assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
2997         Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
2998                             pTNd->GetText(), nSttCnt,
2999                             g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ),
3000                             WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/,
3001                             true);
3002 
3003         if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos )
3004         {
3005             /* Cursor is inside of a word */
3006             if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) {
3007                 /* set current sentence as 'area of effect' */
3008                 nSttCnt = g_pBreakIt->GetBreakIter()->beginOfSentence(
3009                             pTNd->GetText(), nSttCnt,
3010                             g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ) );
3011                 nEndCnt = g_pBreakIt->GetBreakIter()->endOfSentence(
3012                             pTNd->GetText(), nEndCnt,
3013                             g_pBreakIt->GetLocale( pTNd->GetLang( nEndCnt ) ) );
3014             } else {
3015                 /* Set current word as 'area of effect' */
3016                 nSttCnt = aBndry.startPos;
3017                 nEndCnt = aBndry.endPos;
3018             }
3019         } else {
3020             /* Cursor is not inside of a word. Nothing should happen. */
3021             /* Except in the case of change tracking, when the cursor is at the end of the change */
3022             /* Recognize and reject the previous deleted and inserted words to allow to cycle */
3023             IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3024             if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3025                             pStt->GetContentIndex() > 0 )
3026             {
3027                 SwPosition aPos(*pStt->GetContentNode(), pStt->GetContentIndex() - 1);
3028                 SwRedlineTable::size_type n = 0;
3029 
3030                 const SwRangeRedline* pFnd =
3031                                     rIDRA.GetRedlineTable().FindAtPosition( aPos, n );
3032                 if ( pFnd && RedlineType::Insert == pFnd->GetType() && n > 0 )
3033                 {
3034                     const SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[n-1];
3035                     if ( RedlineType::Delete == pFnd2->GetType() &&
3036                           m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() &&
3037                           *pFnd2->End() == *pFnd->Start() &&
3038                           pFnd->GetAuthor() == pFnd2->GetAuthor() )
3039                     {
3040                         SwPosition aPos2(*pFnd2->End());
3041                         rIDRA.RejectRedline(*pFnd, true);
3042                         rIDRA.RejectRedline(*pFnd2, true);
3043                         // positionate the text cursor inside the changed word to allow to cycle
3044                         if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3045                                 m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3046                         {
3047                             pWrtShell->GetCursor()->GetPoint()->
3048                                     Assign(*aPos2.GetContentNode(), aPos2.GetContentIndex() - 1);
3049                         }
3050                     }
3051                 }
3052             }
3053             return;
3054         }
3055     }
3056     else
3057     {
3058         bool bHasTrackedChange = false;
3059         IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess();
3060         if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) &&
3061                         pEnd->GetContentIndex() > 0 )
3062         {
3063             // search all own redlines within the selected area
3064             SwRedlineTable::size_type n = SwRedlineTable::npos;
3065             const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable();
3066             for( SwRedlineTable::size_type m = 0; m < aRedlineTable.size(); ++m )
3067             {
3068                 const SwRangeRedline* pRedline = aRedlineTable[ m ];
3069 
3070                 if ( *pRedline->Start() > *pEnd )
3071                     break;
3072 
3073                 if ( *pRedline->Start() >= *pStt )
3074                     n = m;
3075             }
3076 
3077             if ( n != SwRedlineTable::npos && n > 0 )
3078             {
3079                 SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3080                             m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell());
3081 
3082                 sal_Int32 nRejectedCharacters = 0;
3083                 SwRangeRedline* pFnd = rIDRA.GetRedlineTable()[n];
3084                 SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[--n];
3085                 // loop on all redlines of a case changing, and reject them
3086                 while ( ( ( RedlineType::Insert == pFnd->GetType() &&
3087                             RedlineType::Delete == pFnd2->GetType() ) ||
3088                           ( RedlineType::Delete == pFnd->GetType() &&
3089                             RedlineType::Insert == pFnd2->GetType() ) ) &&
3090                             pWrtShell &&
3091                       // use time stamp to recognize the multiple selections in the text,
3092                       // not only the changes from the same author within the (sometimes
3093                       // incomplete) selection
3094                       ( pFnd2->GetTimeStamp() == pFnd->GetTimeStamp() ||
3095                         ( pStt->GetContentNode() < pFnd2->Start()->GetContentNode() ||
3096                             ( pStt->GetContentNode() == pFnd2->Start()->GetContentNode() &&
3097                               nSttCnt <= pFnd2->Start()->GetContentIndex() ) ) ) &&
3098                         pFnd->GetAuthor() == pFnd2->GetAuthor() )
3099                 {
3100                     bHasTrackedChange = true;
3101 
3102                     if ( RedlineType::Insert == pFnd->GetType() )
3103                         nRejectedCharacters += pFnd->GetText().getLength();
3104 
3105                     rIDRA.RejectRedline(*pFnd, true);
3106 
3107                     pFnd = pFnd2;
3108                     if ( n == 0 )
3109                         break;
3110                     pFnd2 = rIDRA.GetRedlineTable()[--n];
3111                 }
3112 
3113                 // remove the last item and restore the original selection within the node
3114                 if ( bHasTrackedChange )
3115                 {
3116                     if ( nSttNd == nEndNd )
3117                     {
3118                         pWrtShell->GetCursor()->GetPoint()->
3119                             Assign(*rPaM.Start()->GetContentNode(), nSttCnt);
3120                         if ( nEndCnt >= nRejectedCharacters )
3121                             pWrtShell->GetCursor()->GetMark()->
3122                                 Assign(*rPaM.End()->GetContentNode(), nEndCnt - nRejectedCharacters);
3123                     }
3124                     rIDRA.RejectRedline(*pFnd, true);
3125                 }
3126             }
3127         }
3128 
3129         // TODO handle title case to lowercase
3130         if ( bHasTrackedChange )
3131             return;
3132     }
3133 
3134     bool bUseRedlining = m_rDoc.getIDocumentRedlineAccess().IsRedlineOn();
3135     // as a workaround for a known performance problem, switch off redlining
3136     // to avoid freezing, if transliteration could result too many redlines
3137     if ( bUseRedlining )
3138     {
3139         const sal_uLong nMaxRedlines = 500;
3140         const bool bIsTitleCase = rTrans.getType() == TransliterationFlags::TITLE_CASE;
3141         sal_uLong nAffectedNodes = 0;
3142         sal_uLong nAffectedChars = nEndCnt;
3143         SwNodeIndex aIdx( pStt->GetNode() );
3144         for( ; aIdx.GetIndex() <= nEndNd; ++aIdx )
3145         {
3146             SwTextNode* pAffectedNode = aIdx.GetNode().GetTextNode();
3147 
3148             // don't count not text nodes or empty text nodes
3149             if( !pAffectedNode || pAffectedNode->GetText().isEmpty() )
3150                 continue;
3151 
3152             nAffectedNodes++;
3153 
3154             // count characters of the node (the last - maybe partially
3155             // selected - node was counted at initialization of nAffectedChars)
3156             if( aIdx.GetIndex() < nEndNd )
3157                 nAffectedChars += pAffectedNode->GetText().getLength();
3158 
3159             // transliteration creates n redlines for n nodes, except in the
3160             // case of title case, where it creates n redlines for n words
3161             if( nAffectedNodes > nMaxRedlines ||
3162                     // estimate word count based on the character count, where
3163                     // 6 = average English word length is ~5 letters + space
3164                     ( bIsTitleCase && (nAffectedChars - nSttCnt)/6 > nMaxRedlines ) )
3165             {
3166                 bUseRedlining = false;
3167                 break;
3168             }
3169         }
3170     }
3171 
3172     if( nSttNd != nEndNd )  // is more than one text node involved?
3173     {
3174         // iterate over all affected text nodes, the first and the last one
3175         // may be incomplete because the selection starts and/or ends there
3176 
3177         SwNodeIndex aIdx( pStt->GetNode() );
3178         if( nSttCnt )
3179         {
3180             ++aIdx;
3181             if( pTNd )
3182             {
3183                 pTNd->TransliterateText(
3184                         rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3185             }
3186         }
3187 
3188         for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
3189         {
3190             pTNd = aIdx.GetNode().GetTextNode();
3191             if (pTNd)
3192             {
3193                 pTNd->TransliterateText(
3194                         rTrans, 0, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining);
3195             }
3196         }
3197 
3198         if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() ))
3199         {
3200             pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get(), bUseRedlining );
3201         }
3202     }
3203     else if( pTNd && nSttCnt < nEndCnt )
3204     {
3205         pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get(), bUseRedlining );
3206     }
3207     if( pUndo && pUndo->HasData() )
3208     {
3209         m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
3210     }
3211 
3212     // restore selection after tracked changes
3213     if ( !bNoSelection && bUseRedlining && nSttNd == nEndNd )
3214     {
3215         if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>(
3216                         m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) )
3217         {
3218             *pWrtShell->GetCursor()->GetMark() = *pWrtShell->GetCursor()->End();
3219             pWrtShell->GetCursor()->GetPoint()->Assign(*pStt->GetContentNode(), nSttCnt);
3220         }
3221     }
3222 
3223     m_rDoc.getIDocumentState().SetModified();
3224 }
3225 
3226 SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic(
3227         const SwPaM &rRg,
3228                             const OUString& rGrfName,
3229                             const OUString& rFltName,
3230                             const Graphic* pGraphic,
3231                             const SfxItemSet* pFlyAttrSet,
3232                             const SfxItemSet* pGrfAttrSet,
3233                             SwFrameFormat* pFrameFormat )
3234 {
3235     if( !pFrameFormat )
3236         pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC );
3237     SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode(
3238                             m_rDoc.GetNodes().GetEndOfAutotext(),
3239                             rGrfName, rFltName, pGraphic,
3240                             m_rDoc.GetDfltGrfFormatColl() );
3241     SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode,
3242                             pFlyAttrSet, pGrfAttrSet, pFrameFormat );
3243     return pSwFlyFrameFormat;
3244 }
3245 
3246 SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject(
3247         const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj,
3248                         SfxItemSet* pFlyAttrSet)
3249 {
3250     sal_uInt16 nId = RES_POOLFRM_OLE;
3251     if (xObj.is())
3252     {
3253         SvGlobalName aClassName( xObj->getClassID() );
3254         if (SotExchange::IsMath(aClassName))
3255         {
3256             nId = RES_POOLFRM_FORMEL;
3257         }
3258     }
3259 
3260     SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId );
3261 
3262     return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode(
3263                             m_rDoc.GetNodes().GetEndOfAutotext(),
3264                             xObj,
3265                             m_rDoc.GetDfltGrfFormatColl() ),
3266                             pFlyAttrSet, nullptr,
3267                             pFrameFormat );
3268 }
3269 
3270 SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName,
3271                         sal_Int64 nAspect,
3272                         const SfxItemSet* pFlyAttrSet,
3273                         const SfxItemSet* pGrfAttrSet)
3274 {
3275     SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE );
3276 
3277     return InsNoTextNode( *rRg.GetPoint(),
3278                             m_rDoc.GetNodes().MakeOLENode(
3279                                 m_rDoc.GetNodes().GetEndOfAutotext(),
3280                                 rObjName,
3281                                 nAspect,
3282                                 m_rDoc.GetDfltGrfFormatColl(),
3283                                 nullptr ),
3284                             pFlyAttrSet, pGrfAttrSet,
3285                             pFrameFormat );
3286 }
3287 
3288 void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName,
3289                     const OUString& rFltName, const Graphic* pGraphic )
3290 {
3291     SwGrfNode *pGrfNd;
3292     if( !(( !rPam.HasMark()
3293          || rPam.GetPoint()->GetNodeIndex() == rPam.GetMark()->GetNodeIndex() )
3294          && nullptr != ( pGrfNd = rPam.GetPoint()->GetNode().GetGrfNode() )) )
3295         return;
3296 
3297     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3298     {
3299         m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoReRead>(rPam, *pGrfNd));
3300     }
3301 
3302     // Because we don't know if we can mirror the graphic, the mirror attribute is always reset
3303     if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet().
3304                                             GetMirrorGrf().GetValue() )
3305         pGrfNd->SetAttr( SwMirrorGrf() );
3306 
3307     pGrfNd->ReRead( rGrfName, rFltName, pGraphic );
3308     m_rDoc.getIDocumentState().SetModified();
3309 }
3310 
3311 // Insert drawing object, which has to be already inserted in the DrawModel
3312 SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj(
3313     const SwPaM &rRg,
3314     SdrObject& rDrawObj,
3315     const SfxItemSet& rFlyAttrSet )
3316 {
3317     SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() );
3318 
3319     const SwFormatAnchor* pAnchor = rFlyAttrSet.GetItemIfSet( RES_ANCHOR, false );
3320     pFormat->SetFormatAttr( rFlyAttrSet );
3321 
3322     // Didn't set the Anchor yet?
3323     // DrawObjecte must never end up in the Header/Footer!
3324     RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId();
3325     const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId);
3326 
3327     const SwPosition* pChkPos = nullptr;
3328     if ( pAnchor == nullptr )
3329     {
3330         pChkPos = rRg.GetPoint();
3331     }
3332     else if ( bIsAtContent )
3333     {
3334         pChkPos =
3335             pAnchor->GetContentAnchor() ? pAnchor->GetContentAnchor() : rRg.GetPoint();
3336     }
3337 
3338     // allow drawing objects in header/footer, but control objects aren't allowed in header/footer.
3339     if( pChkPos != nullptr
3340         && ::CheckControlLayer( &rDrawObj )
3341         && m_rDoc.IsInHeaderFooter( pChkPos->GetNode() ) )
3342     {
3343         // apply at-page anchor format
3344         eAnchorId = RndStdIds::FLY_AT_PAGE;
3345         pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) );
3346     }
3347     else if( pAnchor == nullptr
3348              || ( bIsAtContent
3349                   && pAnchor->GetAnchorNode() == nullptr ) )
3350     {
3351         // apply anchor format
3352         SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() );
3353         eAnchorId = aAnch.GetAnchorId();
3354         if ( eAnchorId == RndStdIds::FLY_AT_FLY )
3355         {
3356             const SwStartNode* pStartNode = rRg.GetPointNode().FindFlyStartNode();
3357             assert(pStartNode);
3358             SwPosition aPos(*pStartNode);
3359             aAnch.SetAnchor( &aPos );
3360         }
3361         else
3362         {
3363             aAnch.SetAnchor( rRg.GetPoint() );
3364             if ( eAnchorId == RndStdIds::FLY_AT_PAGE )
3365             {
3366                 eAnchorId = dynamic_cast<const SdrUnoObj*>( &rDrawObj) !=  nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA;
3367                 aAnch.SetType( eAnchorId );
3368             }
3369         }
3370         pFormat->SetFormatAttr( aAnch );
3371     }
3372 
3373     // insert text attribute for as-character anchored drawing object
3374     if ( eAnchorId == RndStdIds::FLY_AS_CHAR )
3375     {
3376         bool bAnchorAtPageAsFallback = true;
3377         const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor();
3378         if ( rDrawObjAnchorFormat.GetAnchorNode() != nullptr )
3379         {
3380             SwTextNode* pAnchorTextNode =
3381                     rDrawObjAnchorFormat.GetAnchorNode()->GetTextNode();
3382             if ( pAnchorTextNode != nullptr )
3383             {
3384                 const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->GetContentIndex();
3385                 SwFormatFlyCnt aFormat( pFormat );
3386                 pAnchorTextNode->InsertItem( aFormat, nStt, nStt );
3387                 bAnchorAtPageAsFallback = false;
3388             }
3389         }
3390 
3391         if ( bAnchorAtPageAsFallback )
3392         {
3393             OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" );
3394             pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) );
3395         }
3396     }
3397 
3398     SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj );
3399 
3400     // Create Frames if necessary
3401     if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() )
3402     {
3403         // create layout representation
3404         pFormat->MakeFrames();
3405         // #i42319# - follow-up of #i35635#
3406         // move object to visible layer
3407         // #i79391#
3408         if ( pContact->GetAnchorFrame() )
3409         {
3410             pContact->MoveObjToVisibleLayer( &rDrawObj );
3411         }
3412     }
3413 
3414     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3415     {
3416         m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsLayFormat>(pFormat, SwNodeOffset(0), 0) );
3417     }
3418 
3419     m_rDoc.getIDocumentState().SetModified();
3420     return pFormat;
3421 }
3422 
3423 bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart )
3424 {
3425     SwContentNode *pNode = rPos.GetNode().GetContentNode();
3426     if(nullptr == pNode)
3427         return false;
3428 
3429     {
3430         // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope.
3431         //            After that they can be before/after the position.
3432         SwDataChanged aTmp( m_rDoc, rPos );
3433     }
3434 
3435     SwUndoSplitNode* pUndo = nullptr;
3436     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3437     {
3438         m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3439         // insert the Undo object (currently only for TextNode)
3440         if( pNode->IsTextNode() )
3441         {
3442             pUndo = new SwUndoSplitNode( m_rDoc, rPos, bChkTableStart );
3443             m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
3444         }
3445     }
3446 
3447     // Update the rsid of the old and the new node unless
3448     // the old node is split at the beginning or at the end
3449     SwTextNode *pTextNode =  rPos.GetNode().GetTextNode();
3450     const sal_Int32 nPos = rPos.GetContentIndex();
3451     if( pTextNode && nPos && nPos != pTextNode->Len() )
3452     {
3453         m_rDoc.UpdateParRsid( pTextNode );
3454     }
3455 
3456     //JP 28.01.97: Special case for SplitNode at table start:
3457     //             If it is at the beginning of a Doc/Fly/Footer/... or right at after a table
3458     //             then insert a paragraph before it.
3459     if( bChkTableStart && !rPos.GetContentIndex() && pNode->IsTextNode() )
3460     {
3461         SwNodeOffset nPrevPos = rPos.GetNodeIndex() - 1;
3462         SwTableNode* pTableNd;
3463         const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ];
3464         if( pNd->IsStartNode() &&
3465             SwTableBoxStartNode == static_cast<const SwStartNode*>(pNd)->GetStartNodeType() &&
3466             nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) &&
3467             ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() &&
3468                SwTableBoxStartNode != static_cast<const SwStartNode*>(pNd)->GetStartNodeType() )
3469                || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() )
3470                || pNd->IsContentNode() ))
3471         {
3472             if( pNd->IsContentNode() )
3473             {
3474                 //JP 30.04.99 Bug 65660:
3475                 // There are no page breaks outside of the normal body area,
3476                 // so this is not a valid condition to insert a paragraph.
3477                 if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3478                     pNd = nullptr;
3479                 else
3480                 {
3481                     // Only if the table has page breaks!
3482                     const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3483                     if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) &&
3484                         SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) )
3485                         pNd = nullptr;
3486                 }
3487             }
3488 
3489             if( pNd )
3490             {
3491                 SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode(
3492                                         *pTableNd,
3493                                         m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ));
3494                 if( pTextNd )
3495                 {
3496                     const_cast<SwPosition&>(rPos).Assign( pTableNd->GetIndex() - SwNodeOffset(1) );
3497 
3498                     // only add page breaks/styles to the body area
3499                     if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() )
3500                     {
3501                         SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat();
3502                         const SfxPoolItem *pItem;
3503                         if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC,
3504                             false, &pItem ) )
3505                         {
3506                             pTextNd->SetAttr( *pItem );
3507                             pFrameFormat->ResetFormatAttr( RES_PAGEDESC );
3508                         }
3509                         if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK,
3510                             false, &pItem ) )
3511                         {
3512                             pTextNd->SetAttr( *pItem );
3513                             pFrameFormat->ResetFormatAttr( RES_BREAK );
3514                         }
3515                     }
3516 
3517                     if( pUndo )
3518                         pUndo->SetTableFlag();
3519                     m_rDoc.getIDocumentState().SetModified();
3520                     return true;
3521                 }
3522             }
3523         }
3524     }
3525 
3526     const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
3527     pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true );
3528     assert(pNode->IsTextNode());
3529     std::function<void (SwTextNode *, sw::mark::RestoreMode, bool bAtStart)> restoreFunc(
3530         [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool const bAtStart)
3531         {
3532             if (!pContentStore->Empty())
3533             {   // move all bookmarks, TOXMarks, FlyAtCnt
3534                 pContentStore->Restore(m_rDoc, rPos.GetNodeIndex()-SwNodeOffset(1), 0, true, bAtStart && (eMode & sw::mark::RestoreMode::Flys), eMode);
3535             }
3536             if (eMode & sw::mark::RestoreMode::NonFlys)
3537             {
3538                 // To-Do - add 'SwExtraRedlineTable' also ?
3539                 if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ||
3540                     (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() &&
3541                      !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()))
3542                 {
3543                     SwPaM aPam( rPos );
3544                     aPam.SetMark();
3545                     aPam.Move( fnMoveBackward );
3546                     if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3547                     {
3548                         m_rDoc.getIDocumentRedlineAccess().AppendRedline(
3549                             new SwRangeRedline(RedlineType::Insert, aPam), true);
3550                     }
3551                     else
3552                     {
3553                         m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam);
3554                     }
3555                 }
3556             }
3557         });
3558     pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc);
3559 
3560     m_rDoc.getIDocumentState().SetModified();
3561     return true;
3562 }
3563 
3564 bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos )
3565 {
3566     // create new node before EndOfContent
3567     SwTextNode * pCurNode = rPos.GetNode().GetTextNode();
3568     if( !pCurNode )
3569     {
3570         // so then one can be created!
3571         SwNodeIndex aIdx( rPos.GetNode(), 1 );
3572         pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx.GetNode(),
3573                         m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ));
3574     }
3575     else
3576         pCurNode = pCurNode->AppendNode( rPos )->GetTextNode();
3577 
3578     rPos.Adjust(SwNodeOffset(1));
3579 
3580     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3581     {
3582         m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.GetNode() ) );
3583     }
3584 
3585     // To-Do - add 'SwExtraRedlineTable' also ?
3586     if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))
3587     {
3588         SwPaM aPam( rPos );
3589         aPam.SetMark();
3590         aPam.Move( fnMoveBackward );
3591         if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
3592             m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true);
3593         else
3594             m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam );
3595     }
3596 
3597     m_rDoc.getIDocumentState().SetModified();
3598     return true;
3599 }
3600 
3601 bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr,
3602         const bool bRegExReplace )
3603 {
3604     // unfortunately replace works slightly differently from delete,
3605     // so we cannot use lcl_DoWithBreaks here...
3606 
3607     std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
3608 
3609     SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() );
3610     aPam.Normalize(false);
3611     if (aPam.GetPoint()->GetNode() != aPam.GetMark()->GetNode())
3612     {
3613         aPam.Move(fnMoveBackward);
3614     }
3615     OSL_ENSURE((aPam.GetPoint()->GetNode() == aPam.GetMark()->GetNode()), "invalid pam?");
3616 
3617     sw::CalcBreaks(Breaks, aPam);
3618 
3619     while (!Breaks.empty() // skip over prefix of dummy chars
3620             && (aPam.GetMark()->GetNodeIndex() == Breaks.begin()->first)
3621             && (aPam.GetMark()->GetContentIndex() == Breaks.begin()->second))
3622     {
3623         // skip!
3624         aPam.GetMark()->AdjustContent(+1); // always in bounds if Breaks valid
3625         Breaks.erase(Breaks.begin());
3626     }
3627     *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix
3628 
3629     if (Breaks.empty())
3630     {
3631         // park aPam somewhere so it does not point to node that is deleted
3632         aPam.DeleteMark();
3633         aPam.GetPoint()->Assign(m_rDoc.GetNodes().GetEndOfContent());
3634         return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam!
3635     }
3636 
3637     // Deletion must be split into several parts if the text node
3638     // contains a text attribute with end and with dummy character
3639     // and the selection does not contain the text attribute completely,
3640     // but overlaps its start (left), where the dummy character is.
3641 
3642     bool bRet( true );
3643     // iterate from end to start, to avoid invalidating the offsets!
3644     auto iter( Breaks.rbegin() );
3645     SwNodeOffset nOffset(0);
3646     SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
3647     OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!");
3648     SwPosition & rEnd( *aPam.End() );
3649     SwPosition & rStart( *aPam.Start() );
3650 
3651     // set end of temp pam to original end (undo Move backward above)
3652     rEnd = *rPam.End();
3653     // after first deletion, rEnd will point into the original text node again!
3654 
3655     while (iter != Breaks.rend())
3656     {
3657         rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
3658         if (rStart < rEnd) // check if part is empty
3659         {
3660             bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn())
3661                 ? DeleteAndJoinWithRedlineImpl(aPam, SwDeleteFlags::Default)
3662                 : DeleteAndJoinImpl(aPam, SwDeleteFlags::Default);
3663             nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes...
3664         }
3665         rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
3666         ++iter;
3667     }
3668 
3669     rStart = *rPam.Start(); // set to original start
3670     assert(rStart < rEnd && "replace part empty!");
3671     if (rStart < rEnd) // check if part is empty
3672     {
3673         bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace);
3674     }
3675 
3676     rPam = aPam; // update original pam (is this required?)
3677 
3678     return bRet;
3679 }
3680 
3681 bool DocumentContentOperationsManager::InsertPoolItem(
3682     const SwPaM &rRg,
3683     const SfxPoolItem &rHt,
3684     const SetAttrMode nFlags,
3685     SwRootFrame const*const pLayout,
3686     SwTextAttr **ppNewTextAttr)
3687 {
3688     SwDataChanged aTmp( rRg );
3689     std::unique_ptr<SwUndoAttr> pUndoAttr;
3690     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3691     {
3692         m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3693         pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags ));
3694     }
3695 
3696     SfxItemSet aSet( m_rDoc.GetAttrPool(), rHt.Which(), rHt.Which() );
3697     aSet.Put( rHt );
3698     const bool bRet = lcl_InsAttr(m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, ppNewTextAttr);
3699 
3700     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3701     {
3702         m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3703     }
3704 
3705     if( bRet )
3706     {
3707         m_rDoc.getIDocumentState().SetModified();
3708     }
3709     return bRet;
3710 }
3711 
3712 void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet,
3713         const SetAttrMode nFlags, SwRootFrame const*const pLayout)
3714 {
3715     SwDataChanged aTmp( rRg );
3716     std::unique_ptr<SwUndoAttr> pUndoAttr;
3717     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3718     {
3719         m_rDoc.GetIDocumentUndoRedo().ClearRedo();
3720         pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags ));
3721     }
3722 
3723     bool bRet = lcl_InsAttr(m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*ppNewTextAttr*/nullptr );
3724 
3725     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
3726     {
3727         m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) );
3728     }
3729 
3730     if( bRet )
3731         m_rDoc.getIDocumentState().SetModified();
3732 }
3733 
3734 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos )
3735 {
3736     const SwTextNode* pTNd = rPos.GetNode().GetTextNode();
3737     if ( !pTNd )
3738         return;
3739 
3740     const OUString& rText = pTNd->GetText();
3741     sal_Int32 nIdx = 0;
3742     while (nIdx < rText.getLength())
3743     {
3744         sal_Unicode const cCh = rText[nIdx];
3745         if (('\t' != cCh) && (' ' != cCh))
3746         {
3747             break;
3748         }
3749         ++nIdx;
3750     }
3751 
3752     if ( nIdx > 0 )
3753     {
3754         SwPaM aPam(rPos);
3755         aPam.GetPoint()->SetContent(0);
3756         aPam.SetMark();
3757         aPam.GetMark()->SetContent(nIdx);
3758         DeleteRange( aPam );
3759     }
3760 }
3761 
3762 void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(SwPaM& rPaM )
3763 {
3764     for (SwPaM& rSel :rPaM.GetRingContainer())
3765     {
3766         SwNodeOffset nStt = rSel.Start()->GetNodeIndex();
3767         SwNodeOffset nEnd = rSel.End()->GetNodeIndex();
3768         for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++)
3769             RemoveLeadingWhiteSpace(SwPosition(rSel.GetBound().GetNodes(), nPos));
3770     }
3771 }
3772 
3773 // Copy method from SwDoc - "copy Flys in Flys"
3774 /// note: rRg/rInsPos *exclude* a partially selected start text node;
3775 ///       pCopiedPaM *includes* a partially selected start text node
3776 void DocumentContentOperationsManager::CopyWithFlyInFly(
3777     const SwNodeRange& rRg,
3778     SwNode& rInsPos,
3779     const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/,
3780     const bool bMakeNewFrames,
3781     const bool bDelRedlines,
3782     const bool bCopyFlyAtFly,
3783     SwCopyFlags const flags) const
3784 {
3785     assert(!pCopiedPaM || pCopiedPaM->first.End()->GetNode() == rRg.aEnd.GetNode());
3786     assert(!pCopiedPaM || pCopiedPaM->second.GetNode() <= rInsPos);
3787 
3788     SwDoc& rDest = rInsPos.GetDoc();
3789     SwNodeIndex aSavePos( rInsPos );
3790 
3791     if (rRg.aStart != rRg.aEnd)
3792     {
3793         bool bEndIsEqualEndPos = rInsPos == rRg.aEnd.GetNode();
3794         --aSavePos;
3795         SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 );
3796 
3797         // insert behind the already copied start node
3798         m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true );
3799         aRedlRest.Restore();
3800 
3801         if (bEndIsEqualEndPos)
3802         {
3803             const_cast<SwNodeIndex&>(rRg.aEnd).Assign(aSavePos.GetNode(), +1);
3804         }
3805     }
3806 
3807     // Also copy all bookmarks
3808     // guess this must be done before the DelDummyNodes below as that
3809     // deletes nodes so would mess up the index arithmetic
3810     // sw_fieldmarkhide: also needs to be done before making frames
3811     if (m_rDoc.getIDocumentMarkAccess()->getAllMarksCount())
3812     {
3813         SwPaM aRgTmp( rRg.aStart, rRg.aEnd );
3814         SwPosition targetPos(aSavePos, SwNodeOffset(rRg.aStart != rRg.aEnd ? +1 : 0));
3815         if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3816         {
3817             // there is 1 (partially selected, maybe) paragraph before
3818             assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->GetNode());
3819             // only use the passed in target SwPosition if the source PaM point
3820             // is on a different node; if it was the same node then the target
3821             // position was likely moved along by the copy operation and now
3822             // points to the end of the range!
3823             targetPos = pCopiedPaM->second;
3824         }
3825 
3826         sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, targetPos, flags);
3827     }
3828 
3829     if (rRg.aStart != rRg.aEnd)
3830     {
3831         ++aSavePos;
3832     }
3833 
3834 #if OSL_DEBUG_LEVEL > 0
3835     {
3836         //JP 17.06.99: Bug 66973 - check count only if the selection is in
3837         // the same section or there's no section, because sections that are
3838         // not fully selected are not copied.
3839         const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode();
3840         SwNodeIndex aTmpI( rRg.aEnd, -1 );
3841         const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode();
3842         if( pSSectNd == pESectNd &&
3843             !rRg.aStart.GetNode().IsSectionNode() &&
3844             !aTmpI.GetNode().IsEndNode() )
3845         {
3846             // If the range starts with a SwStartNode, it isn't copied
3847             SwNodeOffset offset( (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0 );
3848             OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() ==
3849                     rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset,
3850                     "An insufficient number of nodes were copied!" );
3851         }
3852     }
3853 #endif
3854 
3855     {
3856         ::sw::UndoGuard const undoGuard(rDest.GetIDocumentUndoRedo());
3857         // this must happen before lcl_DeleteRedlines() because it counts nodes
3858         CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr,
3859             // see comment below regarding use of pCopiedPaM->second
3860             (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode())
3861                 ? pCopiedPaM->second.GetNode()
3862                 : aSavePos.GetNode(),
3863             bCopyFlyAtFly,
3864             flags,
3865             false);
3866     }
3867 
3868     SwNodeRange aCpyRange( aSavePos.GetNode(), rInsPos );
3869 
3870     if( bDelRedlines && ( RedlineFlags::DeleteRedlines & rDest.getIDocumentRedlineAccess().GetRedlineFlags() ))
3871         lcl_DeleteRedlines( rRg, aCpyRange );
3872 
3873     rDest.GetNodes().DelDummyNodes( aCpyRange );
3874 
3875     // tdf#159023 create layout frames after DelDummyNodes():
3876     // InsertCnt_() does early return on the first SwPlaceholderNode
3877     if (rRg.aStart != rRg.aEnd)
3878     {
3879         --aSavePos; // restore temporarily...
3880         bool isRecreateEndNode(false);
3881         if (bMakeNewFrames) // tdf#130685 only after aRedlRest
3882         {   // recreate from previous node (could be merged now)
3883             o3tl::sorted_vector<SwTextFrame*> frames;
3884             SwTextNode * pNode(aSavePos.GetNode().GetTextNode());
3885             SwTextNode *const pEndNode(rInsPos.GetTextNode());
3886             if (pEndNode)
3887             {
3888                 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3889                 for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3890                 {
3891                     if (pFrame->getRootFrame()->HasMergedParas())
3892                     {
3893                         frames.insert(pFrame);
3894                         // tdf#135061 check if end node is merged to a preceding node
3895                         if (pNode == nullptr && pFrame->GetMergedPara()
3896                             && pFrame->GetMergedPara()->pFirstNode->GetIndex() < aSavePos.GetIndex())
3897                         {
3898                             pNode = pFrame->GetMergedPara()->pFirstNode;
3899                         }
3900                     }
3901                 }
3902             }
3903             if (pNode != nullptr)
3904             {
3905                 sw::RecreateStartTextFrames(*pNode);
3906                 if (!frames.empty())
3907                 {   // tdf#132187 check if the end node needs new frames
3908                     SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode);
3909                     for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
3910                     {
3911                         if (pFrame->getRootFrame()->HasMergedParas())
3912                         {
3913                             auto const it(frames.find(pFrame));
3914                             if (it != frames.end())
3915                             {
3916                                 frames.erase(it);
3917                             }
3918                         }
3919                     }
3920                     if (!frames.empty()) // existing frame was deleted
3921                     {   // all layouts because MakeFrames recreates all layouts
3922                         pEndNode->DelFrames(nullptr);
3923                         isRecreateEndNode = true;
3924                     }
3925                 }
3926             }
3927         }
3928         bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode());
3929         ++aSavePos;
3930         if (bMakeNewFrames)
3931         {
3932             // it's possible that CheckParaRedlineMerge() deleted frames
3933             // on rInsPos so have to include it, but it must not be included
3934             // if it was the first node in the document so that MakeFrames()
3935             // will find the existing (wasn't deleted) frame on it
3936             SwNodeIndex const end(rInsPos,
3937                 SwNodeOffset((!isRecreateEndNode || isAtStartOfSection)
3938                     ? 0 : +1));
3939             ::MakeFrames(&rDest, aSavePos.GetNode(), end.GetNode());
3940         }
3941     }
3942 }
3943 
3944 // note: for the redline Show/Hide this must be in sync with
3945 // SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection()
3946 void DocumentContentOperationsManager::CopyFlyInFlyImpl(
3947     const SwNodeRange& rRg,
3948     SwPaM const*const pCopiedPaM,
3949     SwNode& rStartIdx,
3950     const bool bCopyFlyAtFly,
3951     SwCopyFlags const flags,
3952     bool const bMakeNewFrames) const
3953 {
3954     assert(!pCopiedPaM || pCopiedPaM->End()->GetNode() == rRg.aEnd.GetNode());
3955 
3956     // First collect all Flys, sort them according to their ordering number,
3957     // and then only copy them. This maintains the ordering numbers (which are only
3958     // managed in the DrawModel).
3959     SwDoc& rDest = rStartIdx.GetDoc();
3960     std::set< ZSortFly > aSet;
3961     const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size();
3962 
3963     SwTextBoxHelper::SavedLink aOldTextBoxes;
3964     SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes);
3965 
3966     for ( size_t n = 0; n < nArrLen; ++n )
3967     {
3968         SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n];
3969         SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
3970         SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
3971         if ( !pAnchorNode )
3972             continue;
3973         bool bAdd = false;
3974         SwNodeOffset nSkipAfter = pAnchorNode->GetIndex();
3975         SwNodeOffset nStart = rRg.aStart.GetIndex();
3976         switch ( pAnchor->GetAnchorId() )
3977         {
3978             case RndStdIds::FLY_AT_FLY:
3979                 if(bCopyFlyAtFly)
3980                     ++nSkipAfter;
3981                 else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove())
3982                     ++nStart;
3983             break;
3984             case RndStdIds::FLY_AT_PARA:
3985                 {
3986                     bAdd = IsSelectFrameAnchoredAtPara(*pAnchor->GetContentAnchor(),
3987                         pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
3988                         pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
3989                         (flags & SwCopyFlags::IsMoveToFly)
3990                             ? DelContentType::AllMask|DelContentType::WriterfilterHack
3991                             : DelContentType::AllMask);
3992                 }
3993             break;
3994             case RndStdIds::FLY_AT_CHAR:
3995                 {
3996                     bAdd = IsDestroyFrameAnchoredAtChar(*pAnchor->GetContentAnchor(),
3997                         pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart),
3998                         pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd),
3999                         (flags & SwCopyFlags::IsMoveToFly)
4000                             ? DelContentType::AllMask|DelContentType::WriterfilterHack
4001                             : DelContentType::AllMask);
4002                 }
4003             break;
4004             default:
4005                 continue;
4006         }
4007         if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())
4008         {
4009             if (nStart > nSkipAfter)
4010                 continue;
4011             if (*pAnchorNode > rRg.aEnd.GetNode())
4012                 continue;
4013             //frames at the last source node are not always copied:
4014             //- if the node is empty and is the last node of the document or a table cell
4015             //  or a text frame then they have to be copied
4016             //- if the content index in this node is > 0 then paragraph and frame bound objects are copied
4017             //- to-character bound objects are copied if their index is <= nEndContentIndex
4018             if (*pAnchorNode < rRg.aEnd.GetNode())
4019                 bAdd = true;
4020             if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move
4021             {
4022                 if (!bAdd)
4023                 {
4024                     // technically old code checked nContent of AT_FLY which is pointless
4025                     bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->GetContentIndex();
4026                 }
4027             }
4028         }
4029         if( bAdd )
4030         {
4031             aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() ));
4032         }
4033     }
4034 
4035     // Store all copied (and also the newly created) frames in another array.
4036     // They are stored as matching the originals, so that we will be later
4037     // able to build the chains accordingly.
4038     std::vector< SwFrameFormat* > aVecSwFrameFormat;
4039     std::set< ZSortFly >::const_iterator it=aSet.begin();
4040 
4041     while (it != aSet.end())
4042     {
4043         // #i59964#
4044         // correct determination of new anchor position
4045         SwFormatAnchor aAnchor( *(*it).GetAnchor() );
4046         assert( aAnchor.GetContentAnchor() != nullptr );
4047         SwPosition newPos = *aAnchor.GetContentAnchor();
4048         // for at-paragraph and at-character anchored objects the new anchor
4049         // position can *not* be determined by the difference of the current
4050         // anchor position to the start of the copied range, because not
4051         // complete selected sections in the copied range aren't copied - see
4052         // method <SwNodes::CopyNodes(..)>.
4053         // Thus, the new anchor position in the destination document is found
4054         // by counting the text nodes.
4055         if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) ||
4056             (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) )
4057         {
4058             // First, determine number of anchor text node in the copied range.
4059             // Note: The anchor text node *have* to be inside the copied range.
4060             sal_uLong nAnchorTextNdNumInRange( 0 );
4061             bool bAnchorTextNdFound( false );
4062             // start at the first node for which flys are copied
4063             SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->GetNode() : rRg.aStart.GetNode());
4064             while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd )
4065             {
4066                 if ( aIdx.GetNode().IsTextNode() )
4067                 {
4068                     ++nAnchorTextNdNumInRange;
4069                     bAnchorTextNdFound = *aAnchor.GetAnchorNode() == aIdx.GetNode();
4070                 }
4071 
4072                 ++aIdx;
4073             }
4074 
4075             if ( !bAnchorTextNdFound )
4076             {
4077                 // This case can *not* happen, but to be robust take the first
4078                 // text node in the destination document.
4079                 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - anchor text node in copied range not found" );
4080                 nAnchorTextNdNumInRange = 1;
4081             }
4082             // Second, search corresponding text node in destination document
4083             // by counting forward from start insert position <rStartIdx> the
4084             // determined number of text nodes.
4085             aIdx = rStartIdx;
4086             SwNodeIndex aAnchorNdIdx( rStartIdx );
4087             const SwNode& aEndOfContentNd =
4088                                     aIdx.GetNode().GetNodes().GetEndOfContent();
4089             while ( nAnchorTextNdNumInRange > 0 &&
4090                     aIdx.GetNode() != aEndOfContentNd )
4091             {
4092                 if ( aIdx.GetNode().IsTextNode() )
4093                 {
4094                     --nAnchorTextNdNumInRange;
4095                     aAnchorNdIdx = aIdx;
4096                 }
4097 
4098                 ++aIdx;
4099             }
4100             if ( !aAnchorNdIdx.GetNode().IsTextNode() )
4101             {
4102                 // This case can *not* happen, but to be robust take the first
4103                 // text node in the destination document.
4104                 OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - found anchor node index isn't a text node" );
4105                 aAnchorNdIdx = rStartIdx;
4106                 while ( !aAnchorNdIdx.GetNode().IsTextNode() )
4107                 {
4108                     ++aAnchorNdIdx;
4109                 }
4110             }
4111             // apply found anchor text node as new anchor position
4112             newPos.Assign( aAnchorNdIdx );
4113         }
4114         else
4115         {
4116             SwNodeOffset nOffset = newPos.GetNodeIndex() - rRg.aStart.GetIndex();
4117             newPos.Assign( rStartIdx, nOffset );
4118         }
4119         // Set the character bound Flys back at the original character
4120         if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) &&
4121              newPos.GetNode().IsTextNode() )
4122         {
4123             // only if pCopiedPaM: care about partially selected start node
4124             sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->GetNode() == *aAnchor.GetAnchorNode()
4125                 ? newPos.GetContentIndex() - pCopiedPaM->Start()->GetContentIndex()
4126                 : newPos.GetContentIndex();
4127             newPos.SetContent(nContent);
4128         }
4129         aAnchor.SetAnchor( &newPos );
4130 
4131         // Check recursion: if copying content inside the same frame, then don't copy the format.
4132         if( &rDest == &m_rDoc )
4133         {
4134             const SwFormatContent& rContent = (*it).GetFormat()->GetContent();
4135             const SwStartNode* pSNd;
4136             if( rContent.GetContentIdx() &&
4137                 nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) &&
4138                 pSNd->GetIndex() < rStartIdx.GetIndex() &&
4139                 rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() )
4140             {
4141                 it = aSet.erase(it);
4142                 continue;
4143             }
4144         }
4145 
4146         // Ignore TextBoxes, they are already handled in
4147         // sw::DocumentLayoutManager::CopyLayoutFormat().
4148         if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT))
4149         {
4150             it = aSet.erase(it);
4151             continue;
4152         }
4153 
4154         // Copy the format and set the new anchor
4155         aVecSwFrameFormat.push_back( rDest.getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(),
4156                 aAnchor, false, bMakeNewFrames) );
4157         ++it;
4158     }
4159 
4160     // Rebuild as much as possible of all chains that are available in the original,
4161     OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" );
4162     if ( aSet.size() != aVecSwFrameFormat.size() )
4163         return;
4164 
4165     size_t n = 0;
4166     for (const auto& rFlyN : aSet)
4167     {
4168         const SwFrameFormat *pFormatN = rFlyN.GetFormat();
4169         const SwFormatChain &rChain = pFormatN->GetChain();
4170         size_t k = 0;
4171         for (const auto& rFlyK : aSet)
4172         {
4173             const SwFrameFormat *pFormatK = rFlyK.GetFormat();
4174             if ( rChain.GetPrev() == pFormatK )
4175             {
4176                 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]),
4177                                  static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) );
4178             }
4179             else if ( rChain.GetNext() == pFormatK )
4180             {
4181                 ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]),
4182                                  static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) );
4183             }
4184             ++k;
4185         }
4186         ++n;
4187     }
4188 
4189     // Re-create content property of draw formats, knowing how old shapes
4190     // were paired with old fly formats (aOldTextBoxes) and that aSet is
4191     // parallel with aVecSwFrameFormat.
4192     SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes);
4193 }
4194 
4195 /*
4196  * Reset the text's hard formatting
4197  */
4198 /** @params pArgs contains the document's ChrFormatTable
4199  *                Is need for selections at the beginning/end and with no SSelection.
4200  */
4201 bool DocumentContentOperationsManager::lcl_RstTextAttr( SwNode* pNd, void* pArgs )
4202 {
4203     ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs);
4204     if (pPara->pLayout && pPara->pLayout->HasMergedParas()
4205         && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
4206     {
4207         return true; // skip hidden, since new items aren't applied
4208     }
4209     SwTextNode * pTextNode = pNd->GetTextNode();
4210     if( pTextNode && pTextNode->GetpSwpHints() )
4211     {
4212         SwContentIndex aSt( pTextNode, 0 );
4213         sal_Int32 nEnd = pTextNode->Len();
4214 
4215         if( &pPara->pSttNd->GetNode() == pTextNode &&
4216             pPara->pSttNd->GetContentIndex() )
4217             aSt = pPara->pSttNd->GetContentIndex();
4218 
4219         if( &pPara->pEndNd->GetNode() == pNd )
4220             nEnd = pPara->pEndNd->GetContentIndex();
4221 
4222         if( pPara->pHistory )
4223         {
4224             // Save all attributes for the Undo.
4225             SwRegHistory aRHst( *pTextNode, pPara->pHistory );
4226             pTextNode->GetpSwpHints()->Register( &aRHst );
4227             pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4228                                   pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4229             if( pTextNode->GetpSwpHints() )
4230                 pTextNode->GetpSwpHints()->DeRegister();
4231         }
4232         else
4233             pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich,
4234                                   pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange );
4235     }
4236     return true;
4237 }
4238 
4239 DocumentContentOperationsManager::~DocumentContentOperationsManager()
4240 {
4241 }
4242 //Private methods
4243 
4244 bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const flags)
4245 {
4246     assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn());
4247 
4248     RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4249 
4250     if (*rPam.GetPoint() == *rPam.GetMark())
4251     {
4252         return false; // do not add empty redlines
4253     }
4254 
4255     std::vector<std::unique_ptr<SwRangeRedline>> redlines;
4256     {
4257         auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam));
4258         if (pRedline->HasValidRange())
4259         {
4260             redlines.push_back(std::move(pRedline));
4261         }
4262         else // sigh ... why is such a selection even possible...
4263         {    // split it up so we get one SwUndoRedlineDelete per inserted RL
4264             redlines = GetAllValidRanges(std::move(pRedline));
4265         }
4266     }
4267 
4268     if (redlines.empty())
4269     {
4270         return false;
4271     }
4272 
4273     // tdf#54819 current redlining needs also modification of paragraph style and
4274     // attributes added to the same grouped Undo
4275     if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4276         m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4277 
4278     auto & rDMA(*m_rDoc.getIDocumentMarkAccess());
4279     std::vector<std::unique_ptr<SwUndo>> MarkUndos;
4280     for (auto iter = rDMA.getAnnotationMarksBegin();
4281               iter != rDMA.getAnnotationMarksEnd(); )
4282     {
4283         // tdf#111524 remove annotation marks that have their field
4284         // characters deleted
4285         SwPosition const& rEndPos((**iter).GetMarkEnd());
4286         if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End())
4287         {
4288             if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4289             {
4290                 MarkUndos.emplace_back(std::make_unique<SwUndoDeleteBookmark>(**iter));
4291             }
4292             // iter is into annotation mark vector so must be dereferenced!
4293             rDMA.deleteMark(&**iter);
4294             // this invalidates iter, have to start over...
4295             iter = rDMA.getAnnotationMarksBegin();
4296         }
4297         else
4298         {   // marks are sorted by start
4299             if (*rPam.End() < (**iter).GetMarkStart())
4300             {
4301                 break;
4302             }
4303             ++iter;
4304         }
4305     }
4306 
4307     // tdf#119019 accept tracked paragraph formatting to do not hide new deletions
4308     if (*rPam.GetPoint() != *rPam.GetMark())
4309         m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam);
4310 
4311     std::vector<std::unique_ptr<SwUndoRedlineDelete>> undos;
4312     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4313     {
4314         // this should no longer happen in calls from the UI but maybe via API
4315         // (randomTest and testTdf54819 triggers it)
4316         SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4317                 "sw.core", "redlines will be moved in DeleteAndJoin");
4318         m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4319             RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete);
4320 
4321         for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4322         {
4323             assert(pRedline->HasValidRange());
4324             undos.emplace_back(std::make_unique<SwUndoRedlineDelete>(
4325                         *pRedline, SwUndoId::DELETE, flags));
4326         }
4327         const SwRewriter aRewriter = undos.front()->GetRewriter();
4328         // can only group a single undo action
4329         if (MarkUndos.empty() && undos.size() == 1
4330             && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4331         {
4332             SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4333             SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast<SwUndoRedlineDelete*>(pLastUndo));
4334             bool const bMerged = pUndoRedlineDel
4335                 && pUndoRedlineDel->CanGrouping(*undos.front());
4336             if (!bMerged)
4337             {
4338                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front()));
4339             }
4340             undos.clear(); // prevent unmatched EndUndo
4341         }
4342         else
4343         {
4344             m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter);
4345             for (auto& it : MarkUndos)
4346             {
4347                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4348             }
4349             for (auto & it : undos)
4350             {
4351                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it));
4352             }
4353         }
4354     }
4355 
4356     for (std::unique_ptr<SwRangeRedline> & pRedline : redlines)
4357     {
4358         // note: 1. the pRedline can still be merged & deleted
4359         //       2. the impl. can even DeleteAndJoin the range => no plain PaM
4360         std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark()));
4361         pCursor->SetMark();
4362         *pCursor->GetPoint() = *pRedline->GetPoint();
4363         m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline.release(), true);
4364         // sw_redlinehide: 2 reasons why this is needed:
4365         // 1. it's the first redline in node => RedlineDelText was sent but ignored
4366         // 2. redline spans multiple nodes => must merge text frames
4367         sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4368     }
4369     m_rDoc.getIDocumentState().SetModified();
4370 
4371     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4372     {
4373         if (!undos.empty())
4374         {
4375             m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4376         }
4377         m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4378     }
4379 
4380     if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4381         m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4382 
4383     return true;
4384 }
4385 
4386 bool DocumentContentOperationsManager::DeleteAndJoinImpl(SwPaM & rPam, SwDeleteFlags const flags)
4387 {
4388     bool bJoinText, bJoinPrev;
4389     ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4390 
4391     bool const bSuccess( DeleteRangeImpl(rPam, flags) );
4392     if (!bSuccess)
4393         return false;
4394 
4395     if( bJoinText )
4396     {
4397         ::sw_JoinText( rPam, bJoinPrev );
4398     }
4399 
4400     if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline()
4401         && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())
4402     {
4403         m_rDoc.getIDocumentRedlineAccess().CompressRedlines();
4404     }
4405 
4406     return true;
4407 }
4408 
4409 bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, SwDeleteFlags const flags)
4410 {
4411     // Move all cursors out of the deleted range, but first copy the
4412     // passed PaM, because it could be a cursor that would be moved!
4413     SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4414     {
4415         SwPosition const pos(GetCorrPosition(aDelPam));
4416         ::PaMCorrAbs(aDelPam, pos);
4417     }
4418 
4419     bool const bSuccess( DeleteRangeImplImpl(aDelPam, flags) );
4420     if (bSuccess)
4421     {   // now copy position from temp copy to given PaM
4422         *rPam.GetPoint() = *aDelPam.GetPoint();
4423     }
4424 
4425     return bSuccess;
4426 }
4427 
4428 bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam, SwDeleteFlags const flags)
4429 {
4430     auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
4431 
4432     if (!rPam.HasMark()
4433         || (*pStt == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStt, *pEnd)))
4434     {
4435         return false;
4436     }
4437 
4438     if( m_rDoc.GetAutoCorrExceptWord() )
4439     {
4440         // if necessary the saved Word for the exception
4441         if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ||  pStt->GetNode() != pEnd->GetNode() ||
4442             pStt->GetContentIndex() + 1 != pEnd->GetContentIndex() ||
4443             !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt ))
4444                 { m_rDoc.DeleteAutoCorrExceptWord(); }
4445     }
4446 
4447     {
4448         // Delete all empty TextHints at the Mark's position
4449         SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode();
4450         SwpHints* pHts;
4451         if( pTextNd &&  nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() )
4452         {
4453             const sal_Int32 nMkCntPos = rPam.GetMark()->GetContentIndex();
4454             for( size_t n = pHts->Count(); n; )
4455             {
4456                 const SwTextAttr* pAttr = pHts->Get( --n );
4457                 if( nMkCntPos > pAttr->GetStart() )
4458                     break;
4459 
4460                 const sal_Int32 *pEndIdx;
4461                 if( nMkCntPos == pAttr->GetStart() &&
4462                     nullptr != (pEndIdx = pAttr->End()) &&
4463                     *pEndIdx == pAttr->GetStart() )
4464                     pTextNd->DestroyAttr( pHts->Cut( n ) );
4465             }
4466         }
4467     }
4468 
4469     {
4470         // Send DataChanged before deletion, so that we still know
4471         // which objects are in the range.
4472         // Afterwards they could be before/after the Position.
4473         SwDataChanged aTmp( rPam );
4474     }
4475 
4476     if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4477     {
4478         m_rDoc.GetIDocumentUndoRedo().ClearRedo();
4479         bool bMerged(false);
4480         if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo())
4481         {
4482             SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() );
4483             SwUndoDelete *const pUndoDelete(
4484                     dynamic_cast<SwUndoDelete *>(pLastUndo) );
4485             if (pUndoDelete)
4486             {
4487                 bMerged = pUndoDelete->CanGrouping(m_rDoc, rPam);
4488                 // if CanGrouping() returns true it's already merged
4489             }
4490         }
4491         if (!bMerged)
4492         {
4493             m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(rPam, flags));
4494         }
4495 
4496         m_rDoc.getIDocumentState().SetModified();
4497 
4498         return true;
4499     }
4500 
4501     if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4502         m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any );
4503 
4504     // Delete and move all "Flys at the paragraph", which are within the Selection
4505     if (!(flags & SwDeleteFlags::ArtificialSelection))
4506     {
4507         DelFlyInRange(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode(),
4508             rPam.GetMark()->GetContentIndex(), rPam.GetPoint()->GetContentIndex());
4509     }
4510     DelBookmarks(
4511         pStt->GetNode(),
4512         pEnd->GetNode(),
4513         nullptr,
4514         pStt->GetContentIndex(),
4515         pEnd->GetContentIndex(),
4516         bool(flags & SwDeleteFlags::ArtificialSelection));
4517 
4518     SwNodeIndex aSttIdx( pStt->GetNode() );
4519     SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode();
4520 
4521     do {        // middle checked loop!
4522         if( pCNd )
4523         {
4524             SwTextNode * pStartTextNode( pCNd->GetTextNode() );
4525             if ( pStartTextNode )
4526             {
4527                 // now move the Content to the new Node
4528                 bool bOneNd = pStt->GetNode() == pEnd->GetNode();
4529                 const sal_Int32 nLen = ( bOneNd ? pEnd->GetContentIndex()
4530                                            : pCNd->Len() )
4531                                         - pStt->GetContentIndex();
4532 
4533                 // Don't call again, if already empty
4534                 if( nLen )
4535                 {
4536                     pStartTextNode->EraseText( *pStt, nLen );
4537 
4538                     if( !pStartTextNode->Len() )
4539                     {
4540                 // METADATA: remove reference if empty (consider node deleted)
4541                         pStartTextNode->RemoveMetadataReference();
4542                     }
4543                 }
4544 
4545                 if( bOneNd )        // that's it
4546                     break;
4547 
4548                 ++aSttIdx;
4549             }
4550             else
4551             {
4552                 // So that there are no indices left registered when deleted,
4553                 // we remove a SwPaM from the Content here.
4554                 pStt->nContent.Assign( nullptr, 0 );
4555             }
4556         }
4557 
4558         pCNd = pEnd->GetNode().GetContentNode();
4559         if( pCNd )
4560         {
4561             SwTextNode * pEndTextNode( pCNd->GetTextNode() );
4562             if( pEndTextNode )
4563             {
4564                 // if already empty, don't call again
4565                 if( pEnd->GetContentIndex() )
4566                 {
4567                     SwContentIndex aIdx( pCNd, 0 );
4568                     pEndTextNode->EraseText( aIdx, pEnd->GetContentIndex() );
4569 
4570                     if( !pEndTextNode->Len() )
4571                     {
4572                         // METADATA: remove reference if empty (consider node deleted)
4573                         pEndTextNode->RemoveMetadataReference();
4574                     }
4575                 }
4576             }
4577             else
4578             {
4579                 // So that there are no indices left registered when deleted,
4580                 // we remove a SwPaM from the Content here.
4581                 pEnd->nContent.Assign( nullptr, 0 );
4582             }
4583         }
4584 
4585         // if the end is not a content node, delete it as well
4586         SwNodeOffset nEnd = pEnd->GetNodeIndex();
4587         if( pCNd == nullptr )
4588             nEnd++;
4589 
4590         if( aSttIdx != nEnd )
4591         {
4592             // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete
4593             SwNode *pTmpNd;
4594             while (pEnd == rPam.GetPoint()
4595                 && nEnd + SwNodeOffset(2) < m_rDoc.GetNodes().Count()
4596                 && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode()
4597                 && pTmpNd->StartOfSectionNode()->IsSectionNode()
4598                 && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex())
4599             {
4600                 SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd);
4601                 m_rDoc.GetNodes().SectionUp(&range);
4602                 --nEnd; // account for deleted start node
4603             }
4604 
4605             // delete the Nodes from the NodesArray
4606             m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() );
4607         }
4608 
4609         // If the Node that contained the Cursor has been deleted,
4610         // the Content has to be assigned to the current Content.
4611         if (pStt->GetNode().GetContentNode())
4612             pStt->SetContent( pStt->GetContentIndex() );
4613 
4614         // If we deleted across Node boundaries we have to correct the PaM,
4615         // because they are in different Nodes now.
4616         // Also, the Selection is revoked.
4617         *pEnd = *pStt;
4618         rPam.DeleteMark();
4619 
4620     } while( false );
4621 
4622     m_rDoc.getIDocumentState().SetModified();
4623 
4624     return true;
4625 }
4626 
4627 // It's possible to call Replace with a PaM that spans 2 paragraphs:
4628 // search with regex for "$", then replace _all_
4629 bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr,
4630         const bool bRegExReplace )
4631 {
4632     if (!rPam.HasMark())
4633         return false;
4634 
4635     bool bJoinText, bJoinPrev;
4636     ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev );
4637 
4638     {
4639         // Create a copy of the Cursor in order to move all Pams from
4640         // the other views out of the deletion range.
4641         // Except for itself!
4642         SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() );
4643         ::PaMCorrAbs( aDelPam, *aDelPam.End() );
4644 
4645         auto [pStt, pEnd] = aDelPam.StartEnd(); // SwPosition*
4646         bool bOneNode = pStt->GetNode() == pEnd->GetNode();
4647 
4648         // Own Undo?
4649         OUString sRepl( rStr );
4650         SwTextNode* pTextNd = pStt->GetNode().GetTextNode();
4651         sal_Int32 nStt = pStt->GetContentIndex();
4652         sal_Int32 nEnd;
4653 
4654         SwDataChanged aTmp( aDelPam );
4655 
4656         if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
4657         {
4658             RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
4659             if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4660             {
4661                 // this should no longer happen in calls from the UI but maybe via API
4662                 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
4663                         "sw.core", "redlines will be moved in ReplaceRange");
4664 
4665                 m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
4666 
4667                 // If any Redline will change (split!) the node
4668                 const ::sw::mark::IMark* pBkmk =
4669                     m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4670                         OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4671                         ::sw::mark::InsertMode::New);
4672 
4673                 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags(
4674                     RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete );
4675 
4676                 *aDelPam.GetPoint() = pBkmk->GetMarkPos();
4677                 if(pBkmk->IsExpanded())
4678                     *aDelPam.GetMark() = pBkmk->GetOtherMarkPos();
4679                 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4680                 pStt = aDelPam.Start();
4681                 pTextNd = pStt->GetNode().GetTextNode();
4682                 nStt = pStt->GetContentIndex();
4683             }
4684 
4685             if( !sRepl.isEmpty() )
4686             {
4687                 // Apply the first character's attributes to the ReplaceText
4688                 SfxItemSetFixed
4689                             <RES_CHRATR_BEGIN,     RES_TXTATR_WITHEND_END - 1,
4690                             RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>  aSet( m_rDoc.GetAttrPool() );
4691                 pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 );
4692 
4693                 aSet.ClearItem( RES_TXTATR_REFMARK );
4694                 aSet.ClearItem( RES_TXTATR_TOXMARK );
4695                 aSet.ClearItem( RES_TXTATR_CJK_RUBY );
4696                 aSet.ClearItem( RES_TXTATR_INETFMT );
4697                 aSet.ClearItem( RES_TXTATR_META );
4698                 aSet.ClearItem( RES_TXTATR_METAFIELD );
4699 
4700                 if( aDelPam.GetPoint() != aDelPam.End() )
4701                     aDelPam.Exchange();
4702 
4703                 // Remember the End
4704                 SwNodeIndex aPtNd( aDelPam.GetPoint()->GetNode(), -1 );
4705                 const sal_Int32 nPtCnt = aDelPam.GetPoint()->GetContentIndex();
4706 
4707                 bool bFirst = true;
4708                 OUString sIns;
4709                 while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4710                 {
4711                     InsertString( aDelPam, sIns );
4712                     if( bFirst )
4713                     {
4714                         SwNodeIndex aMkNd( aDelPam.GetMark()->GetNode(), -1 );
4715                         const sal_Int32 nMkCnt = aDelPam.GetMark()->GetContentIndex();
4716 
4717                         SplitNode( *aDelPam.GetPoint(), false );
4718 
4719                         ++aMkNd;
4720                         aDelPam.GetMark()->Assign( aMkNd, nMkCnt );
4721                         bFirst = false;
4722                     }
4723                     else
4724                         SplitNode( *aDelPam.GetPoint(), false );
4725                 }
4726                 if( !sIns.isEmpty() )
4727                 {
4728                     InsertString( aDelPam, sIns );
4729                 }
4730 
4731                 SwPaM aTmpRange( *aDelPam.GetPoint() );
4732                 aTmpRange.SetMark();
4733 
4734                 ++aPtNd;
4735                 aDelPam.GetPoint()->Assign(aPtNd, nPtCnt);
4736                 *aTmpRange.GetMark() = *aDelPam.GetPoint();
4737 
4738                 m_rDoc.RstTextAttrs( aTmpRange );
4739                 InsertItemSet( aTmpRange, aSet );
4740             }
4741 
4742             // tdf#139982: Appending the redline may immediately delete flys
4743             // anchored in the previous text if it's inside an insert redline.
4744             // Also flys will be deleted if the redline is accepted. Move them
4745             // to the position between the previous text and the new text,
4746             // there the chance of surviving both accept and reject is best.
4747             SaveFlyArr flys;
4748             SaveFlyInRange(aDelPam, *aDelPam.End(), flys, false);
4749 
4750             if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4751             {
4752                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
4753                     std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE ));
4754             }
4755             // add redline similar to DeleteAndJoinWithRedlineImpl()
4756             std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*aDelPam.GetMark()));
4757             pCursor->SetMark();
4758             *pCursor->GetPoint() = *aDelPam.GetPoint();
4759             m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true);
4760             RestFlyInRange(flys, *aDelPam.End(), &aDelPam.End()->GetNode(), true);
4761             sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor);
4762 
4763             *rPam.GetMark() = *aDelPam.GetMark();
4764             if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
4765             {
4766                 *aDelPam.GetPoint() = *rPam.GetPoint();
4767                 m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
4768 
4769                 // If any Redline will change (split!) the node
4770                 const ::sw::mark::IMark* pBkmk =
4771                     m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam,
4772                         OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK,
4773                         ::sw::mark::InsertMode::New);
4774 
4775                 aDelPam.GetPoint()->Assign( SwNodeOffset(0) );
4776                 aDelPam.GetMark()->Assign( SwNodeOffset(0) );
4777                 rPam.GetPoint()->Assign( SwNodeOffset(0) );
4778                 *rPam.GetMark() = *rPam.GetPoint();
4779                 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld );
4780 
4781                 *rPam.GetPoint() = pBkmk->GetMarkPos();
4782                 *rPam.GetMark() = pBkmk->IsExpanded() ? pBkmk->GetOtherMarkPos() : pBkmk->GetMarkPos();
4783 
4784                 m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk);
4785             }
4786             bJoinText = false;
4787         }
4788         else
4789         {
4790             assert((pStt->GetNode() == pEnd->GetNode() ||
4791                     ( pStt->GetNodeIndex() + 1 == pEnd->GetNodeIndex() &&
4792                         !pEnd->GetContentIndex() )) &&
4793                     "invalid range: Point and Mark on different nodes" );
4794 
4795             if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
4796                 m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any );
4797 
4798             SwUndoReplace* pUndoRpl = nullptr;
4799             bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
4800             if (bDoesUndo)
4801             {
4802                 pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace);
4803                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoRpl));
4804             }
4805             ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo());
4806 
4807             if( aDelPam.GetPoint() != pStt )
4808                 aDelPam.Exchange();
4809 
4810             SwNodeIndex aPtNd( pStt->GetNode(), -1 );
4811             const sal_Int32 nPtCnt = pStt->GetContentIndex();
4812 
4813             // Set the values again, if Frames or footnotes on the Text have been removed.
4814             nStt = nPtCnt;
4815             nEnd = bOneNode ? pEnd->GetContentIndex()
4816                             : pTextNd->GetText().getLength();
4817 
4818             bool bFirst = true;
4819             OUString sIns;
4820             while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) )
4821             {
4822                 if (!bFirst || nStt == pTextNd->GetText().getLength())
4823                 {
4824                     InsertString( aDelPam, sIns );
4825                 }
4826                 else if( nStt < nEnd || !sIns.isEmpty() )
4827                 {
4828                     pTextNd->ReplaceText( *pStt, nEnd - nStt, sIns );
4829                 }
4830                 SplitNode( *pStt, false);
4831                 bFirst = false;
4832             }
4833 
4834             if( bFirst || !sIns.isEmpty() )
4835             {
4836                 if (!bFirst || nStt == pTextNd->GetText().getLength())
4837                 {
4838                     InsertString( aDelPam, sIns );
4839                 }
4840                 else if( nStt < nEnd || !sIns.isEmpty() )
4841                 {
4842                     pTextNd->ReplaceText( *pStt, nEnd - nStt, sIns );
4843                 }
4844             }
4845 
4846             *rPam.GetPoint() = *aDelPam.GetMark();
4847             ++aPtNd;
4848             rPam.GetMark()->Assign( aPtNd, nPtCnt );
4849 
4850             if (bJoinText)
4851             {
4852                 assert(rPam.GetPoint() == rPam.End());
4853                 // move so that SetEnd remembers position after sw_JoinText
4854                 rPam.Move(fnMoveBackward);
4855             }
4856             else if (aDelPam.GetPoint() == pStt) // backward selection?
4857             {
4858                 assert(*rPam.GetMark() <= *rPam.GetPoint());
4859                 rPam.Exchange(); // swap so that rPam is backwards
4860             }
4861 
4862             if( pUndoRpl )
4863             {
4864                 pUndoRpl->SetEnd(rPam);
4865             }
4866         }
4867     }
4868 
4869     bool bRet(true);
4870     if (bJoinText)
4871     {
4872         bRet = ::sw_JoinText(rPam, bJoinPrev);
4873     }
4874 
4875     m_rDoc.getIDocumentState().SetModified();
4876     return bRet;
4877 }
4878 
4879 SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode,
4880                                     const SfxItemSet* pFlyAttrSet,
4881                                     const SfxItemSet* pGrfAttrSet,
4882                                     SwFrameFormat* pFrameFormat)
4883 {
4884     SwFlyFrameFormat *pFormat = nullptr;
4885     if( pNode )
4886     {
4887         pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA,
4888                                 pFlyAttrSet, pFrameFormat );
4889         if( pGrfAttrSet )
4890             pNode->SetAttr( *pGrfAttrSet );
4891     }
4892     return pFormat;
4893 }
4894 
4895 #define NUMRULE_STATE \
4896     std::shared_ptr<SwNumRuleItem> aNumRuleItemHolderIfSet; \
4897     std::shared_ptr<SfxStringItem> aListIdItemHolderIfSet; \
4898 
4899 #define PUSH_NUMRULE_STATE \
4900      lcl_PushNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd );
4901 
4902 #define POP_NUMRULE_STATE \
4903      lcl_PopNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd, rPam );
4904 
4905 static void lcl_PushNumruleState(
4906     std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
4907     std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
4908     const SwTextNode *pDestTextNd )
4909 {
4910     // Safe numrule item at destination.
4911     // #i86492# - Safe also <ListId> item of destination.
4912     const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet();
4913     if (pAttrSet == nullptr)
4914         return;
4915 
4916     if (const SwNumRuleItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
4917     {
4918         aNumRuleItemHolderIfSet.reset(pItem->Clone());
4919     }
4920 
4921     if (const SfxStringItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_LIST_ID, false))
4922     {
4923         aListIdItemHolderIfSet.reset(pItem->Clone());
4924     }
4925 }
4926 
4927 static void lcl_PopNumruleState(
4928     const std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet,
4929     const std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet,
4930     SwTextNode *pDestTextNd, const SwPaM& rPam )
4931 {
4932     /* If only a part of one paragraph is copied
4933        restore the numrule at the destination. */
4934     // #i86492# - restore also <ListId> item
4935     if ( lcl_MarksWholeNode(rPam) )
4936         return;
4937 
4938     if (aNumRuleItemHolderIfSet)
4939     {
4940         pDestTextNd->SetAttr(*aNumRuleItemHolderIfSet);
4941     }
4942     else
4943     {
4944         pDestTextNd->ResetAttr(RES_PARATR_NUMRULE);
4945     }
4946 
4947     if (aListIdItemHolderIfSet)
4948     {
4949         pDestTextNd->SetAttr(*aListIdItemHolderIfSet);
4950     }
4951     else
4952     {
4953         pDestTextNd->ResetAttr(RES_PARATR_LIST_ID);
4954     }
4955 }
4956 
4957 bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos,
4958         SwCopyFlags const flags,
4959         SwPaM *const pCopyRange) const
4960 {
4961     std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
4962 
4963     sw::CalcBreaks(Breaks, rPam, true);
4964 
4965     if (Breaks.empty())
4966     {
4967         return CopyImplImpl(rPam, rPos, flags, pCopyRange);
4968     }
4969 
4970     SwPosition const & rSelectionEnd( *rPam.End() );
4971 
4972     bool bRet(true);
4973     bool bFirst(true);
4974     // iterate from end to start, ... don't think it's necessary here?
4975     auto iter( Breaks.rbegin() );
4976     SwNodeOffset nOffset(0);
4977     SwNodes const& rNodes(rPam.GetPoint()->GetNodes());
4978     SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node!
4979     SwPosition & rEnd( *aPam.End() );
4980     SwPosition & rStart( *aPam.Start() );
4981     SwPaM copyRange(rPos, rPos);
4982 
4983     while (iter != Breaks.rend())
4984     {
4985         rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1);
4986         if (rStart < rEnd) // check if part is empty
4987         {
4988             // pass in copyRange member as rPos; should work ...
4989             bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, &copyRange);
4990             nOffset = iter->first - rStart.GetNodeIndex(); // fly nodes...
4991             if (pCopyRange)
4992             {
4993                 if (bFirst)
4994                 {
4995                     pCopyRange->SetMark();
4996                     *pCopyRange->GetMark() = *copyRange.End();
4997                 }
4998                 *pCopyRange->GetPoint() = *copyRange.Start();
4999             }
5000             bFirst = false;
5001         }
5002         rEnd.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second);
5003         ++iter;
5004     }
5005 
5006     rStart = *rPam.Start(); // set to original start
5007     if (rStart < rEnd) // check if part is empty
5008     {
5009         bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, &copyRange);
5010         if (pCopyRange)
5011         {
5012             if (bFirst)
5013             {
5014                 pCopyRange->SetMark();
5015                 *pCopyRange->GetMark() = *copyRange.End();
5016             }
5017             *pCopyRange->GetPoint() = *copyRange.Start();
5018         }
5019     }
5020 
5021     return bRet;
5022 }
5023 
5024 bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos,
5025         SwCopyFlags const flags,
5026         SwPaM *const pCpyRange) const
5027 {
5028     SwDoc& rDoc = rPos.GetNode().GetDoc();
5029     const bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection();
5030 
5031     auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
5032 
5033     // Catch when there's no copy to do.
5034     if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel) ||
5035         //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end
5036         //JP 15.11.2001: don't test inclusive the end, ever exclusive
5037         ( &rDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd ))
5038     {
5039         return false;
5040     }
5041 
5042     const bool bEndEqualIns = &rDoc == &m_rDoc && rPos == *pEnd;
5043 
5044     // If Undo is enabled, create the UndoCopy object
5045     SwUndoCpyDoc* pUndo = nullptr;
5046     // lcl_DeleteRedlines may delete the start or end node of the cursor when
5047     // removing the redlines so use cursor that is corrected by PaMCorrAbs
5048     std::shared_ptr<SwUnoCursor> const pCopyPam(rDoc.CreateUnoCursor(rPos));
5049 
5050     SwTableNumFormatMerge aTNFM( m_rDoc, rDoc );
5051     std::optional<std::vector<SwFrameFormat*>> pFlys;
5052     std::vector<SwFrameFormat*> const* pFlysAtInsPos;
5053 
5054     if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5055     {
5056         pUndo = new SwUndoCpyDoc(*pCopyPam);
5057         pFlysAtInsPos = pUndo->GetFlysAnchoredAt();
5058     }
5059     else
5060     {
5061         pFlys = sw::GetFlysAnchoredAt(rDoc, rPos.GetNodeIndex());
5062         pFlysAtInsPos = pFlys ? &*pFlys : nullptr;
5063     }
5064 
5065     RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags();
5066     rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore);
5067 
5068     // Move the PaM one node back from the insert position, so that
5069     // the position doesn't get moved
5070     pCopyPam->SetMark();
5071     bool bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent);
5072     // If the position was shifted from more than one node, an end node has been skipped
5073     bool bAfterTable = false;
5074     if ((rPos.GetNodeIndex() - pCopyPam->GetPoint()->GetNodeIndex()) > SwNodeOffset(1))
5075     {
5076         // First go back to the original place
5077         *(pCopyPam->GetPoint()) = rPos;
5078 
5079         bCanMoveBack = false;
5080         bAfterTable = true;
5081     }
5082     if( !bCanMoveBack )
5083     {
5084         pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5085         assert(pCopyPam->GetPoint()->GetContentIndex() == 0);
5086     }
5087 
5088     SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() );
5089     SwNodeIndex aInsPos( rPos.GetNode() );
5090     const bool bOneNode = pStt->GetNode() == pEnd->GetNode();
5091     SwTextNode* pSttTextNd = pStt->GetNode().GetTextNode();
5092     SwTextNode* pEndTextNd = pEnd->GetNode().GetTextNode();
5093     SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode();
5094     bool bCopyCollFormat = !rDoc.IsInsOnlyTextGlossary() &&
5095                         ( (pDestTextNd && !pDestTextNd->GetText().getLength()) ||
5096                           ( !bOneNode && !rPos.GetContentIndex() ) );
5097     bool bCopyBookmarks = true;
5098     bool bCopyPageSource  = false;
5099     SwNodeOffset nDeleteTextNodes(0);
5100 
5101     // #i104585# copy outline num rule to clipboard (for ASCII filter)
5102     if (rDoc.IsClipBoard() && m_rDoc.GetOutlineNumRule())
5103     {
5104         rDoc.SetOutlineNumRule(*m_rDoc.GetOutlineNumRule());
5105     }
5106 
5107     // #i86492#
5108     // Correct the search for a previous list:
5109     // First search for non-outline numbering list. Then search for non-outline
5110     // bullet list.
5111     // Keep also the <ListId> value for possible propagation.
5112     OUString aListIdToPropagate;
5113     const SwNumRule* pNumRuleToPropagate =
5114         rDoc.SearchNumRule( rPos, false, true, false, 0, aListIdToPropagate, nullptr, true );
5115     if ( !pNumRuleToPropagate )
5116     {
5117         pNumRuleToPropagate =
5118             rDoc.SearchNumRule( rPos, false, false, false, 0, aListIdToPropagate, nullptr, true );
5119     }
5120     // #i86492#
5121     // Do not propagate previous found list, if
5122     // - destination is an empty paragraph which is not in a list and
5123     // - source contains at least one paragraph which is not in a list
5124     // or
5125     // - source is a table
5126     if ( pNumRuleToPropagate &&
5127          ((pDestTextNd && !pDestTextNd->GetText().getLength() &&
5128          !pDestTextNd->IsInList() &&
5129          !lcl_ContainsOnlyParagraphsInList(rPam)) ||
5130          rPam.GetBound().nNode.GetNode().GetNodeType() == SwNodeType::Table) )
5131     {
5132         pNumRuleToPropagate = nullptr;
5133     }
5134 
5135     // This do/while block is only there so that we can break out of it!
5136     do {
5137         if( pSttTextNd )
5138         {
5139             ++nDeleteTextNodes; // must be joined in Undo
5140             // Don't copy the beginning completely?
5141             if( !bCopyCollFormat || bColumnSel || pStt->GetContentIndex() )
5142             {
5143                 SwContentIndex aDestIdx( rPos.GetContentNode(), rPos.GetContentIndex() );
5144                 bool bCopyOk = false;
5145                 if( !pDestTextNd )
5146                 {
5147                     if( pStt->GetContentIndex() || bOneNode )
5148                         pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5149                             rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5150                     else
5151                     {
5152                         pDestTextNd = pSttTextNd->MakeCopy(rDoc, aInsPos.GetNode(), true)->GetTextNode();
5153                         bCopyOk = true;
5154                     }
5155                     aDestIdx.Assign( pDestTextNd, 0 );
5156                     bCopyCollFormat = true;
5157                 }
5158                 else if( !bOneNode || bColumnSel )
5159                 {
5160                     const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5161                     {
5162                         ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5163                         rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5164                     }
5165 
5166                     if (bCanMoveBack && rPos == *pCopyPam->GetPoint())
5167                     {
5168                         // after the SplitNode, span the CpyPam correctly again
5169                         pCopyPam->Move( fnMoveBackward, GoInContent );
5170                         pCopyPam->Move( fnMoveBackward, GoInContent );
5171                     }
5172 
5173                     pDestTextNd = rDoc.GetNodes()[ aInsPos.GetIndex()-SwNodeOffset(1) ]->GetTextNode();
5174                     aDestIdx.Assign(
5175                             pDestTextNd, pDestTextNd->GetText().getLength());
5176 
5177                     // Correct the area again
5178                     if( bEndEqualIns )
5179                     {
5180                         bool bChg = pEnd != rPam.GetPoint();
5181                         if( bChg )
5182                             rPam.Exchange();
5183                         rPam.Move( fnMoveBackward, GoInContent );
5184                         if( bChg )
5185                             rPam.Exchange();
5186                     }
5187                     else if( rPos == *pEnd )
5188                     {
5189                         // The end was also moved
5190                         pEnd->Adjust(SwNodeOffset(-1));
5191                         pEnd->SetContent( nContentEnd );
5192                     }
5193                     // tdf#63022 always reset pEndTextNd after SplitNode
5194                     aRg.aEnd = pEnd->GetNode();
5195                     pEndTextNd = pEnd->GetNode().GetTextNode();
5196                 }
5197 
5198                 NUMRULE_STATE
5199                 if( bCopyCollFormat && bOneNode )
5200                 {
5201                     PUSH_NUMRULE_STATE
5202                 }
5203 
5204                 if( !bCopyOk )
5205                 {
5206                     const sal_Int32 nCpyLen = ( bOneNode
5207                                            ? pEnd->GetContentIndex()
5208                                            : pSttTextNd->GetText().getLength())
5209                                          - pStt->GetContentIndex();
5210                     pSttTextNd->CopyText( pDestTextNd, aDestIdx, *pStt, nCpyLen );
5211                     if( bEndEqualIns )
5212                         pEnd->AdjustContent( -nCpyLen );
5213                 }
5214 
5215                 ++aRg.aStart;
5216 
5217                 if( bOneNode )
5218                 {
5219                     if (bCopyCollFormat)
5220                     {
5221                         // tdf#138897 no Undo for applying style, SwUndoInserts does it
5222                         pSttTextNd->CopyCollFormat(*pDestTextNd, false);
5223                         POP_NUMRULE_STATE
5224                     }
5225 
5226                     // Copy at-char flys in rPam.
5227                     // Update to new (start) node for flys.
5228                     // tdf#126626 prevent duplicate Undos.
5229                     ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5230                     CopyFlyInFlyImpl(aRg, &rPam, *pDestTextNd, false);
5231 
5232                     break;
5233                 }
5234             }
5235         }
5236         else if( pDestTextNd )
5237         {
5238             // Problems with insertion of table selections into "normal" text solved.
5239             // We have to set the correct PaM for Undo, if this PaM starts in a textnode,
5240             // the undo operation will try to merge this node after removing the table.
5241             // If we didn't split a textnode, the PaM should start at the inserted table node
5242             if( rPos.GetContentIndex() == pDestTextNd->Len() )
5243             {    // Insertion at the last position of a textnode (empty or not)
5244                 ++aInsPos; // The table will be inserted behind the text node
5245             }
5246             else if( rPos.GetContentIndex() )
5247             {   // Insertion in the middle of a text node, it has to be split
5248                 // (and joined from undo)
5249                 ++nDeleteTextNodes;
5250 
5251                 const sal_Int32 nContentEnd = pEnd->GetContentIndex();
5252                 {
5253                     ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo());
5254                     rDoc.getIDocumentContentOperations().SplitNode( rPos, false );
5255                 }
5256 
5257                 if (bCanMoveBack && rPos == *pCopyPam->GetPoint())
5258                 {
5259                     // after the SplitNode, span the CpyPam correctly again
5260                     pCopyPam->Move( fnMoveBackward, GoInContent );
5261                     pCopyPam->Move( fnMoveBackward, GoInContent );
5262                 }
5263 
5264                 // Correct the area again
5265                 if( bEndEqualIns )
5266                     --aRg.aEnd;
5267                 // The end would also be moved
5268                 else if( rPos == *pEnd )
5269                 {
5270                     rPos.Adjust(SwNodeOffset(-1));
5271                     rPos.SetContent( nContentEnd );
5272                     --aRg.aEnd;
5273                 }
5274             }
5275             else if( bCanMoveBack )
5276             {   // Insertion at the first position of a text node. It will not be split, the table
5277                 // will be inserted before the text node.
5278                 // See below, before the SetInsertRange function of the undo object will be called,
5279                 // the CpyPam would be moved to the next content position. This has to be avoided
5280                 // We want to be moved to the table node itself thus we have to set bCanMoveBack
5281                 // and to manipulate pCopyPam.
5282                 bCanMoveBack = false;
5283                 pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5284             }
5285         }
5286 
5287         pDestTextNd = aInsPos.GetNode().GetTextNode();
5288         if (pEndTextNd)
5289         {
5290             SwContentIndex aDestIdx( aInsPos.GetNode().GetContentNode(), rPos.GetContentIndex() );
5291             if( !pDestTextNd )
5292             {
5293                 pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(),
5294                             rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD));
5295                 aDestIdx.Assign( pDestTextNd, 0  );
5296                 --aInsPos;
5297 
5298                 // if we have to insert an extra text node
5299                 // at the destination, this node will be our new destination
5300                 // (text) node, and thus we increment nDeleteTextNodes. This
5301                 // will ensure that this node will be deleted during Undo.
5302                 ++nDeleteTextNodes; // must be deleted
5303             }
5304 
5305             const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty();
5306 
5307             NUMRULE_STATE
5308             if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5309             {
5310                 PUSH_NUMRULE_STATE
5311             }
5312 
5313             pEndTextNd->CopyText( pDestTextNd, aDestIdx, SwContentIndex( pEndTextNd ),
5314                             pEnd->GetContentIndex() );
5315 
5316             // Also copy all format templates
5317             if( bCopyCollFormat && ( bOneNode || bEmptyDestNd ))
5318             {
5319                 // tdf#138897 no Undo for applying style, SwUndoInserts does it
5320                 pEndTextNd->CopyCollFormat(*pDestTextNd, false);
5321                 if ( bOneNode )
5322                 {
5323                     POP_NUMRULE_STATE
5324                 }
5325             }
5326         }
5327 
5328         SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange );
5329         if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5330         {
5331             if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet())
5332             {
5333                 aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() );
5334                 if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) )
5335                     pDestTextNd->ResetAttr( RES_BREAK );
5336                 if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) )
5337                     pDestTextNd->ResetAttr( RES_PAGEDESC );
5338             }
5339         }
5340 
5341         {
5342             SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5343             if (bCanMoveBack)
5344             {   // pCopyPam is actually 1 before the copy range so move it fwd
5345                 SwPaM temp(*pCopyPam->GetPoint());
5346                 temp.Move(fnMoveForward, GoInContent);
5347                 startPos = *temp.GetPoint();
5348             }
5349             assert(startPos.GetNode().IsContentNode());
5350             std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos);
5351             if( aInsPos == pEnd->GetNode() )
5352             {
5353                 SwNodeIndex aSaveIdx( aInsPos, -1 );
5354                 assert(pStt->GetNode() != pEnd->GetNode());
5355                 pEnd->SetContent(0); // TODO why this?
5356                 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5357                 ++aSaveIdx;
5358                 pEnd->Assign(aSaveIdx);
5359             }
5360             else
5361                 CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags);
5362 
5363             bCopyBookmarks = false;
5364         }
5365 
5366         // at-char anchors post SplitNode are on index 0 of 2nd node and will
5367         // remain there - move them back to the start (end would also work?)
5368         // ... also for at-para anchors; here start is preferable because
5369         // it's consistent with SplitNode from SwUndoInserts::RedoImpl()
5370         if (pFlysAtInsPos)
5371         {
5372             // init *again* - because CopyWithFlyInFly moved startPos
5373             SwPosition startPos(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1));
5374             if (bCanMoveBack)
5375             {   // pCopyPam is actually 1 before the copy range so move it fwd
5376                 SwPaM temp(*pCopyPam->GetPoint());
5377                 temp.Move(fnMoveForward, GoInContent);
5378                 startPos = *temp.GetPoint();
5379             }
5380             assert(startPos.GetNode().IsContentNode());
5381             SwPosition startPosAtPara(startPos);
5382             startPosAtPara.nContent.Assign(nullptr, 0);
5383 
5384             for (SwFrameFormat * pFly : *pFlysAtInsPos)
5385             {
5386                 SwFormatAnchor const*const pAnchor = &pFly->GetAnchor();
5387                 if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR)
5388                 {
5389                     SwFormatAnchor anchor(*pAnchor);
5390                     anchor.SetAnchor( &startPos );
5391                     pFly->SetFormatAttr(anchor);
5392                 }
5393                 else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA)
5394                 {
5395                     SwFormatAnchor anchor(*pAnchor);
5396                     anchor.SetAnchor( &startPosAtPara );
5397 
5398                     bool bSplitFly = false;
5399                     if (pFly->GetFlySplit().GetValue())
5400                     {
5401                         SwIterator<SwFrame, SwModify> aIter(*pFly);
5402                         bSplitFly = aIter.First() && aIter.Next();
5403                     }
5404                     if (bSplitFly)
5405                     {
5406                         // This fly format has multiple frames, and we change the anchor. Remove the
5407                         // old frames, which were based on the old anchor position.
5408                         pFly->DelFrames();
5409                     }
5410 
5411                     pFly->SetFormatAttr(anchor);
5412 
5413                     if (bSplitFly)
5414                     {
5415                         // Re-create the frames now that the new anchor is set.
5416                         pFly->MakeFrames();
5417                     }
5418                 }
5419             }
5420         }
5421 
5422         if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd)
5423         {
5424             // Put the breaks back into the first node
5425             if( aBrkSet.Count() && nullptr != ( pDestTextNd = rDoc.GetNodes()[
5426                     pCopyPam->GetPoint()->GetNodeIndex()+1 ]->GetTextNode()))
5427             {
5428                 pDestTextNd->SetAttr( aBrkSet );
5429                 bCopyPageSource = true;
5430             }
5431         }
5432     } while( false );
5433 
5434 
5435     // it is not possible to make this test when copy from the clipBoard to document
5436     //  in this case the PageNum not exist anymore
5437     // tdf#39400 and tdf#97526
5438     // when copy from document to ClipBoard, and it is from the first page
5439     //  and not the source has the page break
5440     if (rDoc.IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource)
5441     {
5442         if (pDestTextNd)
5443         {
5444             pDestTextNd->ResetAttr(RES_BREAK);        // remove the page-break
5445             pDestTextNd->ResetAttr(RES_PAGEDESC);
5446         }
5447     }
5448 
5449 
5450     // Adjust position (in case it was moved / in another node)
5451     rPos.nContent.Assign( rPos.GetNode().GetContentNode(),
5452                             rPos.GetContentIndex() );
5453 
5454     if( rPos.GetNode() != aInsPos.GetNode() )
5455     {
5456         if (aInsPos < rPos.GetNode())
5457         {   // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above
5458             pCopyPam->GetMark()->AssignEndIndex(*aInsPos.GetNode().GetContentNode());
5459         }
5460         else // incremented in (!pSttTextNd && pDestTextNd) above
5461         {
5462             pCopyPam->GetMark()->Assign(aInsPos);
5463         }
5464         rPos = *pCopyPam->GetMark();
5465     }
5466     else
5467         *pCopyPam->GetMark() = rPos;
5468 
5469     if ( !bAfterTable )
5470         pCopyPam->Move( fnMoveForward, bCanMoveBack ? GoInContent : GoInNode );
5471     else
5472     {
5473         // Reset the offset to 0 as it was before the insertion
5474         pCopyPam->GetPoint()->Adjust(SwNodeOffset(+1));
5475 
5476         // If the next node is a start node, then step back: the start node
5477         // has been copied and needs to be in the selection for the undo
5478         if (pCopyPam->GetPoint()->GetNode().IsStartNode())
5479             pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1));
5480 
5481     }
5482     pCopyPam->Exchange();
5483 
5484     // Also copy all bookmarks
5485     if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() )
5486     {
5487         sw::CopyBookmarks(rPam, *pCopyPam->Start());
5488     }
5489 
5490     if( RedlineFlags::DeleteRedlines & eOld )
5491     {
5492         assert(*pCopyPam->GetPoint() == rPos);
5493         // the Node rPos points to may be deleted so unregister ...
5494         rPos.nContent.Assign(nullptr, 0);
5495         lcl_DeleteRedlines(rPam, *pCopyPam);
5496         rPos = *pCopyPam->GetPoint(); // ... and restore.
5497     }
5498 
5499     // If Undo is enabled, store the inserted area
5500     if (rDoc.GetIDocumentUndoRedo().DoesUndo())
5501     {
5502         // append it after styles have been copied when copying nodes
5503         rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
5504         pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes);
5505     }
5506 
5507     if( pCpyRange )
5508     {
5509         pCpyRange->SetMark();
5510         *pCpyRange->GetPoint() = *pCopyPam->GetPoint();
5511         *pCpyRange->GetMark() = *pCopyPam->GetMark();
5512     }
5513 
5514     if ( pNumRuleToPropagate != nullptr )
5515     {
5516         // #i86492# - use <SwDoc::SetNumRule(..)>, because it also handles the <ListId>
5517         // Don't reset indent attributes, that would mean loss of direct
5518         // formatting.
5519         rDoc.SetNumRule( *pCopyPam, *pNumRuleToPropagate, false, nullptr,
5520                           aListIdToPropagate, true, /*bResetIndentAttrs=*/false );
5521     }
5522 
5523     rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
5524     rDoc.getIDocumentState().SetModified();
5525 
5526     return true;
5527 }
5528 
5529 
5530 }
5531 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
5532