xref: /trunk/main/sw/source/core/doc/doccomp.cxx (revision a5b190bfa3e1bed4623e2958a8877664a3b5506c)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_sw.hxx"
30 
31 
32 #include <hintids.hxx>
33 #include <tools/list.hxx>
34 #include <vcl/vclenum.hxx>
35 #include <editeng/crsditem.hxx>
36 #include <editeng/colritem.hxx>
37 #include <editeng/boxitem.hxx>
38 #include <editeng/udlnitem.hxx>
39 #include <doc.hxx>
40 #include <IDocumentUndoRedo.hxx>
41 #include <docary.hxx>
42 #include <pam.hxx>
43 #include <ndtxt.hxx>
44 #include <redline.hxx>
45 #include <UndoRedline.hxx>
46 #include <section.hxx>
47 #include <tox.hxx>
48 #include <docsh.hxx>
49 
50 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
51 #include <com/sun/star/document/XDocumentProperties.hpp>
52 
53 using namespace ::com::sun::star;
54 
55 
56 class CompareLine
57 {
58 public:
59     CompareLine() {}
60     virtual ~CompareLine();
61 
62     virtual sal_uLong GetHashValue() const = 0;
63     virtual sal_Bool Compare( const CompareLine& rLine ) const = 0;
64 };
65 
66 DECLARE_LIST( CompareList, CompareLine* )
67 
68 class CompareData
69 {
70     sal_uLong* pIndex;
71     sal_Bool* pChangedFlag;
72 
73 protected:
74     CompareList aLines;
75     sal_uLong nSttLineNum;
76 
77     // Anfang und Ende beschneiden und alle anderen in das
78     // LinesArray setzen
79     virtual void CheckRanges( CompareData& ) = 0;
80 
81 public:
82     CompareData();
83     virtual ~CompareData();
84 
85     // gibt es unterschiede?
86     sal_Bool HasDiffs( const CompareData& rData ) const;
87 
88     // startet das Vergleichen und Erzeugen der Unterschiede zweier
89     // Dokumente
90     void CompareLines( CompareData& rData );
91     // lasse die Unterschiede anzeigen - ruft die beiden Methoden
92     // ShowInsert / ShowDelete. Diese bekommen die Start und EndLine-Nummer
93     // uebergeben. Die Abbildung auf den tatsaechline Inhalt muss die
94     // Ableitung uebernehmen!
95     sal_uLong ShowDiffs( const CompareData& rData );
96 
97     virtual void ShowInsert( sal_uLong nStt, sal_uLong nEnd );
98     virtual void ShowDelete( const CompareData& rData, sal_uLong nStt,
99                                 sal_uLong nEnd, sal_uLong nInsPos );
100     virtual void CheckForChangesInLine( const CompareData& rData,
101                                     sal_uLong& nStt, sal_uLong& nEnd,
102                                     sal_uLong& nThisStt, sal_uLong& nThisEnd );
103 
104     // Eindeutigen Index fuer eine Line setzen. Gleiche Lines haben den
105     // selben Index; auch in den anderen CompareData!
106     void SetIndex( sal_uLong nLine, sal_uLong nIndex );
107     sal_uLong GetIndex( sal_uLong nLine ) const
108         { return nLine < aLines.Count() ? pIndex[ nLine ] : 0; }
109 
110     // setze/erfrage ob eine Zeile veraendert ist
111     void SetChanged( sal_uLong nLine, sal_Bool bFlag = sal_True );
112     sal_Bool GetChanged( sal_uLong nLine ) const
113         {
114             return (pChangedFlag && nLine < aLines.Count())
115                 ? pChangedFlag[ nLine ]
116                 : 0;
117         }
118 
119     sal_uLong GetLineCount() const      { return aLines.Count(); }
120     sal_uLong GetLineOffset() const     { return nSttLineNum; }
121     const CompareLine* GetLine( sal_uLong nLine ) const
122             { return aLines.GetObject( nLine ); }
123     void InsertLine( CompareLine* pLine )
124         { aLines.Insert( pLine, LIST_APPEND ); }
125 };
126 
127 class Hash
128 {
129     struct _HashData
130     {
131         sal_uLong nNext, nHash;
132         const CompareLine* pLine;
133 
134         _HashData()
135             : nNext( 0 ), nHash( 0 ), pLine(0) {}
136     };
137 
138     sal_uLong* pHashArr;
139     _HashData* pDataArr;
140     sal_uLong nCount, nPrime;
141 
142 public:
143     Hash( sal_uLong nSize );
144     ~Hash();
145 
146     void CalcHashValue( CompareData& rData );
147 
148     sal_uLong GetCount() const { return nCount; }
149 };
150 
151 class Compare
152 {
153 public:
154     class MovedData
155     {
156         sal_uLong* pIndex;
157         sal_uLong* pLineNum;
158         sal_uLong nCount;
159 
160     public:
161         MovedData( CompareData& rData, sal_Char* pDiscard );
162         ~MovedData();
163 
164         sal_uLong GetIndex( sal_uLong n ) const { return pIndex[ n ]; }
165         sal_uLong GetLineNum( sal_uLong n ) const { return pLineNum[ n ]; }
166         sal_uLong GetCount() const { return nCount; }
167     };
168 
169 private:
170     // Suche die verschobenen Lines
171     class CompareSequence
172     {
173         CompareData &rData1, &rData2;
174         const MovedData &rMoved1, &rMoved2;
175         long *pMemory, *pFDiag, *pBDiag;
176 
177         void Compare( sal_uLong nStt1, sal_uLong nEnd1, sal_uLong nStt2, sal_uLong nEnd2 );
178         sal_uLong CheckDiag( sal_uLong nStt1, sal_uLong nEnd1,
179                         sal_uLong nStt2, sal_uLong nEnd2, sal_uLong* pCost );
180     public:
181         CompareSequence( CompareData& rData1, CompareData& rData2,
182                         const MovedData& rD1, const MovedData& rD2 );
183         ~CompareSequence();
184     };
185 
186 
187     static void CountDifference( const CompareData& rData, sal_uLong* pCounts );
188     static void SetDiscard( const CompareData& rData,
189                             sal_Char* pDiscard, sal_uLong* pCounts );
190     static void CheckDiscard( sal_uLong nLen, sal_Char* pDiscard );
191     static sal_uLong SetChangedFlag( CompareData& rData, sal_Char* pDiscard, int bFirst );
192     static void ShiftBoundaries( CompareData& rData1, CompareData& rData2 );
193 
194 public:
195     Compare( sal_uLong nDiff, CompareData& rData1, CompareData& rData2 );
196 };
197 
198 // ====================================================================
199 
200 CompareLine::~CompareLine() {}
201 
202 // ----------------------------------------------------------------------
203 
204 CompareData::CompareData()
205     : pIndex( 0 ), pChangedFlag( 0 ), nSttLineNum( 0 )
206 {
207 }
208 
209 CompareData::~CompareData()
210 {
211     delete[] pIndex;
212     delete[] pChangedFlag;
213 }
214 
215 void CompareData::SetIndex( sal_uLong nLine, sal_uLong nIndex )
216 {
217     if( !pIndex )
218     {
219         pIndex = new sal_uLong[ aLines.Count() ];
220         memset( pIndex, 0, aLines.Count() * sizeof( sal_uLong ) );
221     }
222     if( nLine < aLines.Count() )
223         pIndex[ nLine ] = nIndex;
224 }
225 
226 void CompareData::SetChanged( sal_uLong nLine, sal_Bool bFlag )
227 {
228     if( !pChangedFlag )
229     {
230         pChangedFlag = new sal_Bool[ aLines.Count() +1 ];
231         memset( pChangedFlag, 0, aLines.Count() +1 * sizeof( sal_Bool ) );
232     }
233     if( nLine < aLines.Count() )
234         pChangedFlag[ nLine ] = bFlag;
235 }
236 
237 void CompareData::CompareLines( CompareData& rData )
238 {
239     CheckRanges( rData );
240 
241     sal_uLong nDifferent;
242     {
243         Hash aH( GetLineCount() + rData.GetLineCount() + 1 );
244         aH.CalcHashValue( *this );
245         aH.CalcHashValue( rData );
246         nDifferent = aH.GetCount();
247     }
248     {
249         Compare aComp( nDifferent, *this, rData );
250     }
251 }
252 
253 sal_uLong CompareData::ShowDiffs( const CompareData& rData )
254 {
255     sal_uLong nLen1 = rData.GetLineCount(), nLen2 = GetLineCount();
256     sal_uLong nStt1 = 0, nStt2 = 0;
257     sal_uLong nCnt = 0;
258 
259     while( nStt1 < nLen1 || nStt2 < nLen2 )
260     {
261         if( rData.GetChanged( nStt1 ) || GetChanged( nStt2 ) )
262         {
263             sal_uLong nSav1 = nStt1, nSav2 = nStt2;
264             while( nStt1 < nLen1 && rData.GetChanged( nStt1 )) ++nStt1;
265             while( nStt2 < nLen2 && GetChanged( nStt2 )) ++nStt2;
266 
267             // rData ist das Original,
268             // this ist das, in das die Veraenderungen sollen
269             if( nSav2 != nStt2 && nSav1 != nStt1 )
270                 CheckForChangesInLine( rData, nSav1, nStt1, nSav2, nStt2 );
271 
272             if( nSav2 != nStt2 )
273                 ShowInsert( nSav2, nStt2 );
274 
275             if( nSav1 != nStt1 )
276                 ShowDelete( rData, nSav1, nStt1, nStt2 );
277             ++nCnt;
278         }
279         ++nStt1, ++nStt2;
280     }
281     return nCnt;
282 }
283 
284 sal_Bool CompareData::HasDiffs( const CompareData& rData ) const
285 {
286     sal_Bool bRet = sal_False;
287     sal_uLong nLen1 = rData.GetLineCount(), nLen2 = GetLineCount();
288     sal_uLong nStt1 = 0, nStt2 = 0;
289 
290     while( nStt1 < nLen1 || nStt2 < nLen2 )
291     {
292         if( rData.GetChanged( nStt1 ) || GetChanged( nStt2 ) )
293         {
294             bRet = sal_True;
295             break;
296         }
297         ++nStt1, ++nStt2;
298     }
299     return bRet;
300 }
301 
302 void CompareData::ShowInsert( sal_uLong, sal_uLong )
303 {
304 }
305 
306 void CompareData::ShowDelete( const CompareData&, sal_uLong, sal_uLong, sal_uLong )
307 {
308 }
309 
310 void CompareData::CheckForChangesInLine( const CompareData& ,
311                                     sal_uLong&, sal_uLong&, sal_uLong&, sal_uLong& )
312 {
313 }
314 
315 // ----------------------------------------------------------------------
316 
317 Hash::Hash( sal_uLong nSize )
318     : nCount( 1 )
319 {
320 
321 static const sal_uLong primes[] =
322 {
323   509,
324   1021,
325   2039,
326   4093,
327   8191,
328   16381,
329   32749,
330   65521,
331   131071,
332   262139,
333   524287,
334   1048573,
335   2097143,
336   4194301,
337   8388593,
338   16777213,
339   33554393,
340   67108859,         /* Preposterously large . . . */
341   134217689,
342   268435399,
343   536870909,
344   1073741789,
345   2147483647,
346   0
347 };
348     int i;
349 
350     pDataArr = new _HashData[ nSize ];
351     pDataArr[0].nNext = 0;
352     pDataArr[0].nHash = 0,
353     pDataArr[0].pLine = 0;
354 
355     for( i = 0; primes[i] < nSize / 3;  i++)
356         if( !primes[i] )
357         {
358             pHashArr = 0;
359             return;
360         }
361     nPrime = primes[ i ];
362     pHashArr = new sal_uLong[ nPrime ];
363     memset( pHashArr, 0, nPrime * sizeof( sal_uLong ) );
364 }
365 
366 Hash::~Hash()
367 {
368     delete[] pHashArr;
369     delete[] pDataArr;
370 }
371 
372 void Hash::CalcHashValue( CompareData& rData )
373 {
374     if( pHashArr )
375     {
376         for( sal_uLong n = 0; n < rData.GetLineCount(); ++n )
377         {
378             const CompareLine* pLine = rData.GetLine( n );
379             ASSERT( pLine, "wo ist die Line?" );
380             sal_uLong nH = pLine->GetHashValue();
381 
382             sal_uLong* pFound = &pHashArr[ nH % nPrime ];
383             sal_uLong i;
384             for( i = *pFound;  ;  i = pDataArr[i].nNext )
385                 if( !i )
386                 {
387                     i = nCount++;
388                     pDataArr[i].nNext = *pFound;
389                     pDataArr[i].nHash = nH;
390                     pDataArr[i].pLine = pLine;
391                     *pFound = i;
392                     break;
393                 }
394                 else if( pDataArr[i].nHash == nH &&
395                         pDataArr[i].pLine->Compare( *pLine ))
396                     break;
397 
398             rData.SetIndex( n, i );
399         }
400     }
401 }
402 
403 // ----------------------------------------------------------------------
404 
405 Compare::Compare( sal_uLong nDiff, CompareData& rData1, CompareData& rData2 )
406 {
407     MovedData *pMD1, *pMD2;
408     // Suche die unterschiedlichen Lines
409     {
410         sal_Char* pDiscard1 = new sal_Char[ rData1.GetLineCount() ];
411         sal_Char* pDiscard2 = new sal_Char[ rData2.GetLineCount() ];
412 
413         sal_uLong* pCount1 = new sal_uLong[ nDiff ];
414         sal_uLong* pCount2 = new sal_uLong[ nDiff ];
415         memset( pCount1, 0, nDiff * sizeof( sal_uLong ));
416         memset( pCount2, 0, nDiff * sizeof( sal_uLong ));
417 
418         // stelle fest, welche Indizies in den CompareData mehrfach vergeben wurden
419         CountDifference( rData1, pCount1 );
420         CountDifference( rData2, pCount2 );
421 
422         // alle die jetzt nur einmal vorhanden sind, sind eingefuegt oder
423         // geloescht worden. Alle die im anderen auch vorhanden sind, sind
424         // verschoben worden
425         SetDiscard( rData1, pDiscard1, pCount2 );
426         SetDiscard( rData2, pDiscard2, pCount1 );
427 
428         // die Arrays koennen wir wieder vergessen
429         delete [] pCount1; delete [] pCount2;
430 
431         CheckDiscard( rData1.GetLineCount(), pDiscard1 );
432         CheckDiscard( rData2.GetLineCount(), pDiscard2 );
433 
434         pMD1 = new MovedData( rData1, pDiscard1 );
435         pMD2 = new MovedData( rData2, pDiscard2 );
436 
437         // die Arrays koennen wir wieder vergessen
438         delete [] pDiscard1; delete [] pDiscard2;
439     }
440 
441     {
442         CompareSequence aTmp( rData1, rData2, *pMD1, *pMD2 );
443     }
444 
445     ShiftBoundaries( rData1, rData2 );
446 
447     delete pMD1;
448     delete pMD2;
449 }
450 
451 
452 
453 void Compare::CountDifference( const CompareData& rData, sal_uLong* pCounts )
454 {
455     sal_uLong nLen = rData.GetLineCount();
456     for( sal_uLong n = 0; n < nLen; ++n )
457     {
458         sal_uLong nIdx = rData.GetIndex( n );
459         ++pCounts[ nIdx ];
460     }
461 }
462 
463 void Compare::SetDiscard( const CompareData& rData,
464                             sal_Char* pDiscard, sal_uLong* pCounts )
465 {
466     sal_uLong nLen = rData.GetLineCount();
467 
468     // berechne Max in Abhanegigkeit zur LineAnzahl
469     sal_uInt16 nMax = 5;
470     sal_uLong n;
471 
472     for( n = nLen / 64; ( n = n >> 2 ) > 0; )
473         nMax <<= 1;
474 
475     for( n = 0; n < nLen; ++n )
476     {
477         sal_uLong nIdx = rData.GetIndex( n );
478         if( nIdx )
479         {
480             nIdx = pCounts[ nIdx ];
481             pDiscard[ n ] = !nIdx ? 1 : nIdx > nMax ? 2 : 0;
482         }
483         else
484             pDiscard[ n ] = 0;
485     }
486 }
487 
488 void Compare::CheckDiscard( sal_uLong nLen, sal_Char* pDiscard )
489 {
490     for( sal_uLong n = 0; n < nLen; ++n )
491     {
492         if( 2 == pDiscard[ n ] )
493             pDiscard[n] = 0;
494         else if( pDiscard[ n ] )
495         {
496             sal_uLong j;
497             sal_uLong length;
498             sal_uLong provisional = 0;
499 
500             /* Find end of this run of discardable lines.
501                 Count how many are provisionally discardable.  */
502             for (j = n; j < nLen; j++)
503             {
504                 if( !pDiscard[j] )
505                     break;
506                 if( 2 == pDiscard[j] )
507                     ++provisional;
508             }
509 
510             /* Cancel provisional discards at end, and shrink the run.  */
511             while( j > n && 2 == pDiscard[j - 1] )
512                 pDiscard[ --j ] = 0, --provisional;
513 
514             /* Now we have the length of a run of discardable lines
515                whose first and last are not provisional.  */
516             length = j - n;
517 
518             /* If 1/4 of the lines in the run are provisional,
519                cancel discarding of all provisional lines in the run.  */
520             if (provisional * 4 > length)
521             {
522                 while (j > n)
523                     if (pDiscard[--j] == 2)
524                         pDiscard[j] = 0;
525             }
526             else
527             {
528                 sal_uLong consec;
529                 sal_uLong minimum = 1;
530                 sal_uLong tem = length / 4;
531 
532                 /* MINIMUM is approximate square root of LENGTH/4.
533                    A subrun of two or more provisionals can stand
534                    when LENGTH is at least 16.
535                    A subrun of 4 or more can stand when LENGTH >= 64.  */
536                 while ((tem = tem >> 2) > 0)
537                     minimum *= 2;
538                 minimum++;
539 
540                 /* Cancel any subrun of MINIMUM or more provisionals
541                    within the larger run.  */
542                 for (j = 0, consec = 0; j < length; j++)
543                     if (pDiscard[n + j] != 2)
544                         consec = 0;
545                     else if (minimum == ++consec)
546                         /* Back up to start of subrun, to cancel it all.  */
547                         j -= consec;
548                     else if (minimum < consec)
549                         pDiscard[n + j] = 0;
550 
551                 /* Scan from beginning of run
552                    until we find 3 or more nonprovisionals in a row
553                    or until the first nonprovisional at least 8 lines in.
554                    Until that point, cancel any provisionals.  */
555                 for (j = 0, consec = 0; j < length; j++)
556                 {
557                     if (j >= 8 && pDiscard[n + j] == 1)
558                         break;
559                     if (pDiscard[n + j] == 2)
560                         consec = 0, pDiscard[n + j] = 0;
561                     else if (pDiscard[n + j] == 0)
562                         consec = 0;
563                     else
564                         consec++;
565                     if (consec == 3)
566                         break;
567                 }
568 
569                 /* I advances to the last line of the run.  */
570                 n += length - 1;
571 
572                 /* Same thing, from end.  */
573                 for (j = 0, consec = 0; j < length; j++)
574                 {
575                     if (j >= 8 && pDiscard[n - j] == 1)
576                         break;
577                     if (pDiscard[n - j] == 2)
578                         consec = 0, pDiscard[n - j] = 0;
579                     else if (pDiscard[n - j] == 0)
580                         consec = 0;
581                     else
582                         consec++;
583                     if (consec == 3)
584                         break;
585                 }
586             }
587         }
588     }
589 }
590 
591 // ----------------------------------------------------------------------
592 
593 Compare::MovedData::MovedData( CompareData& rData, sal_Char* pDiscard )
594     : pIndex( 0 ), pLineNum( 0 ), nCount( 0 )
595 {
596     sal_uLong nLen = rData.GetLineCount();
597     sal_uLong n;
598 
599     for( n = 0; n < nLen; ++n )
600         if( pDiscard[ n ] )
601             rData.SetChanged( n );
602         else
603             ++nCount;
604 
605     if( nCount )
606     {
607         pIndex = new sal_uLong[ nCount ];
608         pLineNum = new sal_uLong[ nCount ];
609 
610         for( n = 0, nCount = 0; n < nLen; ++n )
611             if( !pDiscard[ n ] )
612             {
613                 pIndex[ nCount ] = rData.GetIndex( n );
614                 pLineNum[ nCount++ ] = n;
615             }
616     }
617 }
618 
619 Compare::MovedData::~MovedData()
620 {
621     delete [] pIndex;
622     delete [] pLineNum;
623 }
624 
625 // ----------------------------------------------------------------------
626 
627     // Suche die verschobenen Lines
628 Compare::CompareSequence::CompareSequence(
629                             CompareData& rD1, CompareData& rD2,
630                             const MovedData& rMD1, const MovedData& rMD2 )
631     : rData1( rD1 ), rData2( rD2 ), rMoved1( rMD1 ), rMoved2( rMD2 )
632 {
633     sal_uLong nSize = rMD1.GetCount() + rMD2.GetCount() + 3;
634     pMemory = new long[ nSize * 2 ];
635     pFDiag = pMemory + ( rMD2.GetCount() + 1 );
636     pBDiag = pMemory + ( nSize + rMD2.GetCount() + 1 );
637 
638     Compare( 0, rMD1.GetCount(), 0, rMD2.GetCount() );
639 }
640 
641 Compare::CompareSequence::~CompareSequence()
642 {
643     delete [] pMemory;
644 }
645 
646 void Compare::CompareSequence::Compare( sal_uLong nStt1, sal_uLong nEnd1,
647                                         sal_uLong nStt2, sal_uLong nEnd2 )
648 {
649     /* Slide down the bottom initial diagonal. */
650     while( nStt1 < nEnd1 && nStt2 < nEnd2 &&
651         rMoved1.GetIndex( nStt1 ) == rMoved2.GetIndex( nStt2 ))
652         ++nStt1, ++nStt2;
653 
654     /* Slide up the top initial diagonal. */
655     while( nEnd1 > nStt1 && nEnd2 > nStt2 &&
656         rMoved1.GetIndex( nEnd1 - 1 ) == rMoved2.GetIndex( nEnd2 - 1 ))
657         --nEnd1, --nEnd2;
658 
659     /* Handle simple cases. */
660     if( nStt1 == nEnd1 )
661         while( nStt2 < nEnd2 )
662             rData2.SetChanged( rMoved2.GetLineNum( nStt2++ ));
663 
664     else if (nStt2 == nEnd2)
665         while (nStt1 < nEnd1)
666             rData1.SetChanged( rMoved1.GetLineNum( nStt1++ ));
667 
668     else
669     {
670         sal_uLong c, d, b;
671 
672         /* Find a point of correspondence in the middle of the files.  */
673 
674         d = CheckDiag( nStt1, nEnd1, nStt2, nEnd2, &c );
675         b = pBDiag[ d ];
676 
677         if( 1 != c )
678         {
679             /* Use that point to split this problem into two subproblems.  */
680             Compare( nStt1, b, nStt2, b - d );
681             /* This used to use f instead of b,
682                but that is incorrect!
683                It is not necessarily the case that diagonal d
684                has a snake from b to f.  */
685             Compare( b, nEnd1, b - d, nEnd2 );
686         }
687     }
688 }
689 
690 sal_uLong Compare::CompareSequence::CheckDiag( sal_uLong nStt1, sal_uLong nEnd1,
691                                     sal_uLong nStt2, sal_uLong nEnd2, sal_uLong* pCost )
692 {
693     const long dmin = nStt1 - nEnd2;    /* Minimum valid diagonal. */
694     const long dmax = nEnd1 - nStt2;    /* Maximum valid diagonal. */
695     const long fmid = nStt1 - nStt2;    /* Center diagonal of top-down search. */
696     const long bmid = nEnd1 - nEnd2;    /* Center diagonal of bottom-up search. */
697 
698     long fmin = fmid, fmax = fmid;  /* Limits of top-down search. */
699     long bmin = bmid, bmax = bmid;  /* Limits of bottom-up search. */
700 
701     long c;         /* Cost. */
702     long odd = (fmid - bmid) & 1;   /* True if southeast corner is on an odd
703                      diagonal with respect to the northwest. */
704 
705     pFDiag[fmid] = nStt1;
706     pBDiag[bmid] = nEnd1;
707 
708     for (c = 1;; ++c)
709     {
710         long d;         /* Active diagonal. */
711         long big_snake = 0;
712 
713         /* Extend the top-down search by an edit step in each diagonal. */
714         fmin > dmin ? pFDiag[--fmin - 1] = -1 : ++fmin;
715         fmax < dmax ? pFDiag[++fmax + 1] = -1 : --fmax;
716         for (d = fmax; d >= fmin; d -= 2)
717         {
718             long x, y, oldx, tlo = pFDiag[d - 1], thi = pFDiag[d + 1];
719 
720             if (tlo >= thi)
721                 x = tlo + 1;
722             else
723                 x = thi;
724             oldx = x;
725             y = x - d;
726             while( sal_uLong(x) < nEnd1 && sal_uLong(y) < nEnd2 &&
727                 rMoved1.GetIndex( x ) == rMoved2.GetIndex( y ))
728                 ++x, ++y;
729             if (x - oldx > 20)
730                 big_snake = 1;
731             pFDiag[d] = x;
732             if( odd && bmin <= d && d <= bmax && pBDiag[d] <= pFDiag[d] )
733             {
734                 *pCost = 2 * c - 1;
735                 return d;
736             }
737         }
738 
739         /* Similar extend the bottom-up search. */
740         bmin > dmin ? pBDiag[--bmin - 1] = INT_MAX : ++bmin;
741         bmax < dmax ? pBDiag[++bmax + 1] = INT_MAX : --bmax;
742         for (d = bmax; d >= bmin; d -= 2)
743         {
744             long x, y, oldx, tlo = pBDiag[d - 1], thi = pBDiag[d + 1];
745 
746             if (tlo < thi)
747                 x = tlo;
748             else
749                 x = thi - 1;
750             oldx = x;
751             y = x - d;
752             while( sal_uLong(x) > nStt1 && sal_uLong(y) > nStt2 &&
753                 rMoved1.GetIndex( x - 1 ) == rMoved2.GetIndex( y - 1 ))
754                 --x, --y;
755             if (oldx - x > 20)
756                 big_snake = 1;
757             pBDiag[d] = x;
758             if (!odd && fmin <= d && d <= fmax && pBDiag[d] <= pFDiag[d])
759             {
760                 *pCost = 2 * c;
761                 return d;
762             }
763         }
764     }
765 }
766 
767 void Compare::ShiftBoundaries( CompareData& rData1, CompareData& rData2 )
768 {
769     for( int iz = 0; iz < 2; ++iz )
770     {
771         CompareData* pData = &rData1;
772         CompareData* pOtherData = &rData2;
773 
774         sal_uLong i = 0;
775         sal_uLong j = 0;
776         sal_uLong i_end = pData->GetLineCount();
777         sal_uLong preceding = ULONG_MAX;
778         sal_uLong other_preceding = ULONG_MAX;
779 
780         while (1)
781         {
782             sal_uLong start, other_start;
783 
784             /* Scan forwards to find beginning of another run of changes.
785                Also keep track of the corresponding point in the other file.  */
786 
787             while( i < i_end && !pData->GetChanged( i ) )
788             {
789                 while( pOtherData->GetChanged( j++ ))
790                     /* Non-corresponding lines in the other file
791                        will count as the preceding batch of changes.  */
792                     other_preceding = j;
793                 i++;
794             }
795 
796             if (i == i_end)
797                 break;
798 
799             start = i;
800             other_start = j;
801 
802             while (1)
803             {
804                 /* Now find the end of this run of changes.  */
805 
806                 while( pData->GetChanged( ++i ))
807                     ;
808 
809                 /* If the first changed line matches the following unchanged one,
810                    and this run does not follow right after a previous run,
811                    and there are no lines deleted from the other file here,
812                    then classify the first changed line as unchanged
813                    and the following line as changed in its place.  */
814 
815                 /* You might ask, how could this run follow right after another?
816                    Only because the previous run was shifted here.  */
817 
818                 if( i != i_end &&
819                     pData->GetIndex( start ) == pData->GetIndex( i ) &&
820                     !pOtherData->GetChanged( j ) &&
821                     !( start == preceding || other_start == other_preceding ))
822                 {
823                     pData->SetChanged( start++, 0 );
824                     pData->SetChanged(  i );
825                     /* Since one line-that-matches is now before this run
826                        instead of after, we must advance in the other file
827                        to keep in synch.  */
828                     ++j;
829                 }
830                 else
831                     break;
832             }
833 
834             preceding = i;
835             other_preceding = j;
836         }
837 
838         pData = &rData2;
839         pOtherData = &rData1;
840     }
841 }
842 
843 /*  */
844 
845 class SwCompareLine : public CompareLine
846 {
847     const SwNode& rNode;
848 public:
849     SwCompareLine( const SwNode& rNd );
850     virtual ~SwCompareLine();
851 
852     virtual sal_uLong GetHashValue() const;
853     virtual sal_Bool Compare( const CompareLine& rLine ) const;
854 
855     static sal_uLong GetTxtNodeHashValue( const SwTxtNode& rNd, sal_uLong nVal );
856     static sal_Bool CompareNode( const SwNode& rDstNd, const SwNode& rSrcNd );
857     static sal_Bool CompareTxtNd( const SwTxtNode& rDstNd,
858                               const SwTxtNode& rSrcNd );
859 
860     sal_Bool ChangesInLine( const SwCompareLine& rLine,
861                             SwPaM *& rpInsRing, SwPaM*& rpDelRing ) const;
862 
863     const SwNode& GetNode() const { return rNode; }
864 
865     const SwNode& GetEndNode() const;
866 
867     // fuers Debugging!
868     String GetText() const;
869 };
870 
871 class SwCompareData : public CompareData
872 {
873     SwDoc& rDoc;
874     SwPaM *pInsRing, *pDelRing;
875 
876     sal_uLong PrevIdx( const SwNode* pNd );
877     sal_uLong NextIdx( const SwNode* pNd );
878 
879     virtual void CheckRanges( CompareData& );
880     virtual void ShowInsert( sal_uLong nStt, sal_uLong nEnd );
881     virtual void ShowDelete( const CompareData& rData, sal_uLong nStt,
882                                 sal_uLong nEnd, sal_uLong nInsPos );
883 
884     virtual void CheckForChangesInLine( const CompareData& rData,
885                                     sal_uLong& nStt, sal_uLong& nEnd,
886                                     sal_uLong& nThisStt, sal_uLong& nThisEnd );
887 
888 public:
889     SwCompareData( SwDoc& rD ) : rDoc( rD ), pInsRing(0), pDelRing(0) {}
890     virtual ~SwCompareData();
891 
892     void SetRedlinesToDoc( sal_Bool bUseDocInfo );
893 };
894 
895 // ----------------------------------------------------------------
896 
897 SwCompareLine::SwCompareLine( const SwNode& rNd )
898     : rNode( rNd )
899 {
900 }
901 
902 SwCompareLine::~SwCompareLine()
903 {
904 }
905 
906 sal_uLong SwCompareLine::GetHashValue() const
907 {
908     sal_uLong nRet = 0;
909     switch( rNode.GetNodeType() )
910     {
911     case ND_TEXTNODE:
912         nRet = GetTxtNodeHashValue( (SwTxtNode&)rNode, nRet );
913         break;
914 
915     case ND_TABLENODE:
916         {
917             const SwNode* pEndNd = rNode.EndOfSectionNode();
918             SwNodeIndex aIdx( rNode );
919             while( &aIdx.GetNode() != pEndNd )
920             {
921                 if( aIdx.GetNode().IsTxtNode() )
922                     nRet = GetTxtNodeHashValue( (SwTxtNode&)aIdx.GetNode(), nRet );
923                 aIdx++;
924             }
925         }
926         break;
927 
928     case ND_SECTIONNODE:
929         {
930             String sStr( GetText() );
931             for( xub_StrLen n = 0; n < sStr.Len(); ++n )
932                 ( nRet <<= 1 ) += sStr.GetChar( n );
933         }
934         break;
935 
936     case ND_GRFNODE:
937     case ND_OLENODE:
938         // feste Id ? sollte aber nie auftauchen
939         break;
940     }
941     return nRet;
942 }
943 
944 const SwNode& SwCompareLine::GetEndNode() const
945 {
946     const SwNode* pNd = &rNode;
947     switch( rNode.GetNodeType() )
948     {
949     case ND_TABLENODE:
950         pNd = rNode.EndOfSectionNode();
951         break;
952 
953     case ND_SECTIONNODE:
954         {
955             const SwSectionNode& rSNd = (SwSectionNode&)rNode;
956             const SwSection& rSect = rSNd.GetSection();
957             if( CONTENT_SECTION != rSect.GetType() || rSect.IsProtect() )
958                 pNd = rNode.EndOfSectionNode();
959         }
960         break;
961     }
962     return *pNd;
963 }
964 
965 sal_Bool SwCompareLine::Compare( const CompareLine& rLine ) const
966 {
967     return CompareNode( rNode, ((SwCompareLine&)rLine).rNode );
968 }
969 
970 namespace
971 {
972     static String SimpleTableToText(const SwNode &rNode)
973     {
974         String sRet;
975         const SwNode* pEndNd = rNode.EndOfSectionNode();
976         SwNodeIndex aIdx( rNode );
977         while (&aIdx.GetNode() != pEndNd)
978         {
979             if (aIdx.GetNode().IsTxtNode())
980             {
981                 if (sRet.Len())
982                 {
983                     sRet.Append( '\n' );
984                 }
985                 sRet.Append( aIdx.GetNode().GetTxtNode()->GetExpandTxt() );
986             }
987             aIdx++;
988         }
989         return sRet;
990     }
991 }
992 
993 sal_Bool SwCompareLine::CompareNode( const SwNode& rDstNd, const SwNode& rSrcNd )
994 {
995     if( rSrcNd.GetNodeType() != rDstNd.GetNodeType() )
996         return sal_False;
997 
998     sal_Bool bRet = sal_False;
999 
1000     switch( rDstNd.GetNodeType() )
1001     {
1002     case ND_TEXTNODE:
1003         bRet = CompareTxtNd( (SwTxtNode&)rDstNd, (SwTxtNode&)rSrcNd );
1004         break;
1005 
1006     case ND_TABLENODE:
1007         {
1008             const SwTableNode& rTSrcNd = (SwTableNode&)rSrcNd;
1009             const SwTableNode& rTDstNd = (SwTableNode&)rDstNd;
1010 
1011             bRet = ( rTSrcNd.EndOfSectionIndex() - rTSrcNd.GetIndex() ) ==
1012                    ( rTDstNd.EndOfSectionIndex() - rTDstNd.GetIndex() );
1013 
1014             // --> #i107826#: compare actual table content
1015             if (bRet)
1016             {
1017                 bRet = (SimpleTableToText(rSrcNd) == SimpleTableToText(rDstNd));
1018             }
1019             // <--
1020         }
1021         break;
1022 
1023     case ND_SECTIONNODE:
1024         {
1025             const SwSectionNode& rSSrcNd = (SwSectionNode&)rSrcNd,
1026                                & rSDstNd = (SwSectionNode&)rDstNd;
1027             const SwSection& rSrcSect = rSSrcNd.GetSection(),
1028                            & rDstSect = rSDstNd.GetSection();
1029             SectionType eSrcSectType = rSrcSect.GetType(),
1030                         eDstSectType = rDstSect.GetType();
1031             switch( eSrcSectType )
1032             {
1033             case CONTENT_SECTION:
1034                 bRet = CONTENT_SECTION == eDstSectType &&
1035                         rSrcSect.IsProtect() == rDstSect.IsProtect();
1036                 if( bRet && rSrcSect.IsProtect() )
1037                 {
1038                     // the only have they both the same size
1039                     bRet = ( rSSrcNd.EndOfSectionIndex() - rSSrcNd.GetIndex() ) ==
1040                            ( rSDstNd.EndOfSectionIndex() - rSDstNd.GetIndex() );
1041                 }
1042                 break;
1043 
1044             case TOX_HEADER_SECTION:
1045             case TOX_CONTENT_SECTION:
1046                 if( TOX_HEADER_SECTION == eDstSectType ||
1047                     TOX_CONTENT_SECTION == eDstSectType )
1048                 {
1049                     // the same type of TOX?
1050                     const SwTOXBase* pSrcTOX = rSrcSect.GetTOXBase();
1051                     const SwTOXBase* pDstTOX = rDstSect.GetTOXBase();
1052                     bRet =  pSrcTOX && pDstTOX
1053                             && pSrcTOX->GetType() == pDstTOX->GetType()
1054                             && pSrcTOX->GetTitle() == pDstTOX->GetTitle()
1055                             && pSrcTOX->GetTypeName() == pDstTOX->GetTypeName()
1056 //                          && pSrcTOX->GetTOXName() == pDstTOX->GetTOXName()
1057                             ;
1058                 }
1059                 break;
1060 
1061             case DDE_LINK_SECTION:
1062             case FILE_LINK_SECTION:
1063                 bRet = eSrcSectType == eDstSectType &&
1064                         rSrcSect.GetLinkFileName() ==
1065                         rDstSect.GetLinkFileName();
1066                 break;
1067             }
1068         }
1069         break;
1070 
1071     case ND_ENDNODE:
1072         bRet = rSrcNd.StartOfSectionNode()->GetNodeType() ==
1073                rDstNd.StartOfSectionNode()->GetNodeType();
1074 
1075         // --> #i107826#: compare actual table content
1076         if (bRet && rSrcNd.StartOfSectionNode()->GetNodeType() == ND_TABLENODE)
1077         {
1078             bRet = CompareNode(
1079                 *rSrcNd.StartOfSectionNode(), *rDstNd.StartOfSectionNode());
1080         }
1081         // <--
1082 
1083         break;
1084     }
1085     return bRet;
1086 }
1087 
1088 String SwCompareLine::GetText() const
1089 {
1090     String sRet;
1091     switch( rNode.GetNodeType() )
1092     {
1093     case ND_TEXTNODE:
1094         sRet = ((SwTxtNode&)rNode).GetExpandTxt();
1095         break;
1096 
1097     case ND_TABLENODE:
1098         {
1099             sRet = SimpleTableToText(rNode);
1100             sRet.InsertAscii( "Tabelle: ", 0 );
1101         }
1102         break;
1103 
1104     case ND_SECTIONNODE:
1105         {
1106             sRet.AssignAscii( RTL_CONSTASCII_STRINGPARAM( "Section - Node:" ));
1107 
1108             const SwSectionNode& rSNd = (SwSectionNode&)rNode;
1109             const SwSection& rSect = rSNd.GetSection();
1110             switch( rSect.GetType() )
1111             {
1112             case CONTENT_SECTION:
1113                 if( rSect.IsProtect() )
1114                     sRet.Append( String::CreateFromInt32(
1115                             rSNd.EndOfSectionIndex() - rSNd.GetIndex() ));
1116                 break;
1117 
1118             case TOX_HEADER_SECTION:
1119             case TOX_CONTENT_SECTION:
1120                 {
1121                     const SwTOXBase* pTOX = rSect.GetTOXBase();
1122                     if( pTOX )
1123                         sRet.Append( pTOX->GetTitle() )
1124                             .Append( pTOX->GetTypeName() )
1125 //                          .Append( pTOX->GetTOXName() )
1126                             .Append( String::CreateFromInt32( pTOX->GetType() ));
1127                 }
1128                 break;
1129 
1130             case DDE_LINK_SECTION:
1131             case FILE_LINK_SECTION:
1132                 sRet += rSect.GetLinkFileName();
1133                 break;
1134             }
1135         }
1136         break;
1137 
1138     case ND_GRFNODE:
1139         sRet.AssignAscii( RTL_CONSTASCII_STRINGPARAM( "Grafik - Node:" ));
1140         break;
1141     case ND_OLENODE:
1142         sRet.AssignAscii( RTL_CONSTASCII_STRINGPARAM( "OLE - Node:" ));
1143         break;
1144     }
1145     return sRet;
1146 }
1147 
1148 sal_uLong SwCompareLine::GetTxtNodeHashValue( const SwTxtNode& rNd, sal_uLong nVal )
1149 {
1150     String sStr( rNd.GetExpandTxt() );
1151     for( xub_StrLen n = 0; n < sStr.Len(); ++n )
1152         ( nVal <<= 1 ) += sStr.GetChar( n );
1153     return nVal;
1154 }
1155 
1156 sal_Bool SwCompareLine::CompareTxtNd( const SwTxtNode& rDstNd,
1157                                   const SwTxtNode& rSrcNd )
1158 {
1159     sal_Bool bRet = sal_False;
1160     // erstmal ganz einfach!
1161     if( rDstNd.GetTxt() == rSrcNd.GetTxt() )
1162     {
1163         // der Text ist gleich, aber sind die "Sonderattribute" (0xFF) auch
1164         // dieselben??
1165         bRet = sal_True;
1166     }
1167     return bRet;
1168 }
1169 
1170 sal_Bool SwCompareLine::ChangesInLine( const SwCompareLine& rLine,
1171                             SwPaM *& rpInsRing, SwPaM*& rpDelRing ) const
1172 {
1173     sal_Bool bRet = sal_False;
1174     if( ND_TEXTNODE == rNode.GetNodeType() &&
1175         ND_TEXTNODE == rLine.GetNode().GetNodeType() )
1176     {
1177         SwTxtNode& rDestNd = *(SwTxtNode*)rNode.GetTxtNode();
1178         const SwTxtNode& rSrcNd = *rLine.GetNode().GetTxtNode();
1179 
1180         xub_StrLen nDEnd = rDestNd.GetTxt().Len(), nSEnd = rSrcNd.GetTxt().Len();
1181         xub_StrLen nStt;
1182         xub_StrLen nEnd;
1183 
1184         for( nStt = 0, nEnd = Min( nDEnd, nSEnd ); nStt < nEnd; ++nStt )
1185             if( rDestNd.GetTxt().GetChar( nStt ) !=
1186                 rSrcNd.GetTxt().GetChar( nStt ) )
1187                 break;
1188 
1189         while( nStt < nDEnd && nStt < nSEnd )
1190         {
1191             --nDEnd, --nSEnd;
1192             if( rDestNd.GetTxt().GetChar( nDEnd ) !=
1193                 rSrcNd.GetTxt().GetChar( nSEnd ) )
1194             {
1195                 ++nDEnd, ++nSEnd;
1196                 break;
1197             }
1198         }
1199 
1200         if( nStt || !nDEnd || !nSEnd || nDEnd < rDestNd.GetTxt().Len() ||
1201             nSEnd < rSrcNd.GetTxt().Len() )
1202         {
1203             // jetzt ist zwischen nStt bis nDEnd das neu eingefuegte
1204             // und zwischen nStt und nSEnd das geloeschte
1205             SwDoc* pDoc = rDestNd.GetDoc();
1206             SwPaM aPam( rDestNd, nDEnd );
1207             if( nStt != nDEnd )
1208             {
1209                 SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpInsRing );
1210                 if( !rpInsRing )
1211                     rpInsRing = pTmp;
1212 
1213                 pTmp->SetMark();
1214                 pTmp->GetMark()->nContent = nStt;
1215             }
1216 
1217             if( nStt != nSEnd )
1218             {
1219                 {
1220                     ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo());
1221                     SwPaM aCpyPam( rSrcNd, nStt );
1222                     aCpyPam.SetMark();
1223                     aCpyPam.GetPoint()->nContent = nSEnd;
1224                     aCpyPam.GetDoc()->CopyRange( aCpyPam, *aPam.GetPoint(),
1225                             false );
1226                 }
1227 
1228                 SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpDelRing );
1229                 if( !rpDelRing )
1230                     rpDelRing = pTmp;
1231 
1232                 pTmp->SetMark();
1233                 pTmp->GetMark()->nContent = nDEnd;
1234 
1235                 if( rpInsRing )
1236                 {
1237                     SwPaM* pCorr = (SwPaM*)rpInsRing->GetPrev();
1238                     if( *pCorr->GetPoint() == *pTmp->GetPoint() )
1239                         *pCorr->GetPoint() = *pTmp->GetMark();
1240                 }
1241             }
1242             bRet = sal_True;
1243         }
1244     }
1245     return bRet;
1246 }
1247 
1248 // ----------------------------------------------------------------
1249 
1250 SwCompareData::~SwCompareData()
1251 {
1252     if( pDelRing )
1253     {
1254         while( pDelRing->GetNext() != pDelRing )
1255             delete pDelRing->GetNext();
1256         delete pDelRing;
1257     }
1258     if( pInsRing )
1259     {
1260         while( pInsRing->GetNext() != pInsRing )
1261             delete pInsRing->GetNext();
1262         delete pInsRing;
1263     }
1264 }
1265 
1266 sal_uLong SwCompareData::NextIdx( const SwNode* pNd )
1267 {
1268     if( pNd->IsStartNode() )
1269     {
1270         const SwSectionNode* pSNd;
1271         if( pNd->IsTableNode() ||
1272             ( 0 != (pSNd = pNd->GetSectionNode() ) &&
1273                 ( CONTENT_SECTION != pSNd->GetSection().GetType() ||
1274                     pSNd->GetSection().IsProtect() ) ) )
1275             pNd = pNd->EndOfSectionNode();
1276     }
1277     return pNd->GetIndex() + 1;
1278 }
1279 
1280 sal_uLong SwCompareData::PrevIdx( const SwNode* pNd )
1281 {
1282     if( pNd->IsEndNode() )
1283     {
1284         const SwSectionNode* pSNd;
1285         if( pNd->StartOfSectionNode()->IsTableNode() ||
1286             ( 0 != (pSNd = pNd->StartOfSectionNode()->GetSectionNode() ) &&
1287                 ( CONTENT_SECTION != pSNd->GetSection().GetType() ||
1288                     pSNd->GetSection().IsProtect() ) ) )
1289             pNd = pNd->StartOfSectionNode();
1290     }
1291     return pNd->GetIndex() - 1;
1292 }
1293 
1294 
1295 void SwCompareData::CheckRanges( CompareData& rData )
1296 {
1297     const SwNodes& rSrcNds = ((SwCompareData&)rData).rDoc.GetNodes();
1298     const SwNodes& rDstNds = rDoc.GetNodes();
1299 
1300     const SwNode& rSrcEndNd = rSrcNds.GetEndOfContent();
1301     const SwNode& rDstEndNd = rDstNds.GetEndOfContent();
1302 
1303     sal_uLong nSrcSttIdx = NextIdx( rSrcEndNd.StartOfSectionNode() );
1304     sal_uLong nSrcEndIdx = rSrcEndNd.GetIndex();
1305 
1306     sal_uLong nDstSttIdx = NextIdx( rDstEndNd.StartOfSectionNode() );
1307     sal_uLong nDstEndIdx = rDstEndNd.GetIndex();
1308 
1309     while( nSrcSttIdx < nSrcEndIdx && nDstSttIdx < nDstEndIdx )
1310     {
1311         const SwNode* pSrcNd = rSrcNds[ nSrcSttIdx ];
1312         const SwNode* pDstNd = rDstNds[ nDstSttIdx ];
1313         if( !SwCompareLine::CompareNode( *pSrcNd, *pDstNd ))
1314             break;
1315 
1316         nSrcSttIdx = NextIdx( pSrcNd );
1317         nDstSttIdx = NextIdx( pDstNd );
1318     }
1319 
1320     nSrcEndIdx = PrevIdx( &rSrcEndNd );
1321     nDstEndIdx = PrevIdx( &rDstEndNd );
1322     while( nSrcSttIdx < nSrcEndIdx && nDstSttIdx < nDstEndIdx )
1323     {
1324         const SwNode* pSrcNd = rSrcNds[ nSrcEndIdx ];
1325         const SwNode* pDstNd = rDstNds[ nDstEndIdx ];
1326         if( !SwCompareLine::CompareNode( *pSrcNd, *pDstNd ))
1327             break;
1328 
1329         nSrcEndIdx = PrevIdx( pSrcNd );
1330         nDstEndIdx = PrevIdx( pDstNd );
1331     }
1332 
1333     while( nSrcSttIdx <= nSrcEndIdx )
1334     {
1335         const SwNode* pNd = rSrcNds[ nSrcSttIdx ];
1336         rData.InsertLine( new SwCompareLine( *pNd ) );
1337         nSrcSttIdx = NextIdx( pNd );
1338     }
1339 
1340     while( nDstSttIdx <= nDstEndIdx )
1341     {
1342         const SwNode* pNd = rDstNds[ nDstSttIdx ];
1343         InsertLine( new SwCompareLine( *pNd ) );
1344         nDstSttIdx = NextIdx( pNd );
1345     }
1346 }
1347 
1348 
1349 void SwCompareData::ShowInsert( sal_uLong nStt, sal_uLong nEnd )
1350 {
1351     SwPaM* pTmp = new SwPaM( ((SwCompareLine*)GetLine( nStt ))->GetNode(), 0,
1352                             ((SwCompareLine*)GetLine( nEnd-1 ))->GetEndNode(), 0,
1353                              pInsRing );
1354     if( !pInsRing )
1355         pInsRing = pTmp;
1356 
1357     // #i65201#: These SwPaMs are calculated smaller than needed, see comment below
1358 
1359 }
1360 
1361 void SwCompareData::ShowDelete( const CompareData& rData, sal_uLong nStt,
1362                                 sal_uLong nEnd, sal_uLong nInsPos )
1363 {
1364     SwNodeRange aRg(
1365         ((SwCompareLine*)rData.GetLine( nStt ))->GetNode(), 0,
1366         ((SwCompareLine*)rData.GetLine( nEnd-1 ))->GetEndNode(), 1 );
1367 
1368     sal_uInt16 nOffset = 0;
1369     const CompareLine* pLine;
1370     if( GetLineCount() == nInsPos )
1371     {
1372         pLine = GetLine( nInsPos-1 );
1373         nOffset = 1;
1374     }
1375     else
1376         pLine = GetLine( nInsPos );
1377 
1378     const SwNode* pLineNd;
1379     if( pLine )
1380     {
1381         if( nOffset )
1382             pLineNd = &((SwCompareLine*)pLine)->GetEndNode();
1383         else
1384             pLineNd = &((SwCompareLine*)pLine)->GetNode();
1385     }
1386     else
1387     {
1388         pLineNd = &rDoc.GetNodes().GetEndOfContent();
1389         nOffset = 0;
1390     }
1391 
1392     SwNodeIndex aInsPos( *pLineNd, nOffset );
1393     SwNodeIndex aSavePos( aInsPos, -1 );
1394 
1395     ((SwCompareData&)rData).rDoc.CopyWithFlyInFly( aRg, 0, aInsPos );
1396     rDoc.SetModified();
1397     aSavePos++;
1398 
1399     // #i65201#: These SwPaMs are calculated when the (old) delete-redlines are hidden,
1400     // they will be inserted when the delete-redlines are shown again.
1401     // To avoid unwanted insertions of delete-redlines into these new redlines, what happens
1402     // especially at the end of the document, I reduce the SwPaM by one node.
1403     // Before the new redlines are inserted, they have to expand again.
1404     SwPaM* pTmp = new SwPaM( aSavePos.GetNode(), aInsPos.GetNode(), 0, -1, pDelRing );
1405     if( !pDelRing )
1406         pDelRing = pTmp;
1407 
1408     if( pInsRing )
1409     {
1410         SwPaM* pCorr = (SwPaM*)pInsRing->GetPrev();
1411         if( *pCorr->GetPoint() == *pTmp->GetPoint() )
1412         {
1413             SwNodeIndex aTmpPos( pTmp->GetMark()->nNode, -1 );
1414             *pCorr->GetPoint() = SwPosition( aTmpPos );
1415         }
1416     }
1417 }
1418 
1419 void SwCompareData::CheckForChangesInLine( const CompareData& rData,
1420                                     sal_uLong& rStt, sal_uLong& rEnd,
1421                                     sal_uLong& rThisStt, sal_uLong& rThisEnd )
1422 {
1423     while( rStt < rEnd && rThisStt < rThisEnd )
1424     {
1425         SwCompareLine* pDstLn = (SwCompareLine*)GetLine( rThisStt );
1426         SwCompareLine* pSrcLn = (SwCompareLine*)rData.GetLine( rStt );
1427         if( !pDstLn->ChangesInLine( *pSrcLn, pInsRing, pDelRing ) )
1428             break;
1429 
1430         ++rStt;
1431         ++rThisStt;
1432     }
1433 }
1434 
1435 void SwCompareData::SetRedlinesToDoc( sal_Bool bUseDocInfo )
1436 {
1437     SwPaM* pTmp = pDelRing;
1438 
1439     // Bug #83296#: get the Author / TimeStamp from the "other"
1440     //              document info
1441     sal_uInt16 nAuthor = rDoc.GetRedlineAuthor();
1442     DateTime aTimeStamp;
1443     SwDocShell *pDocShell(rDoc.GetDocShell());
1444     DBG_ASSERT(pDocShell, "no SwDocShell");
1445     if (pDocShell) {
1446         uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
1447             pDocShell->GetModel(), uno::UNO_QUERY_THROW);
1448         uno::Reference<document::XDocumentProperties> xDocProps(
1449             xDPS->getDocumentProperties());
1450         DBG_ASSERT(xDocProps.is(), "Doc has no DocumentProperties");
1451 
1452         if( bUseDocInfo && xDocProps.is() ) {
1453             String aTmp( 1 == xDocProps->getEditingCycles()
1454                                 ? xDocProps->getAuthor()
1455                                 : xDocProps->getModifiedBy() );
1456             util::DateTime uDT( 1 == xDocProps->getEditingCycles()
1457                                 ? xDocProps->getCreationDate()
1458                                 : xDocProps->getModificationDate() );
1459             Date d(uDT.Day, uDT.Month, uDT.Year);
1460             Time t(uDT.Hours, uDT.Minutes, uDT.Seconds, uDT.HundredthSeconds);
1461             DateTime aDT(d,t);
1462 
1463             if( aTmp.Len() )
1464             {
1465                 nAuthor = rDoc.InsertRedlineAuthor( aTmp );
1466                 aTimeStamp = aDT;
1467             }
1468         }
1469     }
1470 
1471     if( pTmp )
1472     {
1473         SwRedlineData aRedlnData( nsRedlineType_t::REDLINE_DELETE, nAuthor, aTimeStamp,
1474                                     aEmptyStr, 0, 0 );
1475         do {
1476             // #i65201#: Expand again, see comment above.
1477             if( pTmp->GetPoint()->nContent == 0 )
1478             {
1479                 pTmp->GetPoint()->nNode++;
1480                 pTmp->GetPoint()->nContent.Assign( pTmp->GetCntntNode(), 0 );
1481             }
1482             // --> mst 2010-05-17 #i101009#
1483             // prevent redlines that end on structural end node
1484             if (& rDoc.GetNodes().GetEndOfContent() ==
1485                 & pTmp->GetPoint()->nNode.GetNode())
1486             {
1487                 pTmp->GetPoint()->nNode--;
1488                 SwCntntNode *const pContentNode( pTmp->GetCntntNode() );
1489                 pTmp->GetPoint()->nContent.Assign( pContentNode,
1490                         (pContentNode) ? pContentNode->Len() : 0 );
1491             }
1492             // <--
1493 
1494             rDoc.DeleteRedline( *pTmp, false, USHRT_MAX );
1495 
1496             if (rDoc.GetIDocumentUndoRedo().DoesUndo())
1497             {
1498                 SwUndo *const pUndo(new SwUndoCompDoc( *pTmp, sal_False )) ;
1499                 rDoc.GetIDocumentUndoRedo().AppendUndo(pUndo);
1500             }
1501             rDoc.AppendRedline( new SwRedline( aRedlnData, *pTmp ), true );
1502 
1503         } while( pDelRing != ( pTmp = (SwPaM*)pTmp->GetNext() ));
1504     }
1505 
1506     pTmp = pInsRing;
1507     if( pTmp )
1508     {
1509         do {
1510             if( pTmp->GetPoint()->nContent == 0 )
1511             {
1512                 pTmp->GetPoint()->nNode++;
1513                 pTmp->GetPoint()->nContent.Assign( pTmp->GetCntntNode(), 0 );
1514             }
1515             // --> mst 2010-05-17 #i101009#
1516             // prevent redlines that end on structural end node
1517             if (& rDoc.GetNodes().GetEndOfContent() ==
1518                 & pTmp->GetPoint()->nNode.GetNode())
1519             {
1520                 pTmp->GetPoint()->nNode--;
1521                 SwCntntNode *const pContentNode( pTmp->GetCntntNode() );
1522                 pTmp->GetPoint()->nContent.Assign( pContentNode,
1523                         (pContentNode) ? pContentNode->Len() : 0 );
1524             }
1525             // <--
1526         } while( pInsRing != ( pTmp = (SwPaM*)pTmp->GetNext() ));
1527         SwRedlineData aRedlnData( nsRedlineType_t::REDLINE_INSERT, nAuthor, aTimeStamp,
1528                                     aEmptyStr, 0, 0 );
1529 
1530         // zusammenhaengende zusammenfassen
1531         if( pTmp->GetNext() != pInsRing )
1532         {
1533             const SwCntntNode* pCNd;
1534             do {
1535                 SwPosition& rSttEnd = *pTmp->End(),
1536                           & rEndStt = *((SwPaM*)pTmp->GetNext())->Start();
1537                 if( rSttEnd == rEndStt ||
1538                     (!rEndStt.nContent.GetIndex() &&
1539                     rEndStt.nNode.GetIndex() - 1 == rSttEnd.nNode.GetIndex() &&
1540                     0 != ( pCNd = rSttEnd.nNode.GetNode().GetCntntNode() )
1541                         ? rSttEnd.nContent.GetIndex() == pCNd->Len()
1542                         : 0 ))
1543                 {
1544                     if( pTmp->GetNext() == pInsRing )
1545                     {
1546                         // liegen hintereinander also zusammen fassen
1547                         rEndStt = *pTmp->Start();
1548                         delete pTmp;
1549                         pTmp = pInsRing;
1550                     }
1551                     else
1552                     {
1553                         // liegen hintereinander also zusammen fassen
1554                         rSttEnd = *((SwPaM*)pTmp->GetNext())->End();
1555                         delete pTmp->GetNext();
1556                     }
1557                 }
1558                 else
1559                     pTmp = (SwPaM*)pTmp->GetNext();
1560             } while( pInsRing != pTmp );
1561         }
1562 
1563         do {
1564             if( rDoc.AppendRedline( new SwRedline( aRedlnData, *pTmp ), true) &&
1565                 rDoc.GetIDocumentUndoRedo().DoesUndo())
1566             {
1567                 SwUndo *const pUndo(new SwUndoCompDoc( *pTmp, sal_True ));
1568                 rDoc.GetIDocumentUndoRedo().AppendUndo(pUndo);
1569             }
1570         } while( pInsRing != ( pTmp = (SwPaM*)pTmp->GetNext() ));
1571     }
1572 }
1573 
1574 /*  */
1575 
1576 
1577 
1578     // returnt (?die Anzahl der Unterschiede?) ob etwas unterschiedlich ist
1579 long SwDoc::CompareDoc( const SwDoc& rDoc )
1580 {
1581     if( &rDoc == this )
1582         return 0;
1583 
1584     long nRet = 0;
1585 
1586     GetIDocumentUndoRedo().StartUndo(UNDO_EMPTY, NULL);
1587     sal_Bool bDocWasModified = IsModified();
1588     SwDoc& rSrcDoc = (SwDoc&)rDoc;
1589     sal_Bool bSrcModified = rSrcDoc.IsModified();
1590 
1591     RedlineMode_t eSrcRedlMode = rSrcDoc.GetRedlineMode();
1592     rSrcDoc.SetRedlineMode( nsRedlineMode_t::REDLINE_SHOW_INSERT );
1593     SetRedlineMode((RedlineMode_t)(nsRedlineMode_t::REDLINE_ON | nsRedlineMode_t::REDLINE_SHOW_INSERT));
1594 
1595     SwCompareData aD0( rSrcDoc );
1596     SwCompareData aD1( *this );
1597 
1598     aD1.CompareLines( aD0 );
1599 
1600     nRet = aD1.ShowDiffs( aD0 );
1601 
1602     if( nRet )
1603     {
1604       SetRedlineMode((RedlineMode_t)(nsRedlineMode_t::REDLINE_ON |
1605                        nsRedlineMode_t::REDLINE_SHOW_INSERT | nsRedlineMode_t::REDLINE_SHOW_DELETE));
1606 
1607         aD1.SetRedlinesToDoc( !bDocWasModified );
1608         SetModified();
1609     }
1610 
1611     rSrcDoc.SetRedlineMode( eSrcRedlMode );
1612     SetRedlineMode((RedlineMode_t)(nsRedlineMode_t::REDLINE_SHOW_INSERT | nsRedlineMode_t::REDLINE_SHOW_DELETE));
1613 
1614     if( !bSrcModified )
1615         rSrcDoc.ResetModified();
1616 
1617     GetIDocumentUndoRedo().EndUndo(UNDO_EMPTY, NULL);
1618 
1619     return nRet;
1620 }
1621 
1622 
1623 class _SaveMergeRedlines : public Ring
1624 {
1625     const SwRedline* pSrcRedl;
1626     SwRedline* pDestRedl;
1627 public:
1628     _SaveMergeRedlines( const SwNode& rDstNd,
1629                         const SwRedline& rSrcRedl, Ring* pRing );
1630     sal_uInt16 InsertRedline();
1631 
1632     SwRedline* GetDestRedline() { return pDestRedl; }
1633 };
1634 
1635 _SaveMergeRedlines::_SaveMergeRedlines( const SwNode& rDstNd,
1636                         const SwRedline& rSrcRedl, Ring* pRing )
1637     : Ring( pRing ), pSrcRedl( &rSrcRedl )
1638 {
1639     SwPosition aPos( rDstNd );
1640 
1641     const SwPosition* pStt = rSrcRedl.Start();
1642     if( rDstNd.IsCntntNode() )
1643         aPos.nContent.Assign( ((SwCntntNode*)&rDstNd), pStt->nContent.GetIndex() );
1644     pDestRedl = new SwRedline( rSrcRedl.GetRedlineData(), aPos );
1645 
1646     if( nsRedlineType_t::REDLINE_DELETE == pDestRedl->GetType() )
1647     {
1648         // den Bereich als geloescht kennzeichnen
1649         const SwPosition* pEnd = pStt == rSrcRedl.GetPoint()
1650                                             ? rSrcRedl.GetMark()
1651                                             : rSrcRedl.GetPoint();
1652 
1653         pDestRedl->SetMark();
1654         pDestRedl->GetPoint()->nNode += pEnd->nNode.GetIndex() -
1655                                         pStt->nNode.GetIndex();
1656         pDestRedl->GetPoint()->nContent.Assign( pDestRedl->GetCntntNode(),
1657                                                 pEnd->nContent.GetIndex() );
1658     }
1659 }
1660 
1661 sal_uInt16 _SaveMergeRedlines::InsertRedline()
1662 {
1663     sal_uInt16 nIns = 0;
1664     SwDoc* pDoc = pDestRedl->GetDoc();
1665 
1666     if( nsRedlineType_t::REDLINE_INSERT == pDestRedl->GetType() )
1667     {
1668         // der Teil wurde eingefuegt, also kopiere ihn aus dem SourceDoc
1669         ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo());
1670 
1671         SwNodeIndex aSaveNd( pDestRedl->GetPoint()->nNode, -1 );
1672         xub_StrLen nSaveCnt = pDestRedl->GetPoint()->nContent.GetIndex();
1673 
1674         RedlineMode_t eOld = pDoc->GetRedlineMode();
1675         pDoc->SetRedlineMode_intern((RedlineMode_t)(eOld | nsRedlineMode_t::REDLINE_IGNORE));
1676 
1677         pSrcRedl->GetDoc()->CopyRange(
1678                 *const_cast<SwPaM*>(static_cast<const SwPaM*>(pSrcRedl)),
1679                 *pDestRedl->GetPoint(), false );
1680 
1681         pDoc->SetRedlineMode_intern( eOld );
1682 
1683         pDestRedl->SetMark();
1684         aSaveNd++;
1685         pDestRedl->GetMark()->nNode = aSaveNd;
1686         pDestRedl->GetMark()->nContent.Assign( aSaveNd.GetNode().GetCntntNode(),
1687                                                 nSaveCnt );
1688 
1689         if( GetPrev() != this )
1690         {
1691             SwPaM* pTmpPrev = ((_SaveMergeRedlines*)GetPrev())->pDestRedl;
1692             if( pTmpPrev && *pTmpPrev->GetPoint() == *pDestRedl->GetPoint() )
1693                 *pTmpPrev->GetPoint() = *pDestRedl->GetMark();
1694         }
1695     }
1696     else
1697     {
1698         //JP 21.09.98: Bug 55909
1699         // falls im Doc auf gleicher Pos aber schon ein geloeschter oder
1700         // eingefuegter ist, dann muss dieser gesplittet werden!
1701         SwPosition* pDStt = pDestRedl->GetMark(),
1702                   * pDEnd = pDestRedl->GetPoint();
1703         sal_uInt16 n = 0;
1704 
1705             // zur StartPos das erste Redline suchen
1706         if( !pDoc->GetRedline( *pDStt, &n ) && n )
1707             --n;
1708 
1709         const SwRedlineTbl& rRedlineTbl = pDoc->GetRedlineTbl();
1710         for( ; n < rRedlineTbl.Count(); ++n )
1711         {
1712             SwRedline* pRedl = rRedlineTbl[ n ];
1713             SwPosition* pRStt = pRedl->Start(),
1714                       * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark()
1715                                                            : pRedl->GetPoint();
1716             if( nsRedlineType_t::REDLINE_DELETE == pRedl->GetType() ||
1717                 nsRedlineType_t::REDLINE_INSERT == pRedl->GetType() )
1718             {
1719                 SwComparePosition eCmpPos = ComparePosition( *pDStt, *pDEnd, *pRStt, *pREnd );
1720                 switch( eCmpPos )
1721                 {
1722                 case POS_COLLIDE_START:
1723                 case POS_BEHIND:
1724                     break;
1725 
1726                 case POS_INSIDE:
1727                 case POS_EQUAL:
1728                     delete pDestRedl, pDestRedl = 0;
1729                     // break; -> kein break !!!!
1730 
1731                 case POS_COLLIDE_END:
1732                 case POS_BEFORE:
1733                     n = rRedlineTbl.Count();
1734                     break;
1735 
1736                 case POS_OUTSIDE:
1737                     {
1738                         SwRedline* pCpyRedl = new SwRedline(
1739                             pDestRedl->GetRedlineData(), *pDStt );
1740                         pCpyRedl->SetMark();
1741                         *pCpyRedl->GetPoint() = *pRStt;
1742 
1743                         SwUndoCompDoc *const pUndo =
1744                             (pDoc->GetIDocumentUndoRedo().DoesUndo())
1745                                     ? new SwUndoCompDoc( *pCpyRedl ) : 0;
1746 
1747                         // now modify doc: append redline, undo (and count)
1748                         pDoc->AppendRedline( pCpyRedl, true );
1749                         if( pUndo )
1750                         {
1751                             pDoc->GetIDocumentUndoRedo().AppendUndo(pUndo);
1752                         }
1753                         ++nIns;
1754 
1755                         *pDStt = *pREnd;
1756 
1757                         // dann solle man neu anfangen
1758                         n = USHRT_MAX;
1759                     }
1760                     break;
1761 
1762                 case POS_OVERLAP_BEFORE:
1763                     *pDEnd = *pRStt;
1764                     break;
1765 
1766                 case POS_OVERLAP_BEHIND:
1767                     *pDStt = *pREnd;
1768                     break;
1769                 }
1770             }
1771             else if( *pDEnd <= *pRStt )
1772                 break;
1773         }
1774 
1775     }
1776 
1777     if( pDestRedl )
1778     {
1779         SwUndoCompDoc *const pUndo = (pDoc->GetIDocumentUndoRedo().DoesUndo())
1780             ? new SwUndoCompDoc( *pDestRedl ) : 0;
1781 
1782         // now modify doc: append redline, undo (and count)
1783         bool bRedlineAccepted = pDoc->AppendRedline( pDestRedl, true );
1784         if( pUndo )
1785         {
1786             pDoc->GetIDocumentUndoRedo().AppendUndo( pUndo );
1787         }
1788         ++nIns;
1789 
1790         // if AppendRedline has deleted our redline, we may not keep a
1791         // reference to it
1792         if( ! bRedlineAccepted )
1793             pDestRedl = NULL;
1794     }
1795     return nIns;
1796 }
1797 
1798 // merge zweier Dokumente
1799 long SwDoc::MergeDoc( const SwDoc& rDoc )
1800 {
1801     if( &rDoc == this )
1802         return 0;
1803 
1804     long nRet = 0;
1805 
1806     GetIDocumentUndoRedo().StartUndo(UNDO_EMPTY, NULL);
1807 
1808     SwDoc& rSrcDoc = (SwDoc&)rDoc;
1809     sal_Bool bSrcModified = rSrcDoc.IsModified();
1810 
1811     RedlineMode_t eSrcRedlMode = rSrcDoc.GetRedlineMode();
1812     rSrcDoc.SetRedlineMode( nsRedlineMode_t::REDLINE_SHOW_DELETE );
1813     SetRedlineMode( nsRedlineMode_t::REDLINE_SHOW_DELETE );
1814 
1815     SwCompareData aD0( rSrcDoc );
1816     SwCompareData aD1( *this );
1817 
1818     aD1.CompareLines( aD0 );
1819 
1820     if( !aD1.HasDiffs( aD0 ) )
1821     {
1822         // jetzt wollen wir alle Redlines aus dem SourceDoc zu uns bekommen
1823 
1824         // suche alle Insert - Redlines aus dem SourceDoc und bestimme
1825         // deren Position im DestDoc
1826         _SaveMergeRedlines* pRing = 0;
1827         const SwRedlineTbl& rSrcRedlTbl = rSrcDoc.GetRedlineTbl();
1828         sal_uLong nEndOfExtra = rSrcDoc.GetNodes().GetEndOfExtras().GetIndex();
1829         sal_uLong nMyEndOfExtra = GetNodes().GetEndOfExtras().GetIndex();
1830         for( sal_uInt16 n = 0; n < rSrcRedlTbl.Count(); ++n )
1831         {
1832             const SwRedline* pRedl = rSrcRedlTbl[ n ];
1833             sal_uLong nNd = pRedl->GetPoint()->nNode.GetIndex();
1834             RedlineType_t eType = pRedl->GetType();
1835             if( nEndOfExtra < nNd &&
1836                 ( nsRedlineType_t::REDLINE_INSERT == eType || nsRedlineType_t::REDLINE_DELETE == eType ))
1837             {
1838                 const SwNode* pDstNd = GetNodes()[
1839                                         nMyEndOfExtra + nNd - nEndOfExtra ];
1840 
1841                 // Position gefunden. Dann muss im DestDoc auch
1842                 // in der Line das Redline eingefuegt werden
1843                 _SaveMergeRedlines* pTmp = new _SaveMergeRedlines(
1844                                                     *pDstNd, *pRedl, pRing );
1845                 if( !pRing )
1846                     pRing = pTmp;
1847             }
1848         }
1849 
1850         if( pRing )
1851         {
1852             // dann alle ins DestDoc ueber nehmen
1853           rSrcDoc.SetRedlineMode((RedlineMode_t)(nsRedlineMode_t::REDLINE_SHOW_INSERT | nsRedlineMode_t::REDLINE_SHOW_DELETE));
1854 
1855           SetRedlineMode((RedlineMode_t)(
1856                                       nsRedlineMode_t::REDLINE_ON |
1857                                       nsRedlineMode_t::REDLINE_SHOW_INSERT |
1858                                       nsRedlineMode_t::REDLINE_SHOW_DELETE));
1859 
1860             _SaveMergeRedlines* pTmp = pRing;
1861 
1862             do {
1863                 nRet += pTmp->InsertRedline();
1864             } while( pRing != ( pTmp = (_SaveMergeRedlines*)pTmp->GetNext() ));
1865 
1866             while( pRing != pRing->GetNext() )
1867                 delete pRing->GetNext();
1868             delete pRing;
1869         }
1870     }
1871 
1872     rSrcDoc.SetRedlineMode( eSrcRedlMode );
1873     if( !bSrcModified )
1874         rSrcDoc.ResetModified();
1875 
1876     SetRedlineMode((RedlineMode_t)(nsRedlineMode_t::REDLINE_SHOW_INSERT | nsRedlineMode_t::REDLINE_SHOW_DELETE));
1877 
1878     GetIDocumentUndoRedo().EndUndo(UNDO_EMPTY, NULL);
1879 
1880     return nRet;
1881 }
1882 
1883 
1884