1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
22 #include <rtl/ustrbuf.hxx>
23 #include <o3tl/safeint.hxx>
24 #include <osl/diagnose.h>
25 #include <rtl/character.hxx>
26 #include <swmodule.hxx>
28 #include <IDocumentUndoRedo.hxx>
29 #include <DocumentContentOperationsManager.hxx>
30 #include <IDocumentRedlineAccess.hxx>
31 #include <IDocumentState.hxx>
35 #include <redline.hxx>
36 #include <UndoRedline.hxx>
37 #include <section.hxx>
40 #include <fmtcntnt.hxx>
42 #include <frameformats.hxx>
44 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
45 #include <com/sun/star/document/XDocumentProperties.hpp>
46 #include <com/sun/star/frame/XModel.hpp>
50 #include <type_traits>
53 using namespace ::com::sun::star
;
59 class SwCompareLine final
61 const SwNode
* m_pNode
;
63 explicit SwCompareLine( const SwNode
& rNd
) : m_pNode( &rNd
) {}
64 SwCompareLine() : m_pNode( nullptr ) {}
66 sal_uLong
GetHashValue() const;
67 bool Compare( const SwCompareLine
& rLine
) const;
69 static sal_uLong
GetTextNodeHashValue( const SwTextNode
& rNd
, sal_uLong nVal
);
70 static bool CompareNode( const SwNode
& rDstNd
, const SwNode
& rSrcNd
);
71 static bool CompareTextNd( const SwTextNode
& rDstNd
,
72 const SwTextNode
& rSrcNd
);
74 bool ChangesInLine( const SwCompareLine
& rLine
,
75 std::unique_ptr
<SwPaM
>& rpInsRing
, std::unique_ptr
<SwPaM
>& rpDelRing
) const;
77 const SwNode
& GetNode() const { return *m_pNode
; }
79 const SwNode
& GetEndNode() const;
82 OUString
GetText() const;
91 std::unique_ptr
<size_t[]> m_pIndex
;
92 std::unique_ptr
<bool[]> m_pChangedFlag
;
94 std::unique_ptr
<SwPaM
> m_pInsertRing
, m_pDelRing
;
96 static SwNodeOffset
PrevIdx( const SwNode
* pNd
);
97 static SwNodeOffset
NextIdx( const SwNode
* pNd
);
99 vector
<SwCompareLine
> m_aLines
;
102 // Truncate beginning and end and add all others to the LinesArray
103 void CheckRanges( CompareData
& );
105 virtual const SwNode
& GetEndOfContent() = 0;
108 CompareData(SwDoc
& rD
, bool bRecordDiff
)
110 , m_bRecordDiff(bRecordDiff
)
113 virtual ~CompareData();
115 // Are there differences?
116 bool HasDiffs( const CompareData
& rData
) const;
118 // Triggers the comparison and creation of two documents
119 void CompareLines( CompareData
& rData
);
120 // Display the differences - calls the methods ShowInsert and ShowDelete.
121 // These are passed the start and end line number.
122 // Displaying the actually content is to be handled by the subclass!
123 sal_uLong
ShowDiffs( const CompareData
& rData
);
125 void ShowInsert( sal_uLong nStt
, sal_uLong nEnd
);
126 void ShowDelete( const CompareData
& rData
, sal_uLong nStt
,
127 sal_uLong nEnd
, sal_uLong nInsPos
);
128 void CheckForChangesInLine( const CompareData
& rData
,
129 sal_uLong nStt
, sal_uLong nEnd
,
130 sal_uLong nThisStt
, sal_uLong nThisEnd
);
132 // Set non-ambiguous index for a line. Same lines have the same index, even in the other CompareData!
133 void SetIndex( size_t nLine
, size_t nIndex
);
134 size_t GetIndex( size_t nLine
) const
135 { return nLine
< m_aLines
.size() ? m_pIndex
[ nLine
] : 0; }
137 // Set/get of a line has changed
138 void SetChanged( size_t nLine
, bool bFlag
= true );
139 bool GetChanged( size_t nLine
) const
141 return (m_pChangedFlag
&& nLine
< m_aLines
.size())
142 && m_pChangedFlag
[ nLine
];
145 size_t GetLineCount() const { return m_aLines
.size(); }
146 const SwCompareLine
& GetLine( size_t nLine
) const
147 { return m_aLines
[ nLine
]; }
148 void InsertLine( SwCompareLine aLine
)
149 { m_aLines
.push_back( aLine
); }
151 void SetRedlinesToDoc( bool bUseDocInfo
);
154 class CompareMainText
: public CompareData
157 CompareMainText(SwDoc
&rD
, bool bRecordDiff
)
158 : CompareData(rD
, bRecordDiff
)
162 virtual const SwNode
& GetEndOfContent() override
164 return m_rDoc
.GetNodes().GetEndOfContent();
168 class CompareFrameFormatText
: public CompareData
170 const SwNodeIndex
&m_rIndex
;
172 CompareFrameFormatText(SwDoc
&rD
, const SwNodeIndex
&rIndex
)
173 : CompareData(rD
, true/*bRecordDiff*/)
178 virtual const SwNode
& GetEndOfContent() override
180 return *m_rIndex
.GetNode().EndOfSectionNode();
188 sal_uLong nNext
, nHash
;
192 : nNext( 0 ), nHash( 0 ) {}
195 std::unique_ptr
<sal_uLong
[]> m_pHashArr
;
196 std::unique_ptr
<HashData
[]> m_pDataArr
;
197 sal_uLong m_nCount
, m_nPrime
;
200 explicit Hash( sal_uLong nSize
);
202 void CalcHashValue( CompareData
& rData
);
204 sal_uLong
GetCount() const { return m_nCount
; }
212 std::unique_ptr
<sal_uLong
[]> m_pIndex
;
213 std::unique_ptr
<sal_uLong
[]> m_pLineNum
;
217 MovedData( CompareData
& rData
, const char* pDiscard
);
219 sal_uLong
GetIndex( sal_uLong n
) const { return m_pIndex
[ n
]; }
220 sal_uLong
GetLineNum( sal_uLong n
) const { return m_pLineNum
[ n
]; }
221 sal_uLong
GetCount() const { return m_nCount
; }
225 /// Look for the moved lines
226 class CompareSequence
228 CompareData
&m_rData1
, &m_rData2
;
229 const MovedData
&m_rMoved1
, &m_rMoved2
;
230 std::unique_ptr
<tools::Long
[]> m_pMemory
;
231 tools::Long
*m_pFDiag
, *m_pBDiag
;
233 void Compare( sal_uLong nStt1
, sal_uLong nEnd1
, sal_uLong nStt2
, sal_uLong nEnd2
);
234 sal_uLong
CheckDiag( sal_uLong nStt1
, sal_uLong nEnd1
,
235 sal_uLong nStt2
, sal_uLong nEnd2
, sal_uLong
* pCost
);
237 CompareSequence( CompareData
& rD1
, CompareData
& rD2
,
238 const MovedData
& rMD1
, const MovedData
& rMD2
);
241 static void CountDifference( const CompareData
& rData
, sal_uLong
* pCounts
);
242 static void SetDiscard( const CompareData
& rData
,
243 char* pDiscard
, const sal_uLong
* pCounts
);
244 static void CheckDiscard( sal_uLong nLen
, char* pDiscard
);
245 static void ShiftBoundaries( CompareData
& rData1
, CompareData
& rData2
);
248 Compare( sal_uLong nDiff
, CompareData
& rData1
, CompareData
& rData2
);
251 class ArrayComparator
254 virtual bool Compare( int nIdx1
, int nIdx2
) const = 0;
255 virtual int GetLen1() const = 0;
256 virtual int GetLen2() const = 0;
257 virtual ~ArrayComparator() {}
260 /// Consider two lines equal if similar enough (e.g. look like different
261 /// versions of the same paragraph)
262 class LineArrayComparator
: public ArrayComparator
265 int m_nLen1
, m_nLen2
;
266 const CompareData
&m_rData1
, &m_rData2
;
267 int m_nFirst1
, m_nFirst2
;
270 LineArrayComparator( const CompareData
&rD1
, const CompareData
&rD2
,
271 int nStt1
, int nEnd1
, int nStt2
, int nEnd2
);
273 virtual bool Compare( int nIdx1
, int nIdx2
) const override
;
274 virtual int GetLen1() const override
{ return m_nLen1
; }
275 virtual int GetLen2() const override
{ return m_nLen2
; }
278 class WordArrayComparator
: public ArrayComparator
281 const SwTextNode
*m_pTextNode1
, *m_pTextNode2
;
282 std::unique_ptr
<int[]> m_pPos1
, m_pPos2
;
283 int m_nCount1
, m_nCount2
; // number of words
285 static void CalcPositions( int *pPos
, const SwTextNode
*pTextNd
, int &nCnt
);
288 WordArrayComparator( const SwTextNode
*pNode1
, const SwTextNode
*pNode2
);
290 virtual bool Compare( int nIdx1
, int nIdx2
) const override
;
291 virtual int GetLen1() const override
{ return m_nCount1
; }
292 virtual int GetLen2() const override
{ return m_nCount2
; }
293 int GetCharSequence( const int *pWordLcs1
, const int *pWordLcs2
,
294 int *pSubseq1
, int *pSubseq2
, int nLcsLen
);
297 class CharArrayComparator
: public ArrayComparator
300 const SwTextNode
*m_pTextNode1
, *m_pTextNode2
;
303 CharArrayComparator( const SwTextNode
*pNode1
, const SwTextNode
*pNode2
)
304 : m_pTextNode1( pNode1
), m_pTextNode2( pNode2
)
308 virtual bool Compare( int nIdx1
, int nIdx2
) const override
;
309 virtual int GetLen1() const override
{ return m_pTextNode1
->GetText().getLength(); }
310 virtual int GetLen2() const override
{ return m_pTextNode2
->GetText().getLength(); }
313 /// Options set in Tools->Options->Writer->Comparison
314 struct CmpOptionsContainer
316 SwCompareMode eCmpMode
;
323 static CmpOptionsContainer CmpOptions
;
330 std::unique_ptr
<int[]> m_pData
;
333 ArrayComparator
&m_rComparator
;
335 CommonSubseq( ArrayComparator
&rComparator
, int nMaxSize
)
336 : m_rComparator( rComparator
)
338 m_pData
.reset( new int[ nMaxSize
] );
341 int FindLCS( int *pLcs1
, int *pLcs2
, int nStt1
,
342 int nEnd1
, int nStt2
, int nEnd2
);
345 static int IgnoreIsolatedPieces( int *pLcs1
, int *pLcs2
, int nLen1
, int nLen2
,
346 int nLcsLen
, int nPieceLen
);
349 /// Use Hirschberg's algorithm to find LCS in linear space
350 class LgstCommonSubseq
: public CommonSubseq
353 static const int CUTOFF
= 1<<20; // Stop recursion at this value
355 std::unique_ptr
<int[]> m_pL1
, m_pL2
;
356 std::unique_ptr
<int[]> m_pBuff1
, m_pBuff2
;
358 void FindL( int *pL
, int nStt1
, int nEnd1
, int nStt2
, int nEnd2
);
359 int HirschbergLCS( int *pLcs1
, int *pLcs2
, int nStt1
, int nEnd1
,
360 int nStt2
, int nEnd2
);
363 explicit LgstCommonSubseq( ArrayComparator
&rComparator
);
365 int Find( int *pSubseq1
, int *pSubseq2
);
368 /// Find a common subsequence in linear time
369 class FastCommonSubseq
: private CommonSubseq
372 static const int CUTOFF
= 2056;
374 int FindFastCS( int *pSeq1
, int *pSeq2
, int nStt1
, int nEnd1
,
375 int nStt2
, int nEnd2
);
378 explicit FastCommonSubseq( ArrayComparator
&rComparator
)
379 : CommonSubseq( rComparator
, CUTOFF
)
383 int Find( int *pSubseq1
, int *pSubseq2
)
385 return FindFastCS( pSubseq1
, pSubseq2
, 0, m_rComparator
.GetLen1(),
386 0, m_rComparator
.GetLen2() );
392 CompareData::~CompareData()
396 while( m_pDelRing
->GetNext() != m_pDelRing
.get() )
397 delete m_pDelRing
->GetNext();
402 while( m_pInsertRing
->GetNext() != m_pInsertRing
.get() )
403 delete m_pInsertRing
->GetNext();
404 m_pInsertRing
.reset();
408 void CompareData::SetIndex( size_t nLine
, size_t nIndex
)
412 m_pIndex
.reset( new size_t[ m_aLines
.size() ] );
413 memset( m_pIndex
.get(), 0, m_aLines
.size() * sizeof( size_t ) );
415 if( nLine
< m_aLines
.size() )
416 m_pIndex
[ nLine
] = nIndex
;
419 void CompareData::SetChanged( size_t nLine
, bool bFlag
)
421 if( !m_pChangedFlag
)
423 m_pChangedFlag
.reset( new bool[ m_aLines
.size() +1 ] );
424 memset( m_pChangedFlag
.get(), 0, (m_aLines
.size() +1) * sizeof( bool ) );
426 if( nLine
< m_aLines
.size() )
427 m_pChangedFlag
[ nLine
] = bFlag
;
430 void CompareData::CompareLines( CompareData
& rData
)
432 CheckRanges( rData
);
434 sal_uLong nDifferent
;
436 Hash
aH( GetLineCount() + rData
.GetLineCount() + 1 );
437 aH
.CalcHashValue( *this );
438 aH
.CalcHashValue( rData
);
439 nDifferent
= aH
.GetCount();
442 Compare
aComp( nDifferent
, *this, rData
);
446 sal_uLong
CompareData::ShowDiffs( const CompareData
& rData
)
448 sal_uLong nLen1
= rData
.GetLineCount(), nLen2
= GetLineCount();
449 sal_uLong nStt1
= 0, nStt2
= 0;
452 while( nStt1
< nLen1
|| nStt2
< nLen2
)
454 if( rData
.GetChanged( nStt1
) || GetChanged( nStt2
) )
456 // Find a region of different lines between two pairs of identical
458 sal_uLong nSav1
= nStt1
, nSav2
= nStt2
;
459 while( nStt1
< nLen1
&& rData
.GetChanged( nStt1
)) ++nStt1
;
460 while( nStt2
< nLen2
&& GetChanged( nStt2
)) ++nStt2
;
464 // Check if there are changed lines (only slightly different) and
465 // compare them in detail.
466 CheckForChangesInLine( rData
, nSav1
, nStt1
, nSav2
, nStt2
);
477 bool CompareData::HasDiffs( const CompareData
& rData
) const
480 sal_uLong nLen1
= rData
.GetLineCount(), nLen2
= GetLineCount();
481 sal_uLong nStt1
= 0, nStt2
= 0;
483 while( nStt1
< nLen1
|| nStt2
< nLen2
)
485 if( rData
.GetChanged( nStt1
) || GetChanged( nStt2
) )
496 Hash::Hash( sal_uLong nSize
)
500 static const sal_uLong primes
[] =
519 67108859, /* Preposterously large . . . */
529 m_pDataArr
.reset( new HashData
[ nSize
] );
530 m_pDataArr
[0].nNext
= 0;
531 m_pDataArr
[0].nHash
= 0;
532 m_nPrime
= primes
[0];
534 for( i
= 0; primes
[i
] < nSize
/ 3; i
++)
537 m_pHashArr
= nullptr;
540 m_nPrime
= primes
[ i
];
541 m_pHashArr
.reset( new sal_uLong
[ m_nPrime
] );
542 memset( m_pHashArr
.get(), 0, m_nPrime
* sizeof( sal_uLong
) );
545 void Hash::CalcHashValue( CompareData
& rData
)
550 for( size_t n
= 0; n
< rData
.GetLineCount(); ++n
)
552 const SwCompareLine aLine
= rData
.GetLine( n
);
553 sal_uLong nH
= aLine
.GetHashValue();
555 sal_uLong
* pFound
= &m_pHashArr
[ nH
% m_nPrime
];
557 for( i
= *pFound
; ; i
= m_pDataArr
[i
].nNext
)
561 m_pDataArr
[i
].nNext
= *pFound
;
562 m_pDataArr
[i
].nHash
= nH
;
563 m_pDataArr
[i
].aLine
= aLine
;
567 else if( m_pDataArr
[i
].nHash
== nH
&&
568 m_pDataArr
[i
].aLine
.Compare( aLine
))
571 rData
.SetIndex( n
, i
);
575 Compare::Compare( sal_uLong nDiff
, CompareData
& rData1
, CompareData
& rData2
)
577 std::unique_ptr
<MovedData
> pMD1
, pMD2
;
578 // Look for the differing lines
580 std::unique_ptr
<char[]> pDiscard1( new char[ rData1
.GetLineCount() ] );
581 std::unique_ptr
<char[]> pDiscard2( new char[ rData2
.GetLineCount() ] );
583 std::unique_ptr
<sal_uLong
[]> pCount1(new sal_uLong
[ nDiff
]);
584 std::unique_ptr
<sal_uLong
[]> pCount2(new sal_uLong
[ nDiff
]);
585 memset( pCount1
.get(), 0, nDiff
* sizeof( sal_uLong
));
586 memset( pCount2
.get(), 0, nDiff
* sizeof( sal_uLong
));
588 // find indices in CompareData which have been assigned multiple times
589 CountDifference( rData1
, pCount1
.get() );
590 CountDifference( rData2
, pCount2
.get() );
592 // All which occur only once now have either been inserted or deleted.
593 // All which are also contained in the other one have been moved.
594 SetDiscard( rData1
, pDiscard1
.get(), pCount2
.get() );
595 SetDiscard( rData2
, pDiscard2
.get(), pCount1
.get() );
597 CheckDiscard( rData1
.GetLineCount(), pDiscard1
.get() );
598 CheckDiscard( rData2
.GetLineCount(), pDiscard2
.get() );
600 pMD1
.reset(new MovedData( rData1
, pDiscard1
.get() ));
601 pMD2
.reset(new MovedData( rData2
, pDiscard2
.get() ));
605 CompareSequence
aTmp( rData1
, rData2
, *pMD1
, *pMD2
);
608 ShiftBoundaries( rData1
, rData2
);
611 void Compare::CountDifference( const CompareData
& rData
, sal_uLong
* pCounts
)
613 sal_uLong nLen
= rData
.GetLineCount();
614 for( sal_uLong n
= 0; n
< nLen
; ++n
)
616 sal_uLong nIdx
= rData
.GetIndex( n
);
621 void Compare::SetDiscard( const CompareData
& rData
,
622 char* pDiscard
, const sal_uLong
* pCounts
)
624 const sal_uLong nLen
= rData
.GetLineCount();
626 // calculate Max with respect to the line count
629 for( sal_uLong n
= nLen
/ 64; ( n
= n
>> 2 ) > 0; )
632 for( sal_uLong n
= 0; n
< nLen
; ++n
)
634 sal_uLong nIdx
= rData
.GetIndex( n
);
637 nIdx
= pCounts
[ nIdx
];
638 pDiscard
[ n
] = !nIdx
? 1 : nIdx
> nMax
? 2 : 0;
645 void Compare::CheckDiscard( sal_uLong nLen
, char* pDiscard
)
647 for( sal_uLong n
= 0; n
< nLen
; ++n
)
649 if( 2 == pDiscard
[ n
] )
651 else if( pDiscard
[ n
] )
655 sal_uLong provisional
= 0;
657 /* Find end of this run of discardable lines.
658 Count how many are provisionally discardable. */
659 for (j
= n
; j
< nLen
; j
++)
663 if( 2 == pDiscard
[j
] )
667 /* Cancel provisional discards at end, and shrink the run. */
668 while( j
> n
&& 2 == pDiscard
[j
- 1] )
674 /* Now we have the length of a run of discardable lines
675 whose first and last are not provisional. */
678 /* If 1/4 of the lines in the run are provisional,
679 cancel discarding of all provisional lines in the run. */
680 if (provisional
* 4 > length
)
683 if (pDiscard
[--j
] == 2)
689 sal_uLong minimum
= 1;
690 sal_uLong tem
= length
/ 4;
692 /* MINIMUM is approximate square root of LENGTH/4.
693 A subrun of two or more provisionals can stand
694 when LENGTH is at least 16.
695 A subrun of 4 or more can stand when LENGTH >= 64. */
696 while ((tem
= tem
>> 2) > 0)
700 /* Cancel any subrun of MINIMUM or more provisionals
701 within the larger run. */
702 for (j
= 0, consec
= 0; j
< length
; j
++)
703 if (pDiscard
[n
+ j
] != 2)
705 else if (minimum
== ++consec
)
706 /* Back up to start of subrun, to cancel it all. */
708 else if (minimum
< consec
)
711 /* Scan from beginning of run
712 until we find 3 or more nonprovisionals in a row
713 or until the first nonprovisional at least 8 lines in.
714 Until that point, cancel any provisionals. */
715 for (j
= 0, consec
= 0; j
< length
; j
++)
717 if (j
>= 8 && pDiscard
[n
+ j
] == 1)
719 if (pDiscard
[n
+ j
] == 2)
724 else if (pDiscard
[n
+ j
] == 0)
732 /* I advances to the last line of the run. */
735 /* Same thing, from end. */
736 for (j
= 0, consec
= 0; j
< length
; j
++)
738 if (j
>= 8 && pDiscard
[n
- j
] == 1)
740 if (pDiscard
[n
- j
] == 2)
745 else if (pDiscard
[n
- j
] == 0)
757 Compare::MovedData::MovedData( CompareData
& rData
, const char* pDiscard
)
760 sal_uLong nLen
= rData
.GetLineCount();
763 for( n
= 0; n
< nLen
; ++n
)
765 rData
.SetChanged( n
);
771 m_pIndex
.reset( new sal_uLong
[ m_nCount
] );
772 m_pLineNum
.reset( new sal_uLong
[ m_nCount
] );
774 for( n
= 0, m_nCount
= 0; n
< nLen
; ++n
)
777 m_pIndex
[ m_nCount
] = rData
.GetIndex( n
);
778 m_pLineNum
[ m_nCount
++ ] = n
;
783 /// Find the differing lines
784 Compare::CompareSequence::CompareSequence(
785 CompareData
& rD1
, CompareData
& rD2
,
786 const MovedData
& rMD1
, const MovedData
& rMD2
)
787 : m_rData1( rD1
), m_rData2( rD2
), m_rMoved1( rMD1
), m_rMoved2( rMD2
)
789 sal_uLong nSize
= rMD1
.GetCount() + rMD2
.GetCount() + 3;
790 m_pMemory
.reset( new tools::Long
[ nSize
* 2 ] );
791 m_pFDiag
= m_pMemory
.get() + ( rMD2
.GetCount() + 1 );
792 m_pBDiag
= m_pMemory
.get() + ( nSize
+ rMD2
.GetCount() + 1 );
794 Compare( 0, rMD1
.GetCount(), 0, rMD2
.GetCount() );
797 void Compare::CompareSequence::Compare( sal_uLong nStt1
, sal_uLong nEnd1
,
798 sal_uLong nStt2
, sal_uLong nEnd2
)
800 /* Slide down the bottom initial diagonal. */
801 while( nStt1
< nEnd1
&& nStt2
< nEnd2
&&
802 m_rMoved1
.GetIndex( nStt1
) == m_rMoved2
.GetIndex( nStt2
))
808 /* Slide up the top initial diagonal. */
809 while( nEnd1
> nStt1
&& nEnd2
> nStt2
&&
810 m_rMoved1
.GetIndex( nEnd1
- 1 ) == m_rMoved2
.GetIndex( nEnd2
- 1 ))
816 /* Handle simple cases. */
818 while( nStt2
< nEnd2
)
819 m_rData2
.SetChanged( m_rMoved2
.GetLineNum( nStt2
++ ));
821 else if (nStt2
== nEnd2
)
822 while (nStt1
< nEnd1
)
823 m_rData1
.SetChanged( m_rMoved1
.GetLineNum( nStt1
++ ));
829 /* Find a point of correspondence in the middle of the files. */
831 d
= CheckDiag( nStt1
, nEnd1
, nStt2
, nEnd2
, &c
);
832 b
= m_pBDiag
[ std::make_signed_t
<decltype(d
)>(d
) ];
836 /* Use that point to split this problem into two subproblems. */
837 Compare( nStt1
, b
, nStt2
, b
- d
);
838 /* This used to use f instead of b,
839 but that is incorrect!
840 It is not necessarily the case that diagonal d
841 has a snake from b to f. */
842 Compare( b
, nEnd1
, b
- d
, nEnd2
);
847 sal_uLong
Compare::CompareSequence::CheckDiag( sal_uLong nStt1
, sal_uLong nEnd1
,
848 sal_uLong nStt2
, sal_uLong nEnd2
, sal_uLong
* pCost
)
850 const tools::Long dmin
= nStt1
- nEnd2
; /* Minimum valid diagonal. */
851 const tools::Long dmax
= nEnd1
- nStt2
; /* Maximum valid diagonal. */
852 const tools::Long fmid
= nStt1
- nStt2
; /* Center diagonal of top-down search. */
853 const tools::Long bmid
= nEnd1
- nEnd2
; /* Center diagonal of bottom-up search. */
855 tools::Long fmin
= fmid
, fmax
= fmid
; /* Limits of top-down search. */
856 tools::Long bmin
= bmid
, bmax
= bmid
; /* Limits of bottom-up search. */
858 tools::Long c
; /* Cost. */
859 tools::Long odd
= (fmid
- bmid
) & 1; /* True if southeast corner is on an odd
860 diagonal with respect to the northwest. */
862 m_pFDiag
[fmid
] = nStt1
;
863 m_pBDiag
[bmid
] = nEnd1
;
867 tools::Long d
; /* Active diagonal. */
869 /* Extend the top-down search by an edit step in each diagonal. */
871 m_pFDiag
[--fmin
- 1] = -1;
875 m_pFDiag
[++fmax
+ 1] = -1;
878 for (d
= fmax
; d
>= fmin
; d
-= 2)
880 tools::Long x
, y
, tlo
= m_pFDiag
[d
- 1], thi
= m_pFDiag
[d
+ 1];
887 while( o3tl::make_unsigned(x
) < nEnd1
&& o3tl::make_unsigned(y
) < nEnd2
&&
888 m_rMoved1
.GetIndex( x
) == m_rMoved2
.GetIndex( y
))
894 if( odd
&& bmin
<= d
&& d
<= bmax
&& m_pBDiag
[d
] <= m_pFDiag
[d
] )
901 /* Similar extend the bottom-up search. */
903 m_pBDiag
[--bmin
- 1] = INT_MAX
;
907 m_pBDiag
[++bmax
+ 1] = INT_MAX
;
910 for (d
= bmax
; d
>= bmin
; d
-= 2)
912 tools::Long x
, y
, tlo
= m_pBDiag
[d
- 1], thi
= m_pBDiag
[d
+ 1];
919 while( o3tl::make_unsigned(x
) > nStt1
&& o3tl::make_unsigned(y
) > nStt2
&&
920 m_rMoved1
.GetIndex( x
- 1 ) == m_rMoved2
.GetIndex( y
- 1 ))
926 if (!odd
&& fmin
<= d
&& d
<= fmax
&& m_pBDiag
[d
] <= m_pFDiag
[d
])
937 void lcl_ShiftBoundariesOneway( CompareData
* const pData
, CompareData
const * const pOtherData
)
941 sal_uLong i_end
= pData
->GetLineCount();
942 sal_uLong preceding
= ULONG_MAX
;
943 sal_uLong other_preceding
= ULONG_MAX
;
947 sal_uLong start
, other_start
;
949 /* Scan forwards to find beginning of another run of changes.
950 Also keep track of the corresponding point in the other file. */
952 while( i
< i_end
&& !pData
->GetChanged( i
) )
954 while( pOtherData
->GetChanged( j
++ ))
955 /* Non-corresponding lines in the other file
956 will count as the preceding batch of changes. */
969 /* Now find the end of this run of changes. */
971 while( pData
->GetChanged( ++i
))
974 /* If the first changed line matches the following unchanged one,
975 and this run does not follow right after a previous run,
976 and there are no lines deleted from the other file here,
977 then classify the first changed line as unchanged
978 and the following line as changed in its place. */
980 /* You might ask, how could this run follow right after another?
981 Only because the previous run was shifted here. */
984 pData
->GetIndex( start
) == pData
->GetIndex( i
) &&
985 !pOtherData
->GetChanged( j
) &&
986 start
!= preceding
&& other_start
!= other_preceding
)
988 pData
->SetChanged( start
++, false );
989 pData
->SetChanged( i
);
990 /* Since one line-that-matches is now before this run
991 instead of after, we must advance in the other file
1000 other_preceding
= j
;
1005 void Compare::ShiftBoundaries( CompareData
& rData1
, CompareData
& rData2
)
1007 lcl_ShiftBoundariesOneway(&rData1
, &rData2
);
1008 lcl_ShiftBoundariesOneway(&rData2
, &rData1
);
1011 sal_uLong
SwCompareLine::GetHashValue() const
1014 switch( m_pNode
->GetNodeType() )
1016 case SwNodeType::Text
:
1017 nRet
= GetTextNodeHashValue( *m_pNode
->GetTextNode(), nRet
);
1020 case SwNodeType::Table
:
1022 const SwNode
* pEndNd
= m_pNode
->EndOfSectionNode();
1023 SwNodeIndex
aIdx( *m_pNode
);
1024 while( &aIdx
.GetNode() != pEndNd
)
1026 if( aIdx
.GetNode().IsTextNode() )
1027 nRet
= GetTextNodeHashValue( *aIdx
.GetNode().GetTextNode(), nRet
);
1033 case SwNodeType::Section
:
1035 OUString
sStr( GetText() );
1036 for( sal_Int32 n
= 0; n
< sStr
.getLength(); ++n
)
1037 nRet
= (nRet
<< 1) + sStr
[ n
];
1041 case SwNodeType::Grf
:
1042 case SwNodeType::Ole
:
1043 // Fixed ID? Should never occur ...
1050 const SwNode
& SwCompareLine::GetEndNode() const
1052 const SwNode
* pNd
= m_pNode
;
1053 switch( m_pNode
->GetNodeType() )
1055 case SwNodeType::Table
:
1056 pNd
= m_pNode
->EndOfSectionNode();
1059 case SwNodeType::Section
:
1061 const SwSectionNode
& rSNd
= static_cast<const SwSectionNode
&>(*m_pNode
);
1062 const SwSection
& rSect
= rSNd
.GetSection();
1063 if( SectionType::Content
!= rSect
.GetType() || rSect
.IsProtect() )
1064 pNd
= m_pNode
->EndOfSectionNode();
1072 bool SwCompareLine::Compare( const SwCompareLine
& rLine
) const
1074 return CompareNode( *m_pNode
, *rLine
.m_pNode
);
1079 OUString
SimpleTableToText(const SwNode
&rNode
)
1081 OUStringBuffer sRet
;
1082 const SwNode
* pEndNd
= rNode
.EndOfSectionNode();
1083 SwNodeIndex
aIdx( rNode
);
1084 while (&aIdx
.GetNode() != pEndNd
)
1086 if (aIdx
.GetNode().IsTextNode())
1088 if (sRet
.getLength())
1090 sRet
.append( '\n' );
1092 sRet
.append( aIdx
.GetNode().GetTextNode()->GetExpandText(nullptr) );
1096 return sRet
.makeStringAndClear();
1100 bool SwCompareLine::CompareNode( const SwNode
& rDstNd
, const SwNode
& rSrcNd
)
1102 if( rSrcNd
.GetNodeType() != rDstNd
.GetNodeType() )
1107 switch( rDstNd
.GetNodeType() )
1109 case SwNodeType::Text
:
1110 bRet
= CompareTextNd( *rDstNd
.GetTextNode(), *rSrcNd
.GetTextNode() )
1111 && ( !CmpOptions
.bUseRsid
|| rDstNd
.GetTextNode()->CompareParRsid( *rSrcNd
.GetTextNode() ) );
1114 case SwNodeType::Table
:
1116 const SwTableNode
& rTSrcNd
= static_cast<const SwTableNode
&>(rSrcNd
);
1117 const SwTableNode
& rTDstNd
= static_cast<const SwTableNode
&>(rDstNd
);
1119 bRet
= ( rTSrcNd
.EndOfSectionIndex() - rTSrcNd
.GetIndex() ) ==
1120 ( rTDstNd
.EndOfSectionIndex() - rTDstNd
.GetIndex() );
1122 // --> #i107826#: compare actual table content
1125 bRet
= (SimpleTableToText(rSrcNd
) == SimpleTableToText(rDstNd
));
1130 case SwNodeType::Section
:
1132 const SwSectionNode
& rSSrcNd
= static_cast<const SwSectionNode
&>(rSrcNd
),
1133 & rSDstNd
= static_cast<const SwSectionNode
&>(rDstNd
);
1134 const SwSection
& rSrcSect
= rSSrcNd
.GetSection(),
1135 & rDstSect
= rSDstNd
.GetSection();
1136 SectionType eSrcSectType
= rSrcSect
.GetType(),
1137 eDstSectType
= rDstSect
.GetType();
1138 switch( eSrcSectType
)
1140 case SectionType::Content
:
1141 bRet
= SectionType::Content
== eDstSectType
&&
1142 rSrcSect
.IsProtect() == rDstSect
.IsProtect();
1143 if( bRet
&& rSrcSect
.IsProtect() )
1145 // the only have they both the same size
1146 bRet
= ( rSSrcNd
.EndOfSectionIndex() - rSSrcNd
.GetIndex() ) ==
1147 ( rSDstNd
.EndOfSectionIndex() - rSDstNd
.GetIndex() );
1151 case SectionType::ToxHeader
:
1152 case SectionType::ToxContent
:
1153 if( SectionType::ToxHeader
== eDstSectType
||
1154 SectionType::ToxContent
== eDstSectType
)
1156 // the same type of TOX?
1157 const SwTOXBase
* pSrcTOX
= rSrcSect
.GetTOXBase();
1158 const SwTOXBase
* pDstTOX
= rDstSect
.GetTOXBase();
1159 bRet
= pSrcTOX
&& pDstTOX
1160 && pSrcTOX
->GetType() == pDstTOX
->GetType()
1161 && pSrcTOX
->GetTitle() == pDstTOX
->GetTitle()
1162 && pSrcTOX
->GetTypeName() == pDstTOX
->GetTypeName()
1167 case SectionType::DdeLink
:
1168 case SectionType::FileLink
:
1169 bRet
= eSrcSectType
== eDstSectType
&&
1170 rSrcSect
.GetLinkFileName() ==
1171 rDstSect
.GetLinkFileName();
1177 case SwNodeType::End
:
1178 bRet
= rSrcNd
.StartOfSectionNode()->GetNodeType() ==
1179 rDstNd
.StartOfSectionNode()->GetNodeType();
1181 // --> #i107826#: compare actual table content
1182 if (bRet
&& rSrcNd
.StartOfSectionNode()->GetNodeType() == SwNodeType::Table
)
1185 *rSrcNd
.StartOfSectionNode(), *rDstNd
.StartOfSectionNode());
1195 OUString
SwCompareLine::GetText() const
1198 switch( m_pNode
->GetNodeType() )
1200 case SwNodeType::Text
:
1201 sRet
= m_pNode
->GetTextNode()->GetExpandText(nullptr);
1204 case SwNodeType::Table
:
1206 sRet
= "Tabelle: " + SimpleTableToText(*m_pNode
);
1210 case SwNodeType::Section
:
1212 sRet
= "Section - Node:";
1214 const SwSectionNode
& rSNd
= static_cast<const SwSectionNode
&>(*m_pNode
);
1215 const SwSection
& rSect
= rSNd
.GetSection();
1216 switch( rSect
.GetType() )
1218 case SectionType::Content
:
1219 if( rSect
.IsProtect() )
1220 sRet
+= OUString::number(
1221 sal_Int32(rSNd
.EndOfSectionIndex() - rSNd
.GetIndex()) );
1224 case SectionType::ToxHeader
:
1225 case SectionType::ToxContent
:
1227 const SwTOXBase
* pTOX
= rSect
.GetTOXBase();
1229 sRet
+= pTOX
->GetTitle() + pTOX
->GetTypeName() +
1230 OUString::number(pTOX
->GetType());
1234 case SectionType::DdeLink
:
1235 case SectionType::FileLink
:
1236 sRet
+= rSect
.GetLinkFileName();
1242 case SwNodeType::Grf
:
1243 sRet
= "Grafik - Node:";
1245 case SwNodeType::Ole
:
1246 sRet
= "OLE - Node:";
1253 sal_uLong
SwCompareLine::GetTextNodeHashValue( const SwTextNode
& rNd
, sal_uLong nVal
)
1255 OUString
sStr( rNd
.GetExpandText(nullptr) );
1256 for( sal_Int32 n
= 0; n
< sStr
.getLength(); ++n
)
1257 nVal
= (nVal
<< 1 ) + sStr
[ n
];
1261 bool SwCompareLine::CompareTextNd( const SwTextNode
& rDstNd
,
1262 const SwTextNode
& rSrcNd
)
1265 // Very simple at first
1266 if( rDstNd
.GetText() == rSrcNd
.GetText() )
1268 // The text is the same, but are the "special attributes" (0xFF) also the same?
1274 bool SwCompareLine::ChangesInLine( const SwCompareLine
& rLine
,
1275 std::unique_ptr
<SwPaM
>& rpInsRing
, std::unique_ptr
<SwPaM
>& rpDelRing
) const
1279 // Only compare textnodes
1280 if( SwNodeType::Text
== m_pNode
->GetNodeType() &&
1281 SwNodeType::Text
== rLine
.GetNode().GetNodeType() )
1283 SwTextNode
& rDstNd
= *const_cast<SwTextNode
*>(m_pNode
->GetTextNode());
1284 const SwTextNode
& rSrcNd
= *rLine
.GetNode().GetTextNode();
1285 SwDoc
& rDstDoc
= rDstNd
.GetDoc();
1289 int nDstLen
= rDstNd
.GetText().getLength();
1290 int nSrcLen
= rSrcNd
.GetText().getLength();
1292 int nMinLen
= std::min( nDstLen
, nSrcLen
);
1293 int nAvgLen
= ( nDstLen
+ nSrcLen
)/2;
1295 std::vector
<int> aLcsDst( nMinLen
+ 1 );
1296 std::vector
<int> aLcsSrc( nMinLen
+ 1 );
1298 if( CmpOptions
.eCmpMode
== SwCompareMode::ByWord
)
1300 std::vector
<int> aTmpLcsDst( nMinLen
+ 1 );
1301 std::vector
<int> aTmpLcsSrc( nMinLen
+ 1 );
1303 WordArrayComparator
aCmp( &rDstNd
, &rSrcNd
);
1305 LgstCommonSubseq
aSeq( aCmp
);
1307 nLcsLen
= aSeq
.Find( aTmpLcsDst
.data(), aTmpLcsSrc
.data() );
1309 if( CmpOptions
.nIgnoreLen
)
1311 nLcsLen
= CommonSubseq::IgnoreIsolatedPieces( aTmpLcsDst
.data(), aTmpLcsSrc
.data(),
1312 aCmp
.GetLen1(), aCmp
.GetLen2(),
1313 nLcsLen
, CmpOptions
.nIgnoreLen
);
1316 nLcsLen
= aCmp
.GetCharSequence( aTmpLcsDst
.data(), aTmpLcsSrc
.data(),
1317 aLcsDst
.data(), aLcsSrc
.data(), nLcsLen
);
1321 CharArrayComparator
aCmp( &rDstNd
, &rSrcNd
);
1322 LgstCommonSubseq
aSeq( aCmp
);
1324 nLcsLen
= aSeq
.Find( aLcsDst
.data(), aLcsSrc
.data() );
1326 if( CmpOptions
.nIgnoreLen
)
1328 nLcsLen
= CommonSubseq::IgnoreIsolatedPieces( aLcsDst
.data(), aLcsSrc
.data(), nDstLen
,
1330 CmpOptions
.nIgnoreLen
);
1334 // find the sum of the squares of the continuous substrings
1337 for( int i
= 0; i
< nLcsLen
; i
++ )
1339 if( i
!= nLcsLen
- 1 && aLcsDst
[i
] + 1 == aLcsDst
[i
+ 1]
1340 && aLcsSrc
[i
] + 1 == aLcsSrc
[i
+ 1] )
1346 nSqSum
+= nCnt
*nCnt
;
1351 // Don't compare if there aren't enough similarities
1352 if ( nAvgLen
>= 8 && nSqSum
*32 < nAvgLen
*nAvgLen
)
1357 // Show the differences
1359 for( int i
= 0; i
<= nLcsLen
; i
++ )
1361 int nDstFrom
= i
? (aLcsDst
[i
- 1] + 1) : 0;
1362 int nDstTo
= ( i
== nLcsLen
) ? nDstLen
: aLcsDst
[i
];
1363 int nSrcFrom
= i
? (aLcsSrc
[i
- 1] + 1) : 0;
1364 int nSrcTo
= ( i
== nLcsLen
) ? nSrcLen
: aLcsSrc
[i
];
1366 SwPaM
aPam( rDstNd
, nDstTo
+ nSkip
);
1368 if ( nDstFrom
< nDstTo
)
1370 SwPaM
* pTmp
= new SwPaM( *aPam
.GetPoint(), rpInsRing
.get() );
1372 rpInsRing
.reset(pTmp
);
1374 pTmp
->GetMark()->SetContent(nDstFrom
+ nSkip
);
1377 if ( nSrcFrom
< nSrcTo
)
1379 bool bUndo
= rDstDoc
.GetIDocumentUndoRedo().DoesUndo();
1380 rDstDoc
.GetIDocumentUndoRedo().DoUndo( false );
1381 SwPaM
aCpyPam( rSrcNd
, nSrcFrom
);
1383 aCpyPam
.GetPoint()->SetContent(nSrcTo
);
1384 aCpyPam
.GetDoc().getIDocumentContentOperations().CopyRange( aCpyPam
, *aPam
.GetPoint(),
1385 SwCopyFlags::CheckPosInFly
);
1386 rDstDoc
.GetIDocumentUndoRedo().DoUndo( bUndo
);
1388 SwPaM
* pTmp
= new SwPaM( *aPam
.GetPoint(), rpDelRing
.get() );
1390 rpDelRing
.reset(pTmp
);
1393 pTmp
->GetMark()->SetContent(nDstTo
+ nSkip
);
1394 nSkip
+= nSrcTo
- nSrcFrom
;
1398 SwPaM
* pCorr
= rpInsRing
->GetPrev();
1399 if( *pCorr
->GetPoint() == *pTmp
->GetPoint() )
1400 *pCorr
->GetPoint() = *pTmp
->GetMark();
1411 SwNodeOffset
CompareData::NextIdx( const SwNode
* pNd
)
1413 if( pNd
->IsStartNode() )
1415 if( pNd
->IsTableNode() )
1416 pNd
= pNd
->EndOfSectionNode();
1419 const SwSectionNode
* pSNd
= pNd
->GetSectionNode();
1421 ( SectionType::Content
!= pSNd
->GetSection().GetType() ||
1422 pSNd
->GetSection().IsProtect() ) )
1423 pNd
= pNd
->EndOfSectionNode();
1426 return pNd
->GetIndex() + 1;
1429 SwNodeOffset
CompareData::PrevIdx( const SwNode
* pNd
)
1431 if( pNd
->IsEndNode() )
1433 if( pNd
->StartOfSectionNode()->IsTableNode() )
1434 pNd
= pNd
->StartOfSectionNode();
1437 const SwSectionNode
* pSNd
= pNd
->StartOfSectionNode()->GetSectionNode();
1439 ( SectionType::Content
!= pSNd
->GetSection().GetType() ||
1440 pSNd
->GetSection().IsProtect() ) )
1441 pNd
= pNd
->StartOfSectionNode();
1444 return pNd
->GetIndex() - 1;
1447 void CompareData::CheckRanges( CompareData
& rData
)
1449 const SwNodes
& rSrcNds
= rData
.m_rDoc
.GetNodes();
1450 const SwNodes
& rDstNds
= m_rDoc
.GetNodes();
1452 const SwNode
& rSrcEndNd
= rData
.GetEndOfContent();
1453 const SwNode
& rDstEndNd
= GetEndOfContent();
1455 SwNodeOffset nSrcSttIdx
= NextIdx( rSrcEndNd
.StartOfSectionNode() );
1456 SwNodeOffset nSrcEndIdx
= rSrcEndNd
.GetIndex();
1458 SwNodeOffset nDstSttIdx
= NextIdx( rDstEndNd
.StartOfSectionNode() );
1459 SwNodeOffset nDstEndIdx
= rDstEndNd
.GetIndex();
1461 while( nSrcSttIdx
< nSrcEndIdx
&& nDstSttIdx
< nDstEndIdx
)
1463 const SwNode
* pSrcNd
= rSrcNds
[ nSrcSttIdx
];
1464 const SwNode
* pDstNd
= rDstNds
[ nDstSttIdx
];
1465 if( !SwCompareLine::CompareNode( *pSrcNd
, *pDstNd
))
1468 nSrcSttIdx
= NextIdx( pSrcNd
);
1469 nDstSttIdx
= NextIdx( pDstNd
);
1472 nSrcEndIdx
= PrevIdx( &rSrcEndNd
);
1473 nDstEndIdx
= PrevIdx( &rDstEndNd
);
1474 while( nSrcSttIdx
< nSrcEndIdx
&& nDstSttIdx
< nDstEndIdx
)
1476 const SwNode
* pSrcNd
= rSrcNds
[ nSrcEndIdx
];
1477 const SwNode
* pDstNd
= rDstNds
[ nDstEndIdx
];
1478 if( !SwCompareLine::CompareNode( *pSrcNd
, *pDstNd
))
1481 nSrcEndIdx
= PrevIdx( pSrcNd
);
1482 nDstEndIdx
= PrevIdx( pDstNd
);
1485 while( nSrcSttIdx
<= nSrcEndIdx
)
1487 const SwNode
* pNd
= rSrcNds
[ nSrcSttIdx
];
1488 rData
.InsertLine( SwCompareLine( *pNd
) );
1489 nSrcSttIdx
= NextIdx( pNd
);
1492 while( nDstSttIdx
<= nDstEndIdx
)
1494 const SwNode
* pNd
= rDstNds
[ nDstSttIdx
];
1495 InsertLine( SwCompareLine( *pNd
) );
1496 nDstSttIdx
= NextIdx( pNd
);
1500 void CompareData::ShowInsert( sal_uLong nStt
, sal_uLong nEnd
)
1502 SwPaM
* pTmp
= new SwPaM( GetLine( nStt
).GetNode(), 0,
1503 GetLine( nEnd
-1 ).GetEndNode(), 0,
1504 m_pInsertRing
.get() );
1505 if( !m_pInsertRing
)
1506 m_pInsertRing
.reset( pTmp
);
1508 // #i65201#: These SwPaMs are calculated smaller than needed, see comment below
1511 void CompareData::ShowDelete(
1512 const CompareData
& rData
,
1518 rData
.GetLine( nStt
).GetNode(), SwNodeOffset(0),
1519 rData
.GetLine( nEnd
-1 ).GetEndNode(), SwNodeOffset(1) );
1521 SwNodeOffset
nOffset(0);
1522 std::optional
<SwCompareLine
> xLine
;
1525 if( GetLineCount() == nInsPos
)
1527 xLine
= GetLine( nInsPos
-1 );
1528 nOffset
= SwNodeOffset(1);
1531 xLine
= GetLine( nInsPos
);
1534 const SwNode
* pLineNd
;
1538 pLineNd
= &xLine
->GetEndNode();
1540 pLineNd
= &xLine
->GetNode();
1544 pLineNd
= &GetEndOfContent();
1545 nOffset
= SwNodeOffset(0);
1548 SwNodeIndex
aInsPos( *pLineNd
, nOffset
);
1549 SwNodeIndex
aSavePos( aInsPos
, -1 );
1551 rData
.m_rDoc
.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg
, aInsPos
.GetNode());
1552 m_rDoc
.getIDocumentState().SetModified();
1555 // #i65201#: These SwPaMs are calculated when the (old) delete-redlines are hidden,
1556 // they will be inserted when the delete-redlines are shown again.
1557 // To avoid unwanted insertions of delete-redlines into these new redlines, what happens
1558 // especially at the end of the document, I reduce the SwPaM by one node.
1559 // Before the new redlines are inserted, they have to expand again.
1560 SwPaM
* pTmp
= new SwPaM( aSavePos
.GetNode(), aInsPos
.GetNode(), SwNodeOffset(0), SwNodeOffset(-1), m_pDelRing
.get() );
1562 m_pDelRing
.reset(pTmp
);
1566 SwPaM
* pCorr
= m_pInsertRing
->GetPrev();
1567 if( *pCorr
->GetPoint() == *pTmp
->GetPoint() )
1569 SwNodeIndex
aTmpPos( pTmp
->GetMark()->GetNode(), -1 );
1570 pCorr
->GetPoint()->Assign( aTmpPos
);
1575 void CompareData::CheckForChangesInLine( const CompareData
& rData
,
1576 sal_uLong nStt
, sal_uLong nEnd
,
1577 sal_uLong nThisStt
, sal_uLong nThisEnd
)
1579 LineArrayComparator
aCmp( *this, rData
, nThisStt
, nThisEnd
,
1582 int nMinLen
= std::min( aCmp
.GetLen1(), aCmp
.GetLen2() );
1583 std::unique_ptr
<int[]> pLcsDst(new int[ nMinLen
]);
1584 std::unique_ptr
<int[]> pLcsSrc(new int[ nMinLen
]);
1586 FastCommonSubseq
subseq( aCmp
);
1587 int nLcsLen
= subseq
.Find( pLcsDst
.get(), pLcsSrc
.get() );
1588 for (int i
= 0; i
<= nLcsLen
; i
++)
1590 // Beginning of inserted lines (inclusive)
1591 int nDstFrom
= i
? pLcsDst
[i
- 1] + 1 : 0;
1592 // End of inserted lines (exclusive)
1593 int nDstTo
= ( i
== nLcsLen
) ? aCmp
.GetLen1() : pLcsDst
[i
];
1594 // Beginning of deleted lines (inclusive)
1595 int nSrcFrom
= i
? pLcsSrc
[i
- 1] + 1 : 0;
1596 // End of deleted lines (exclusive)
1597 int nSrcTo
= ( i
== nLcsLen
) ? aCmp
.GetLen2() : pLcsSrc
[i
];
1601 const SwCompareLine aDstLn
= GetLine( nThisStt
+ nDstFrom
- 1 );
1602 const SwCompareLine aSrcLn
= rData
.GetLine( nStt
+ nSrcFrom
- 1 );
1604 // Show differences in detail for lines that
1605 // were matched as only slightly different
1606 if( !aDstLn
.ChangesInLine( aSrcLn
, m_pInsertRing
, m_pDelRing
) )
1608 ShowInsert( nThisStt
+ nDstFrom
- 1, nThisStt
+ nDstFrom
);
1609 ShowDelete( rData
, nStt
+ nSrcFrom
- 1, nStt
+ nSrcFrom
,
1610 nThisStt
+ nDstFrom
);
1614 // Lines missing from source are inserted
1615 if( nDstFrom
!= nDstTo
)
1617 ShowInsert( nThisStt
+ nDstFrom
, nThisStt
+ nDstTo
);
1620 // Lines missing from destination are deleted
1621 if( nSrcFrom
!= nSrcTo
)
1623 ShowDelete( rData
, nStt
+ nSrcFrom
, nStt
+ nSrcTo
, nThisStt
+ nDstTo
);
1628 void CompareData::SetRedlinesToDoc( bool bUseDocInfo
)
1630 SwPaM
* pTmp
= m_pDelRing
.get();
1632 // get the Author / TimeStamp from the "other" document info
1633 std::size_t nAuthor
= m_rDoc
.getIDocumentRedlineAccess().GetRedlineAuthor();
1634 DateTime
aTimeStamp( DateTime::SYSTEM
);
1635 SwDocShell
*pDocShell(m_rDoc
.GetDocShell());
1636 OSL_ENSURE(pDocShell
, "no SwDocShell");
1638 uno::Reference
<document::XDocumentPropertiesSupplier
> xDPS(
1639 pDocShell
->GetModel(), uno::UNO_QUERY_THROW
);
1640 uno::Reference
<document::XDocumentProperties
> xDocProps(
1641 xDPS
->getDocumentProperties());
1642 OSL_ENSURE(xDocProps
.is(), "Doc has no DocumentProperties");
1644 if( bUseDocInfo
&& xDocProps
.is() ) {
1645 OUString
aTmp( 1 == xDocProps
->getEditingCycles()
1646 ? xDocProps
->getAuthor()
1647 : xDocProps
->getModifiedBy() );
1648 util::DateTime
uDT( 1 == xDocProps
->getEditingCycles()
1649 ? xDocProps
->getCreationDate()
1650 : xDocProps
->getModificationDate() );
1652 if( !aTmp
.isEmpty() )
1654 nAuthor
= m_rDoc
.getIDocumentRedlineAccess().InsertRedlineAuthor( aTmp
);
1655 aTimeStamp
= DateTime(uDT
);
1662 SwRedlineData
aRedlnData( RedlineType::Delete
, nAuthor
, aTimeStamp
,
1663 OUString(), nullptr );
1665 // #i65201#: Expand again, see comment above.
1666 if( pTmp
->GetPoint()->GetContentIndex() == 0 )
1668 pTmp
->GetPoint()->Adjust(SwNodeOffset(1));
1671 // prevent redlines that end on structural end node
1672 if (& GetEndOfContent() ==
1673 & pTmp
->GetPoint()->GetNode())
1675 pTmp
->GetPoint()->Adjust(SwNodeOffset(-1));
1676 SwContentNode
*const pContentNode( pTmp
->GetPointContentNode() );
1678 pTmp
->GetPoint()->SetContent( pContentNode
->Len() );
1679 // tdf#106218 try to avoid losing a paragraph break here:
1680 if (pTmp
->GetMark()->GetContentIndex() == 0)
1682 SwNodeIndex
const prev(pTmp
->GetMark()->GetNode(), -1);
1683 if (prev
.GetNode().IsTextNode())
1685 pTmp
->GetMark()->Assign(
1686 *prev
.GetNode().GetTextNode(),
1687 prev
.GetNode().GetTextNode()->Len());
1692 m_rDoc
.getIDocumentRedlineAccess().DeleteRedline( *pTmp
, false, RedlineType::Any
);
1694 if (m_rDoc
.GetIDocumentUndoRedo().DoesUndo())
1696 m_rDoc
.GetIDocumentUndoRedo().AppendUndo(
1697 std::make_unique
<SwUndoCompDoc
>( *pTmp
, false ));
1699 m_rDoc
.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( aRedlnData
, *pTmp
), true );
1701 } while( m_pDelRing
.get() != ( pTmp
= pTmp
->GetNext()) );
1704 pTmp
= m_pInsertRing
.get();
1709 if( pTmp
->GetPoint()->GetContentIndex() == 0 )
1711 pTmp
->GetPoint()->Adjust(SwNodeOffset(1));
1714 // prevent redlines that end on structural end node
1715 if (& GetEndOfContent() ==
1716 & pTmp
->GetPoint()->GetNode())
1718 pTmp
->GetPoint()->Adjust(SwNodeOffset(-1));
1719 SwContentNode
*const pContentNode( pTmp
->GetPointContentNode() );
1721 pTmp
->GetPoint()->SetContent( pContentNode
->Len() );
1722 // tdf#106218 try to avoid losing a paragraph break here:
1723 if (pTmp
->GetMark()->GetContentIndex() == 0)
1725 SwNodeIndex
const prev(pTmp
->GetMark()->GetNode(), -1);
1726 if (prev
.GetNode().IsTextNode())
1728 pTmp
->GetMark()->Assign(
1729 *prev
.GetNode().GetTextNode(),
1730 prev
.GetNode().GetTextNode()->Len());
1734 } while( m_pInsertRing
.get() != ( pTmp
= pTmp
->GetNext()) );
1735 SwRedlineData
aRedlnData( RedlineType::Insert
, nAuthor
, aTimeStamp
,
1736 OUString(), nullptr );
1738 // combine consecutive
1739 if( pTmp
->GetNext() != m_pInsertRing
.get() )
1742 // coverity[deref_arg] - the SwPaM delete moves a new entry into GetNext()
1743 SwPosition
& rSttEnd
= *pTmp
->End(),
1744 & rEndStt
= *pTmp
->GetNext()->Start();
1745 const SwContentNode
* pCNd
;
1746 if( rSttEnd
== rEndStt
||
1747 (!rEndStt
.GetContentIndex() &&
1748 rEndStt
.GetNodeIndex() - 1 == rSttEnd
.GetNodeIndex() &&
1749 nullptr != ( pCNd
= rSttEnd
.GetNode().GetContentNode() ) &&
1750 rSttEnd
.GetContentIndex() == pCNd
->Len()))
1752 if( pTmp
->GetNext() == m_pInsertRing
.get() )
1754 // are consecutive, so combine
1755 rEndStt
= *pTmp
->Start();
1757 pTmp
= m_pInsertRing
.get();
1761 // are consecutive, so combine
1762 rSttEnd
= *pTmp
->GetNext()->End();
1763 delete pTmp
->GetNext();
1767 pTmp
= pTmp
->GetNext();
1768 } while( m_pInsertRing
.get() != pTmp
);
1772 // coverity[deref_arg] - pTmp is valid here
1773 if (IDocumentRedlineAccess::AppendResult::APPENDED
==
1774 m_rDoc
.getIDocumentRedlineAccess().AppendRedline(
1775 new SwRangeRedline(aRedlnData
, *pTmp
), true) &&
1776 m_rDoc
.GetIDocumentUndoRedo().DoesUndo())
1778 m_rDoc
.GetIDocumentUndoRedo().AppendUndo(
1779 std::make_unique
<SwUndoCompDoc
>( *pTmp
, true ));
1781 } while( m_pInsertRing
.get() != ( pTmp
= pTmp
->GetNext()) );
1784 typedef std::shared_ptr
<CompareData
> CompareDataPtr
;
1785 typedef std::pair
<CompareDataPtr
, CompareDataPtr
> CompareDataPtrPair
;
1786 typedef std::vector
<CompareDataPtrPair
> Comparators
;
1790 Comparators
buildComparators(SwDoc
&rSrcDoc
, SwDoc
&rDestDoc
)
1792 Comparators aComparisons
;
1794 aComparisons
.emplace_back(std::make_shared
<CompareMainText
>(rSrcDoc
, true),
1795 std::make_shared
<CompareMainText
>(rDestDoc
, true));
1797 //if we have the same number of frames then try to compare within them
1798 const sw::SpzFrameFormats
* pSrcFrameFormats
= rSrcDoc
.GetSpzFrameFormats();
1799 const sw::SpzFrameFormats
* pDestFrameFormats
= rDestDoc
.GetSpzFrameFormats();
1800 if (pSrcFrameFormats
->size() == pDestFrameFormats
->size())
1802 for(sw::FrameFormats
<sw::SpzFrameFormat
*>::size_type i
= 0; i
< pSrcFrameFormats
->size(); ++i
)
1804 const sw::SpzFrameFormat
& rSrcFormat
= *(*pSrcFrameFormats
)[i
];
1805 const sw::SpzFrameFormat
& rDestFormat
= *(*pDestFrameFormats
)[i
];
1806 const SwNodeIndex
* pSrcIdx
= rSrcFormat
.GetContent().GetContentIdx();
1807 const SwNodeIndex
* pDestIdx
= rDestFormat
.GetContent().GetContentIdx();
1808 if (!pSrcIdx
&& !pDestIdx
)
1810 if (!pSrcIdx
|| !pDestIdx
)
1812 const SwNode
* pSrcNode
= pSrcIdx
->GetNode().EndOfSectionNode();
1813 const SwNode
* pDestNode
= pDestIdx
->GetNode().EndOfSectionNode();
1814 if (!pSrcNode
&& !pDestNode
)
1816 if (!pSrcNode
|| !pDestNode
)
1818 if (pSrcIdx
->GetNodes()[pSrcIdx
->GetIndex() + 1]->IsNoTextNode()
1819 || pDestIdx
->GetNodes()[pDestIdx
->GetIndex() + 1]->IsNoTextNode())
1821 continue; // tdf#125660 don't redline GrfNode/OLENode
1823 aComparisons
.emplace_back(std::make_shared
<CompareFrameFormatText
>(rSrcDoc
, *pSrcIdx
),
1824 std::make_shared
<CompareFrameFormatText
>(rDestDoc
, *pDestIdx
));
1827 return aComparisons
;
1831 // Returns (the difference count?) if something is different
1832 tools::Long
SwDoc::CompareDoc( const SwDoc
& rDoc
)
1837 tools::Long nRet
= 0;
1839 // Get comparison options
1840 CmpOptions
.eCmpMode
= SW_MOD()->GetCompareMode();
1841 if( CmpOptions
.eCmpMode
== SwCompareMode::Auto
)
1843 if( getRsidRoot() == rDoc
.getRsidRoot() )
1845 CmpOptions
.eCmpMode
= SwCompareMode::ByChar
;
1846 CmpOptions
.bUseRsid
= true;
1847 CmpOptions
.nIgnoreLen
= 2;
1851 CmpOptions
.eCmpMode
= SwCompareMode::ByWord
;
1852 CmpOptions
.bUseRsid
= false;
1853 CmpOptions
.nIgnoreLen
= 3;
1858 CmpOptions
.bUseRsid
= getRsidRoot() == rDoc
.getRsidRoot() && SW_MOD()->IsUseRsid();
1859 CmpOptions
.nIgnoreLen
= SW_MOD()->IsIgnorePieces() ? SW_MOD()->GetPieceLen() : 0;
1862 GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY
, nullptr);
1863 bool bDocWasModified
= getIDocumentState().IsModified();
1864 SwDoc
& rSrcDoc
= const_cast<SwDoc
&>(rDoc
);
1865 bool bSrcModified
= rSrcDoc
.getIDocumentState().IsModified();
1867 RedlineFlags eSrcRedlMode
= rSrcDoc
.getIDocumentRedlineAccess().GetRedlineFlags();
1868 rSrcDoc
.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowInsert
);
1869 getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On
| RedlineFlags::ShowInsert
);
1871 Comparators
aComparisons(buildComparators(rSrcDoc
, *this));
1873 for (auto& a
: aComparisons
)
1875 CompareData
& rD0
= *a
.first
;
1876 CompareData
& rD1
= *a
.second
;
1877 rD1
.CompareLines( rD0
);
1878 nRet
|= rD1
.ShowDiffs( rD0
);
1883 getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On
|
1884 RedlineFlags::ShowInsert
| RedlineFlags::ShowDelete
);
1886 for (auto& a
: aComparisons
)
1888 CompareData
& rD1
= *a
.second
;
1889 rD1
.SetRedlinesToDoc( !bDocWasModified
);
1891 getIDocumentState().SetModified();
1894 rSrcDoc
.getIDocumentRedlineAccess().SetRedlineFlags( eSrcRedlMode
);
1895 getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert
| RedlineFlags::ShowDelete
);
1898 rSrcDoc
.getIDocumentState().ResetModified();
1900 GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY
, nullptr);
1907 struct SaveMergeRedline
1909 const SwRangeRedline
* pSrcRedl
;
1910 SwRangeRedline
* pDestRedl
;
1911 SaveMergeRedline( const SwNode
& rDstNd
, const SwRangeRedline
& rSrcRedl
);
1912 sal_uInt16
InsertRedline(SwPaM
* pLastDestRedline
);
1916 SaveMergeRedline::SaveMergeRedline( const SwNode
& rDstNd
,
1917 const SwRangeRedline
& rSrcRedl
)
1918 : pSrcRedl( &rSrcRedl
)
1920 SwPosition
aPos( rDstNd
);
1922 const SwPosition
* pStt
= rSrcRedl
.Start();
1923 if( rDstNd
.IsContentNode() )
1924 aPos
.SetContent( pStt
->GetContentIndex() );
1925 pDestRedl
= new SwRangeRedline( rSrcRedl
.GetRedlineData(), aPos
);
1927 if( RedlineType::Delete
!= pDestRedl
->GetType() )
1930 // mark the area as deleted
1931 const SwPosition
* pEnd
= rSrcRedl
.End();
1933 pDestRedl
->SetMark();
1934 pDestRedl
->GetPoint()->Adjust( pEnd
->GetNodeIndex() -
1935 pStt
->GetNodeIndex() );
1936 if( pDestRedl
->GetPointContentNode() )
1937 pDestRedl
->GetPoint()->SetContent( pEnd
->GetContentIndex() );
1940 sal_uInt16
SaveMergeRedline::InsertRedline(SwPaM
* pLastDestRedline
)
1942 sal_uInt16 nIns
= 0;
1943 SwDoc
& rDoc
= pDestRedl
->GetDoc();
1945 if( RedlineType::Insert
== pDestRedl
->GetType() )
1947 // the part was inserted so copy it from the SourceDoc
1948 ::sw::UndoGuard
const undoGuard(rDoc
.GetIDocumentUndoRedo());
1950 SwNodeIndex
aSaveNd( pDestRedl
->GetPoint()->GetNode(), -1 );
1951 const sal_Int32 nSaveCnt
= pDestRedl
->GetPoint()->GetContentIndex();
1953 RedlineFlags eOld
= rDoc
.getIDocumentRedlineAccess().GetRedlineFlags();
1954 rDoc
.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld
| RedlineFlags::Ignore
);
1956 pSrcRedl
->GetDoc().getIDocumentContentOperations().CopyRange(
1957 *const_cast<SwPaM
*>(static_cast<const SwPaM
*>(pSrcRedl
)),
1958 *pDestRedl
->GetPoint(), SwCopyFlags::CheckPosInFly
);
1960 rDoc
.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld
);
1962 pDestRedl
->SetMark();
1964 pDestRedl
->GetMark()->Assign( aSaveNd
, nSaveCnt
);
1966 if( pLastDestRedline
&& *pLastDestRedline
->GetPoint() == *pDestRedl
->GetPoint() )
1967 *pLastDestRedline
->GetPoint() = *pDestRedl
->GetMark();
1971 //JP 21.09.98: Bug 55909
1972 // If there already is a deleted or inserted one at the same position, we have to split it!
1973 SwPosition
* pDStt
= pDestRedl
->GetMark(),
1974 * pDEnd
= pDestRedl
->GetPoint();
1975 SwRedlineTable::size_type n
= 0;
1977 // find the first redline for StartPos
1978 if( !rDoc
.getIDocumentRedlineAccess().GetRedline( *pDStt
, &n
) && n
)
1981 const SwRedlineTable
& rRedlineTable
= rDoc
.getIDocumentRedlineAccess().GetRedlineTable();
1982 for( ; n
< rRedlineTable
.size(); ++n
)
1984 SwRangeRedline
* pRedl
= rRedlineTable
[ n
];
1985 SwPosition
* pRStt
= pRedl
->Start(),
1986 * pREnd
= pRedl
->End();
1987 if( RedlineType::Delete
== pRedl
->GetType() ||
1988 RedlineType::Insert
== pRedl
->GetType() )
1990 SwComparePosition eCmpPos
= ComparePosition( *pDStt
, *pDEnd
, *pRStt
, *pREnd
);
1993 case SwComparePosition::CollideStart
:
1994 case SwComparePosition::Behind
:
1997 case SwComparePosition::Inside
:
1998 case SwComparePosition::Equal
:
2000 pDestRedl
= nullptr;
2003 case SwComparePosition::CollideEnd
:
2004 case SwComparePosition::Before
:
2005 n
= rRedlineTable
.size();
2008 case SwComparePosition::Outside
:
2009 assert(pDestRedl
&& "is this actually impossible");
2012 SwRangeRedline
* pCpyRedl
= new SwRangeRedline(
2013 pDestRedl
->GetRedlineData(), *pDStt
);
2014 pCpyRedl
->SetMark();
2015 *pCpyRedl
->GetPoint() = *pRStt
;
2017 std::unique_ptr
<SwUndoCompDoc
> pUndo
;
2018 if (rDoc
.GetIDocumentUndoRedo().DoesUndo())
2019 pUndo
.reset(new SwUndoCompDoc( *pCpyRedl
));
2021 // now modify doc: append redline, undo (and count)
2022 rDoc
.getIDocumentRedlineAccess().AppendRedline( pCpyRedl
, true );
2025 rDoc
.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo
));
2031 // we should start over now
2032 n
= SwRedlineTable::npos
;
2036 case SwComparePosition::OverlapBefore
:
2040 case SwComparePosition::OverlapBehind
:
2045 else if( *pDEnd
<= *pRStt
)
2053 std::unique_ptr
<SwUndoCompDoc
> pUndo
;
2054 if (rDoc
.GetIDocumentUndoRedo().DoesUndo())
2055 pUndo
.reset(new SwUndoCompDoc( *pDestRedl
));
2057 // now modify doc: append redline, undo (and count)
2058 IDocumentRedlineAccess::AppendResult
const result(
2059 rDoc
.getIDocumentRedlineAccess().AppendRedline(pDestRedl
, true));
2062 rDoc
.GetIDocumentUndoRedo().AppendUndo( std::move(pUndo
) );
2066 // if AppendRedline has deleted our redline, we may not keep a
2068 if (IDocumentRedlineAccess::AppendResult::APPENDED
!= result
)
2069 pDestRedl
= nullptr;
2074 /// Merge two documents
2075 tools::Long
SwDoc::MergeDoc( const SwDoc
& rDoc
)
2080 tools::Long nRet
= 0;
2082 GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY
, nullptr);
2084 SwDoc
& rSrcDoc
= const_cast<SwDoc
&>(rDoc
);
2085 bool bSrcModified
= rSrcDoc
.getIDocumentState().IsModified();
2087 RedlineFlags eSrcRedlMode
= rSrcDoc
.getIDocumentRedlineAccess().GetRedlineFlags();
2088 rSrcDoc
.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowDelete
);
2089 getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowDelete
);
2091 CompareMainText
aD0(rSrcDoc
, false);
2092 CompareMainText
aD1(*this, false);
2093 aD1
.CompareLines( aD0
);
2094 if( !aD1
.HasDiffs( aD0
) )
2096 // we want to get all redlines from the SourceDoc
2098 // look for all insert redlines from the SourceDoc and determine their position in the DestDoc
2099 std::vector
<SaveMergeRedline
> vRedlines
;
2100 const SwRedlineTable
& rSrcRedlTable
= rSrcDoc
.getIDocumentRedlineAccess().GetRedlineTable();
2101 SwNodeOffset nEndOfExtra
= rSrcDoc
.GetNodes().GetEndOfExtras().GetIndex();
2102 SwNodeOffset nMyEndOfExtra
= GetNodes().GetEndOfExtras().GetIndex();
2103 for(const SwRangeRedline
* pRedl
: rSrcRedlTable
)
2105 SwNodeOffset nNd
= pRedl
->GetPoint()->GetNodeIndex();
2106 RedlineType eType
= pRedl
->GetType();
2107 if( nEndOfExtra
< nNd
&&
2108 ( RedlineType::Insert
== eType
|| RedlineType::Delete
== eType
))
2110 const SwNode
* pDstNd
= GetNodes()[
2111 nMyEndOfExtra
+ nNd
- nEndOfExtra
];
2113 // Found the position.
2114 // Then we also have to insert the redline to the line in the DestDoc.
2115 vRedlines
.emplace_back(*pDstNd
, *pRedl
);
2119 if( !vRedlines
.empty() )
2121 // Carry over all into DestDoc
2122 rSrcDoc
.getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert
| RedlineFlags::ShowDelete
);
2124 getIDocumentRedlineAccess().SetRedlineFlags(
2126 RedlineFlags::ShowInsert
|
2127 RedlineFlags::ShowDelete
);
2129 SwPaM
* pLastDestRedline(nullptr);
2130 for(SaveMergeRedline
& rRedline
: vRedlines
)
2132 nRet
+= rRedline
.InsertRedline(pLastDestRedline
);
2133 pLastDestRedline
= rRedline
.pDestRedl
;
2138 rSrcDoc
.getIDocumentRedlineAccess().SetRedlineFlags( eSrcRedlMode
);
2140 rSrcDoc
.getIDocumentState().ResetModified();
2142 getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert
| RedlineFlags::ShowDelete
);
2144 GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY
, nullptr);
2149 LineArrayComparator::LineArrayComparator( const CompareData
&rD1
,
2150 const CompareData
&rD2
, int nStt1
,
2151 int nEnd1
, int nStt2
, int nEnd2
)
2152 : m_rData1( rD1
), m_rData2( rD2
), m_nFirst1( nStt1
), m_nFirst2( nStt2
)
2154 m_nLen1
= nEnd1
- nStt1
;
2155 m_nLen2
= nEnd2
- nStt2
;
2158 bool LineArrayComparator::Compare( int nIdx1
, int nIdx2
) const
2160 if( nIdx1
< 0 || nIdx2
< 0 || nIdx1
>= m_nLen1
|| nIdx2
>= m_nLen2
)
2162 OSL_ENSURE( false, "Index out of range!" );
2166 const SwTextNode
*pTextNd1
= m_rData1
.GetLine( m_nFirst1
+ nIdx1
).GetNode().GetTextNode();
2167 const SwTextNode
*pTextNd2
= m_rData2
.GetLine( m_nFirst2
+ nIdx2
).GetNode().GetTextNode();
2169 if( !pTextNd1
|| !pTextNd2
2170 || ( CmpOptions
.bUseRsid
&& !pTextNd1
->CompareParRsid( *pTextNd2
) ) )
2175 const sal_Int32 nPar1Len
= pTextNd1
->Len();
2176 const sal_Int32 nPar2Len
= pTextNd2
->Len();
2178 if( std::min( nPar1Len
, nPar2Len
) * 3 < std::max( nPar1Len
, nPar2Len
) )
2183 sal_Int32 nBorderLen
= ( nPar1Len
+ nPar2Len
)/16;
2185 if( nBorderLen
< 3 )
2187 nBorderLen
= std::min
<sal_Int32
>( 3, std::min( nPar1Len
, nPar2Len
) );
2190 std::set
<unsigned> aHashes
;
2192 unsigned nMul
= 251;
2196 for( i
= 0; i
< nBorderLen
- 1; i
++ )
2200 for( i
= 0; i
< nBorderLen
; i
++ )
2202 nHash
= nHash
*nMul
+ pTextNd1
->GetText()[i
];
2204 aHashes
.insert( nHash
);
2205 for( ; i
< nPar1Len
; i
++ )
2207 nHash
= nHash
- nPow
*pTextNd1
->GetText()[ i
- nBorderLen
];
2208 nHash
= nHash
*nMul
+ pTextNd1
->GetText()[ i
];
2210 aHashes
.insert( nHash
);
2214 for( i
= 0; i
< nBorderLen
; i
++ )
2216 nHash
= nHash
*nMul
+ pTextNd2
->GetText()[ i
];
2219 if( aHashes
.find( nHash
) != aHashes
.end() )
2224 for( ; i
< nPar2Len
; i
++ )
2226 nHash
= nHash
- nPow
*pTextNd2
->GetText()[ i
- nBorderLen
];
2227 nHash
= nHash
*nMul
+ pTextNd2
->GetText()[ i
];
2228 if( aHashes
.find( nHash
) != aHashes
.end() )
2236 bool CharArrayComparator::Compare( int nIdx1
, int nIdx2
) const
2238 if( nIdx1
< 0 || nIdx2
< 0 || nIdx1
>= GetLen1() || nIdx2
>= GetLen2() )
2240 OSL_ENSURE( false, "Index out of range!" );
2244 return ( !CmpOptions
.bUseRsid
2245 || m_pTextNode1
->CompareRsid( *m_pTextNode2
, nIdx1
+ 1, nIdx2
+ 1 ) )
2246 && m_pTextNode1
->GetText()[ nIdx1
] == m_pTextNode2
->GetText()[ nIdx2
];
2249 WordArrayComparator::WordArrayComparator( const SwTextNode
*pNode1
,
2250 const SwTextNode
*pNode2
)
2251 : m_pTextNode1( pNode1
), m_pTextNode2( pNode2
)
2253 m_pPos1
.reset( new int[ m_pTextNode1
->GetText().getLength() + 1 ] );
2254 m_pPos2
.reset( new int[ m_pTextNode2
->GetText().getLength() + 1 ] );
2256 CalcPositions( m_pPos1
.get(), m_pTextNode1
, m_nCount1
);
2257 CalcPositions( m_pPos2
.get(), m_pTextNode2
, m_nCount2
);
2260 bool WordArrayComparator::Compare( int nIdx1
, int nIdx2
) const
2262 int nLen
= m_pPos1
[ nIdx1
+ 1 ] - m_pPos1
[ nIdx1
];
2263 if( nLen
!= m_pPos2
[ nIdx2
+ 1 ] - m_pPos2
[ nIdx2
] )
2267 for( int i
= 0; i
< nLen
; i
++)
2269 if( m_pTextNode1
->GetText()[ m_pPos1
[ nIdx1
] + i
]
2270 != m_pTextNode2
->GetText()[ m_pPos2
[ nIdx2
] + i
]
2271 || ( CmpOptions
.bUseRsid
&& !m_pTextNode1
->CompareRsid( *m_pTextNode2
,
2272 m_pPos1
[ nIdx1
] + i
, m_pPos2
[ nIdx2
] + i
) ) )
2280 int WordArrayComparator::GetCharSequence( const int *pWordLcs1
,
2281 const int *pWordLcs2
, int *pSubseq1
, int *pSubseq2
, int nLcsLen
)
2284 for( int i
= 0; i
< nLcsLen
; i
++ )
2286 // Check for hash collisions
2287 if( m_pPos1
[ pWordLcs1
[i
] + 1 ] - m_pPos1
[ pWordLcs1
[i
] ]
2288 != m_pPos2
[ pWordLcs2
[i
] + 1 ] - m_pPos2
[ pWordLcs2
[i
] ] )
2292 for( int j
= 0; j
< m_pPos1
[pWordLcs1
[i
]+1] - m_pPos1
[pWordLcs1
[i
]]; j
++)
2294 pSubseq1
[ nLen
] = m_pPos1
[ pWordLcs1
[i
] ] + j
;
2295 pSubseq2
[ nLen
] = m_pPos2
[ pWordLcs2
[i
] ] + j
;
2297 if( m_pTextNode1
->GetText()[ m_pPos1
[ pWordLcs1
[i
] ] + j
]
2298 != m_pTextNode2
->GetText()[ m_pPos2
[ pWordLcs2
[i
] ] + j
] )
2310 void WordArrayComparator::CalcPositions( int *pPos
, const SwTextNode
*pTextNd
,
2314 for (int i
= 0; i
<= pTextNd
->GetText().getLength(); ++i
)
2316 if (i
== 0 || i
== pTextNd
->GetText().getLength()
2317 || !rtl::isAsciiAlphanumeric( pTextNd
->GetText()[ i
- 1 ])
2318 || !rtl::isAsciiAlphanumeric( pTextNd
->GetText()[ i
]))
2326 int CommonSubseq::FindLCS( int *pLcs1
, int *pLcs2
, int nStt1
, int nEnd1
,
2327 int nStt2
, int nEnd2
)
2329 int nLen1
= nEnd1
? nEnd1
- nStt1
: m_rComparator
.GetLen1();
2330 int nLen2
= nEnd2
? nEnd2
- nStt2
: m_rComparator
.GetLen2();
2332 assert( nLen1
>= 0 );
2333 assert( nLen2
>= 0 );
2335 std::unique_ptr
<int*[]> pLcs( new int*[ nLen1
+ 1 ] );
2336 pLcs
[ 0 ] = m_pData
.get();
2338 for( int i
= 1; i
< nLen1
+ 1; i
++ )
2339 pLcs
[ i
] = pLcs
[ i
- 1 ] + nLen2
+ 1;
2341 for( int i
= 0; i
<= nLen1
; i
++ )
2344 for( int j
= 0; j
<= nLen2
; j
++ )
2348 for( int i
= 1; i
<= nLen1
; i
++ )
2350 for( int j
= 1; j
<= nLen2
; j
++ )
2352 if( m_rComparator
.Compare( nStt1
+ i
- 1, nStt2
+ j
- 1 ) )
2353 pLcs
[i
][j
] = pLcs
[i
- 1][j
- 1] + 1;
2355 pLcs
[i
][j
] = std::max( pLcs
[i
][j
- 1], pLcs
[i
- 1][j
] );
2359 int nLcsLen
= pLcs
[ nLen1
][ nLen2
];
2361 // Recover the lcs in the two sequences
2362 if( pLcs1
&& pLcs2
)
2366 int nIdx
= nLcsLen
- 1;
2368 while( nIdx1
> 0 && nIdx2
> 0 )
2370 if( pLcs
[ nIdx1
][ nIdx2
] == pLcs
[ nIdx1
- 1 ][ nIdx2
] )
2372 else if( pLcs
[ nIdx1
][ nIdx2
] == pLcs
[ nIdx1
][ nIdx2
- 1 ] )
2378 pLcs1
[ nIdx
] = nIdx1
+ nStt1
;
2379 pLcs2
[ nIdx
] = nIdx2
+ nStt2
;
2388 int CommonSubseq::IgnoreIsolatedPieces( int *pLcs1
, int *pLcs2
, int nLen1
,
2389 int nLen2
, int nLcsLen
, int nPieceLen
)
2398 // Don't ignore text at the beginning of the paragraphs
2399 if( pLcs1
[ 0 ] == 0 && pLcs2
[ 0 ] == 0 )
2401 while( nNext
< nLcsLen
- 1 && pLcs1
[ nNext
] + 1 == pLcs1
[ nNext
+ 1 ]
2402 && pLcs2
[ nNext
] + 1 == pLcs2
[ nNext
+ 1 ] )
2411 for( int i
= nNext
; i
< nLcsLen
; i
++ )
2413 if( i
!= nLcsLen
- 1 && pLcs1
[ i
] + 1 == pLcs1
[ i
+ 1 ]
2414 && pLcs2
[ i
] + 1 == pLcs2
[ i
+ 1 ] )
2420 if( nCnt
> nPieceLen
2421 // Don't ignore text at the end of the paragraphs
2422 || ( i
== nLcsLen
- 1
2423 && pLcs1
[i
] == nLen1
- 1 && pLcs2
[i
] == nLen2
- 1 ))
2425 for( int j
= i
+ 1 - nCnt
; j
<= i
; j
++ )
2427 pLcs2
[ nNext
] = pLcs2
[ j
];
2428 pLcs1
[ nNext
] = pLcs1
[ j
];
2439 LgstCommonSubseq::LgstCommonSubseq( ArrayComparator
&rComparator
)
2440 : CommonSubseq( rComparator
, CUTOFF
)
2442 m_pBuff1
.reset( new int[ rComparator
.GetLen2() + 1 ] );
2443 m_pBuff2
.reset( new int[ rComparator
.GetLen2() + 1 ] );
2445 m_pL1
.reset( new int[ rComparator
.GetLen2() + 1 ] );
2446 m_pL2
.reset( new int[ rComparator
.GetLen2() + 1 ] );
2449 void LgstCommonSubseq::FindL( int *pL
, int nStt1
, int nEnd1
,
2450 int nStt2
, int nEnd2
)
2452 int nLen1
= nEnd1
? nEnd1
- nStt1
: m_rComparator
.GetLen1();
2453 int nLen2
= nEnd2
? nEnd2
- nStt2
: m_rComparator
.GetLen2();
2455 int *currL
= m_pBuff1
.get();
2456 int *prevL
= m_pBuff2
.get();
2458 // Avoid memory corruption
2459 if( nLen2
> m_rComparator
.GetLen2() )
2465 memset( m_pBuff1
.get(), 0, sizeof( m_pBuff1
[0] ) * ( nLen2
+ 1 ) );
2466 memset( m_pBuff2
.get(), 0, sizeof( m_pBuff2
[0] ) * ( nLen2
+ 1 ) );
2469 for( int i
= 1; i
<= nLen1
; i
++ )
2471 for( int j
= 1; j
<= nLen2
; j
++ )
2473 if( m_rComparator
.Compare( nStt1
+ i
- 1, nStt2
+ j
- 1 ) )
2474 currL
[j
] = prevL
[j
- 1] + 1;
2476 currL
[j
] = std::max( currL
[j
- 1], prevL
[j
] );
2478 std::swap( currL
, prevL
);
2480 memcpy( pL
, prevL
, ( nLen2
+ 1 ) * sizeof( *prevL
) );
2483 int LgstCommonSubseq::HirschbergLCS( int *pLcs1
, int *pLcs2
, int nStt1
,
2484 int nEnd1
, int nStt2
, int nEnd2
)
2488 nLen1
= nEnd1
- nStt1
;
2489 nLen2
= nEnd2
- nStt2
;
2491 if( ( nLen1
+ 1 ) * ( nLen2
+ 1 ) <= CUTOFF
)
2493 if( !nLen1
|| !nLen2
)
2497 return FindLCS(pLcs1
, pLcs2
, nStt1
, nEnd1
, nStt2
, nEnd2
);
2502 FindL( m_pL1
.get(), nStt1
, nStt1
+ nMid
, nStt2
, nEnd2
);
2503 FindL( m_pL2
.get(), nStt1
+ nMid
, nEnd1
, nStt2
, nEnd2
);
2510 for( i
= 0; i
<= nLen2
; i
++ )
2512 if( m_pL1
[i
] + ( m_pL2
[nLen2
] - m_pL2
[i
] ) > nMaxVal
)
2515 nMaxVal
= m_pL1
[i
]+( m_pL2
[nLen2
] - m_pL2
[i
] );
2519 int nRet
= HirschbergLCS( pLcs1
, pLcs2
, nStt1
, nStt1
+ nMid
,
2520 nStt2
, nStt2
+ nMaxPos
);
2521 nRet
+= HirschbergLCS( pLcs1
+ nRet
, pLcs2
+ nRet
, nStt1
+ nMid
, nEnd1
,
2522 nStt2
+ nMaxPos
, nEnd2
);
2527 int LgstCommonSubseq::Find( int *pSubseq1
, int *pSubseq2
)
2531 int nEnd1
= m_rComparator
.GetLen1();
2532 int nEnd2
= m_rComparator
.GetLen2();
2534 // Check for corresponding lines in the beginning of the sequences
2535 while( nStt
< nEnd1
&& nStt
< nEnd2
&& m_rComparator
.Compare( nStt
, nStt
) )
2537 pSubseq1
[ nStt
] = nStt
;
2538 pSubseq2
[ nStt
] = nStt
;
2545 // Check for corresponding lines in the end of the sequences
2546 while( nStt
< nEnd1
&& nStt
< nEnd2
2547 && m_rComparator
.Compare( nEnd1
- 1, nEnd2
- 1 ) )
2554 int nLen
= HirschbergLCS( pSubseq1
, pSubseq2
, nStt
, nEnd1
, nStt
, nEnd2
);
2556 for( int i
= 0; i
< nCutEnd
; i
++ )
2558 pSubseq1
[ nLen
+ i
] = nEnd1
+ i
;
2559 pSubseq2
[ nLen
+ i
] = nEnd2
+ i
;
2562 return nStt
+ nLen
+ nCutEnd
;
2565 int FastCommonSubseq::FindFastCS( int *pSeq1
, int *pSeq2
, int nStt1
,
2566 int nEnd1
, int nStt2
, int nEnd2
)
2571 // Check for corresponding lines in the beginning of the sequences
2572 while( nStt1
< nEnd1
&& nStt2
< nEnd2
&& m_rComparator
.Compare( nStt1
, nStt2
) )
2574 pSeq1
[ nCutBeg
] = nStt1
++;
2575 pSeq2
[ nCutBeg
] = nStt2
++;
2582 // Check for corresponding lines in the end of the sequences
2583 while( nStt1
< nEnd1
&& nStt2
< nEnd2
2584 && m_rComparator
.Compare( nEnd1
- 1, nEnd2
- 1 ) )
2591 int nLen1
= nEnd1
- nStt1
;
2592 int nLen2
= nEnd2
- nStt2
;
2594 // Return if a sequence is empty
2595 if( nLen1
<= 0 || nLen2
<= 0 )
2597 for( int i
= 0; i
< nCutEnd
; i
++ )
2599 pSeq1
[ i
] = nEnd1
+ i
;
2600 pSeq2
[ i
] = nEnd2
+ i
;
2602 return nCutBeg
+ nCutEnd
;
2605 // Cut to LCS for small values
2606 if( nLen1
< 3 || nLen2
< 3 || ( nLen1
+ 1 ) * ( nLen2
+ 1 ) <= CUTOFF
)
2608 int nLcsLen
= FindLCS( pSeq1
, pSeq2
, nStt1
, nEnd1
, nStt2
, nEnd2
);
2610 for( int i
= 0; i
< nCutEnd
; i
++ )
2612 pSeq1
[ nLcsLen
+ i
] = nEnd1
+ i
;
2613 pSeq2
[ nLcsLen
+ i
] = nEnd2
+ i
;
2615 return nCutBeg
+ nLcsLen
+ nCutEnd
;
2618 int nMid1
= nLen1
/2;
2619 int nMid2
= nLen2
/2;
2622 int nPos1
= -1, nPos2
= -1;
2624 // Find a point of correspondence in the middle of the sequences
2625 for( nRad
= 0; nRad
*nRad
< std::min( nMid1
, nMid2
); nRad
++ )
2627 // Search to the left and to the right of the middle of the first sequence
2628 for( int i
= nMid1
- nRad
; i
<= nMid1
+ nRad
; i
++ )
2630 if( m_rComparator
.Compare( nStt1
+ i
, nStt2
+ nMid2
- nRad
) )
2633 nPos2
= nStt2
+ nMid2
- nRad
;
2636 if( m_rComparator
.Compare( nStt1
+ i
, nStt2
+ nMid2
+ nRad
) )
2639 nPos2
= nStt2
+ nMid2
- nRad
;
2643 // Search to the left and to the right of the middle of the second sequence
2644 for( int i
= nMid2
- nRad
; i
<= nMid2
+ nRad
; i
++ )
2646 if( m_rComparator
.Compare( nStt2
+ nMid2
- nRad
, nStt2
+ i
) )
2649 nPos1
= nStt1
+ nMid1
- nRad
;
2652 if( m_rComparator
.Compare( nStt2
+ nMid2
- nRad
, nStt2
+ i
) )
2655 nPos1
= nStt1
+ nMid1
- nRad
;
2661 // return if no point of correspondence found
2664 for( int i
= 0; i
< nCutEnd
; i
++ )
2666 pSeq1
[ i
] = nEnd1
+ i
;
2667 pSeq2
[ i
] = nEnd2
+ i
;
2669 return nCutBeg
+ nCutEnd
;
2672 // Run the same on the sequences to the left of the correspondence point
2673 int nLen
= FindFastCS( pSeq1
, pSeq2
, nStt1
, nPos1
, nStt2
, nPos2
);
2675 pSeq1
[ nLen
] = nPos1
;
2676 pSeq2
[ nLen
] = nPos2
;
2678 // Run the same on the sequences to the right of the correspondence point
2679 nLen
+= FindFastCS( pSeq1
+ nLen
+ 1, pSeq2
+ nLen
+ 1,
2680 nPos1
+ 1, nEnd1
, nPos2
+ 1, nEnd2
) + 1;
2682 for( int i
= 0; i
< nCutEnd
; i
++ )
2684 pSeq1
[ nLen
+ i
] = nEnd1
+ i
;
2685 pSeq2
[ nLen
+ i
] = nEnd2
+ i
;
2688 return nLen
+ nCutBeg
+ nCutEnd
;
2691 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */