Mercurial > hg > easyhg-kdiff3
comparison kdiff3/src/diff.cpp @ 58:8af4bb9d9a5a
Version 0.9.83
author | joachim99 |
---|---|
date | Sun, 07 Mar 2004 09:59:09 +0000 |
parents | 32d5cbf9db71 |
children | efe33e938730 |
comparison
equal
deleted
inserted
replaced
57:023fbd76c1e3 | 58:8af4bb9d9a5a |
---|---|
1 /*************************************************************************** | 1 /*************************************************************************** |
2 diff.cpp - description | 2 diff.cpp - description |
3 ------------------- | 3 ------------------- |
4 begin : Mon Mar 18 2002 | 4 begin : Mon Mar 18 2002 |
5 copyright : (C) 2002 by Joachim Eibl | 5 copyright : (C) 2002-2004 by Joachim Eibl |
6 email : joachim.eibl@gmx.de | 6 email : joachim.eibl@gmx.de |
7 ***************************************************************************/ | 7 ***************************************************************************/ |
8 | 8 |
9 /*************************************************************************** | 9 /*************************************************************************** |
10 * * | 10 * * |
18 #include <stdio.h> | 18 #include <stdio.h> |
19 #include <iostream> | 19 #include <iostream> |
20 | 20 |
21 #include "diff.h" | 21 #include "diff.h" |
22 #include "fileaccess.h" | 22 #include "fileaccess.h" |
23 #include "optiondialog.h" | |
23 | 24 |
24 #include <kmessagebox.h> | 25 #include <kmessagebox.h> |
25 #include <klocale.h> | 26 #include <klocale.h> |
26 #include <qfileinfo.h> | 27 #include <qfileinfo.h> |
27 #include <qdir.h> | 28 #include <qdir.h> |
30 #include <assert.h> | 31 #include <assert.h> |
31 #include <ctype.h> | 32 #include <ctype.h> |
32 //using namespace std; | 33 //using namespace std; |
33 | 34 |
34 | 35 |
35 int LineData::width() | 36 int LineData::width() const |
36 { | 37 { |
37 int w=0; | 38 int w=0; |
38 int j=0; | 39 int j=0; |
39 for( int i=0; i<size; ++i ) | 40 for( int i=0; i<size; ++i ) |
40 { | 41 { |
108 return false; | 109 return false; |
109 } | 110 } |
110 } | 111 } |
111 | 112 |
112 | 113 |
113 // class needed during preprocess phase | 114 |
114 class LineDataRef | 115 |
115 { | 116 static bool isLineOrBufEnd( const char* p, int i, int size ) |
116 const LineData* m_pLd; | 117 { |
117 public: | 118 return |
118 LineDataRef(const LineData* pLd){ m_pLd = pLd; } | 119 i>=size // End of file |
119 | 120 || p[i]=='\n' // Normal end of line |
120 bool operator<(const LineDataRef& ldr2) const | 121 |
121 { | 122 // No support for Mac-end of line yet, because incompatible with GNU-diff-routines. |
122 const LineData* pLd1 = m_pLd; | 123 // || ( p[i]=='\r' && (i>=size-1 || p[i+1]!='\n') |
123 const LineData* pLd2 = ldr2.m_pLd; | 124 // && (i==0 || p[i-1]!='\n') ) // Special case: '\r' without '\n' |
124 const char* p1 = pLd1->pFirstNonWhiteChar; | 125 ; |
125 const char* p2 = pLd2->pFirstNonWhiteChar; | 126 } |
126 | 127 |
127 int size1=pLd1->size; | 128 |
128 int size2=pLd2->size; | 129 /* Features of class SourceData: |
129 | 130 - Read a file (from the given URL) or accept data via a string. |
130 int i1=min2(pLd1->pFirstNonWhiteChar - pLd1->pLine,size1); | 131 - Allocate and free buffers as necessary. |
131 int i2=min2(pLd2->pFirstNonWhiteChar - pLd2->pLine,size2); | 132 - Run a preprocessor, when specified. |
132 for(;;) | 133 - Run the line-matching preprocessor, when specified. |
133 { | 134 - Run other preprocessing steps: Uppercase, ignore comments, |
134 while( i1<size1 && isWhite( p1[i1] ) ) ++i1; | 135 remove carriage return, ignore numbers. |
135 while( i2<size2 && isWhite( p2[i2] ) ) ++i2; | 136 |
136 if ( i1==size1 || i2==size2 ) | 137 Order of operation: |
137 { | 138 1. If data was given via a string then save it to a temp file. (see setData()) |
138 if ( i1==size1 && i2==size2 ) return false; // Equal | 139 2. If the specified file is nonlocal (URL) copy it to a temp file. |
139 if ( i1==size1 ) return true; // String 1 is shorter than string 2 | 140 3. If a preprocessor was specified, run the input file through it. |
140 if ( i2==size2 ) return false; // String 1 is longer than string 2 | 141 4. Read the output of the preprocessor. |
141 } | 142 5. If Uppercase was specified: Turn the read data to uppercase. |
142 if ( p1[i1]==p2[i2] ) { ++i1; ++i2; continue; } | 143 6. Write the result to a temp file. |
143 return p1[i1]<p2[i2]; | 144 7. If a line-matching preprocessor was specified, run the temp file through it. |
144 } | 145 8. Read the output of the line-matching preprocessor. |
145 } | 146 9. If ignore numbers was specified, strip the LMPP-output of all numbers. |
146 }; | 147 10. If ignore comments was specified, strip the LMPP-output of comments. |
148 | |
149 Optimizations: Skip unneeded steps. | |
150 */ | |
151 | |
152 SourceData::SourceData() | |
153 { | |
154 m_pOptionDialog = 0; | |
155 reset(); | |
156 } | |
157 | |
158 SourceData::~SourceData() | |
159 { | |
160 reset(); | |
161 } | |
147 | 162 |
148 void SourceData::reset() | 163 void SourceData::reset() |
164 { | |
165 m_normalData.reset(); | |
166 m_lmppData.reset(); | |
167 if ( !m_tempInputFileName.isEmpty() ) | |
168 { | |
169 FileAccess::removeFile( m_tempInputFileName ); | |
170 m_tempInputFileName = ""; | |
171 } | |
172 } | |
173 | |
174 void SourceData::setFilename( const QString& filename ) | |
175 { | |
176 if (filename.isEmpty()) | |
177 { | |
178 reset(); | |
179 } | |
180 else | |
181 { | |
182 FileAccess fa( filename ); | |
183 setFileAccess( fa ); | |
184 } | |
185 } | |
186 | |
187 bool SourceData::isEmpty() | |
188 { | |
189 return getFilename().isEmpty(); | |
190 } | |
191 | |
192 bool SourceData::hasData() | |
193 { | |
194 return m_normalData.m_pBuf != 0; | |
195 } | |
196 | |
197 void SourceData::setOptionDialog( OptionDialog* pOptionDialog ) | |
198 { | |
199 m_pOptionDialog = pOptionDialog; | |
200 } | |
201 | |
202 QString SourceData::getFilename() | |
203 { | |
204 return m_fileAccess.absFilePath(); | |
205 } | |
206 | |
207 QString SourceData::getAliasName() | |
208 { | |
209 return m_aliasName.isEmpty() ? m_fileAccess.prettyAbsPath() : m_aliasName; | |
210 } | |
211 | |
212 void SourceData::setAliasName( const QString& name ) | |
213 { | |
214 m_aliasName = name; | |
215 } | |
216 | |
217 void SourceData::setFileAccess( const FileAccess& fileAccess ) | |
218 { | |
219 m_fileAccess = fileAccess; | |
220 m_aliasName = QString(); | |
221 if ( !m_tempInputFileName.isEmpty() ) | |
222 { | |
223 FileAccess::removeFile( m_tempInputFileName ); | |
224 m_tempInputFileName = ""; | |
225 } | |
226 } | |
227 | |
228 void SourceData::setData( const QString& data ) | |
229 { | |
230 // Create a temp file for preprocessing: | |
231 if ( m_tempInputFileName.isEmpty() ) | |
232 { | |
233 m_tempInputFileName = FileAccess::tempFileName(); | |
234 } | |
235 | |
236 FileAccess f( m_tempInputFileName ); | |
237 bool bSuccess = f.writeFile( encodeString(data, m_pOptionDialog), data.length() ); | |
238 if ( !bSuccess ) | |
239 { | |
240 KMessageBox::error( m_pOptionDialog, i18n("Writing clipboard data to temp file failed.") ); | |
241 return; | |
242 } | |
243 | |
244 m_aliasName = i18n("From Clipboard"); | |
245 m_fileAccess = FileAccess(""); // Effect: m_fileAccess.isValid() is false | |
246 } | |
247 | |
248 const LineData* SourceData::getLineDataForDiff() const | |
249 { | |
250 return m_lmppData.m_pBuf==0 ? &m_normalData.m_v[0] : &m_lmppData.m_v[0]; | |
251 } | |
252 | |
253 const LineData* SourceData::getLineDataForDisplay() const | |
254 { | |
255 return &m_normalData.m_v[0]; | |
256 } | |
257 | |
258 int SourceData::getSizeLines() const | |
259 { | |
260 return m_normalData.m_vSize; | |
261 } | |
262 | |
263 int SourceData::getSizeBytes() const | |
264 { | |
265 return m_normalData.m_size; | |
266 } | |
267 | |
268 const char* SourceData::getBuf() const | |
269 { | |
270 return m_normalData.m_pBuf; | |
271 } | |
272 | |
273 bool SourceData::isText() | |
274 { | |
275 return m_normalData.m_bIsText; | |
276 } | |
277 | |
278 bool SourceData::isFromBuffer() | |
279 { | |
280 return !m_fileAccess.isValid(); | |
281 } | |
282 | |
283 | |
284 bool SourceData::isBinaryEqualWith( const SourceData& other ) const | |
285 { | |
286 return getSizeBytes() == other.getSizeBytes() && memcmp( getBuf(), other.getBuf(), getSizeBytes() )==0; | |
287 } | |
288 | |
289 void SourceData::FileData::reset() | |
149 { | 290 { |
150 delete (char*)m_pBuf; | 291 delete (char*)m_pBuf; |
151 m_pBuf = 0; | 292 m_pBuf = 0; |
152 m_v.clear(); | 293 m_v.clear(); |
153 m_size = 0; | 294 m_size = 0; |
154 m_vSize = 0; | 295 m_vSize = 0; |
155 m_bIsText = false; | 296 m_bIsText = true; |
156 m_bPreserve = false; | 297 } |
157 m_fileAccess = FileAccess(""); | 298 |
158 } | 299 bool SourceData::FileData::readFile( const QString& filename ) |
300 { | |
301 reset(); | |
302 if ( filename.isEmpty() ) { return true; } | |
303 | |
304 FileAccess fa( filename ); | |
305 m_size = fa.sizeForReading(); | |
306 char* pBuf; | |
307 m_pBuf = pBuf = new char[m_size+100]; // Alloc 100 byte extra: Savety hack, not nice but does no harm. | |
308 bool bSuccess = fa.readFile( pBuf, m_size ); | |
309 if ( !bSuccess ) | |
310 { | |
311 delete pBuf; | |
312 m_pBuf = 0; | |
313 m_size = 0; | |
314 } | |
315 return bSuccess; | |
316 } | |
317 | |
318 bool SourceData::saveNormalDataAs( const QString& fileName ) | |
319 { | |
320 return m_normalData.writeFile( fileName ); | |
321 } | |
322 | |
323 bool SourceData::FileData::writeFile( const QString& filename ) | |
324 { | |
325 if ( filename.isEmpty() ) { return true; } | |
326 | |
327 FileAccess fa( filename ); | |
328 bool bSuccess = fa.writeFile(m_pBuf, m_size); | |
329 return bSuccess; | |
330 } | |
331 | |
332 void SourceData::FileData::copyBufFrom( const FileData& src ) | |
333 { | |
334 reset(); | |
335 char* pBuf; | |
336 m_size = src.m_size; | |
337 m_pBuf = pBuf = new char[m_size+100]; | |
338 memcpy( pBuf, src.m_pBuf, m_size ); | |
339 } | |
340 | |
341 void SourceData::readAndPreprocess() | |
342 { | |
343 QString fileNameIn1; | |
344 QString fileNameOut1; | |
345 QString fileNameIn2; | |
346 QString fileNameOut2; | |
347 | |
348 bool bTempFileFromClipboard = !m_fileAccess.isValid(); | |
349 | |
350 // Detect the input for the preprocessing operations | |
351 if ( !bTempFileFromClipboard ) | |
352 { | |
353 if ( m_fileAccess.isLocal() ) | |
354 { | |
355 fileNameIn1 = m_fileAccess.absFilePath(); | |
356 } | |
357 else // File is not local: create a temporary local copy: | |
358 { | |
359 if ( m_tempInputFileName.isEmpty() ) { m_tempInputFileName = FileAccess::tempFileName(); } | |
360 | |
361 m_fileAccess.copyFile(m_tempInputFileName); | |
362 fileNameIn1 = m_tempInputFileName; | |
363 } | |
364 } | |
365 else // The input was set via setData(), probably from clipboard. | |
366 { | |
367 fileNameIn1 = m_tempInputFileName; | |
368 } | |
369 | |
370 m_normalData.reset(); | |
371 m_lmppData.reset(); | |
372 | |
373 FileAccess faIn(fileNameIn1); | |
374 int fileInSize = faIn.size(); | |
375 | |
376 if ( fileInSize > 0 ) | |
377 { | |
378 | |
379 #ifdef _WIN32 | |
380 QString catCmd = "type"; | |
381 fileNameIn1.replace( '/', "\\" ); | |
382 #else | |
383 QString catCmd = "cat"; | |
384 #endif | |
385 | |
386 // Run the first preprocessor | |
387 if ( m_pOptionDialog->m_PreProcessorCmd.isEmpty() ) | |
388 { | |
389 // No preprocessing: Read the file directly: | |
390 m_normalData.readFile( fileNameIn1 ); | |
391 } | |
392 else | |
393 { | |
394 QString ppCmd = m_pOptionDialog->m_PreProcessorCmd; | |
395 fileNameOut1 = FileAccess::tempFileName(); | |
396 QString cmd = catCmd + " \"" + fileNameIn1 + "\" | " + ppCmd + " >\"" + fileNameOut1+"\""; | |
397 ::system( encodeString(cmd, m_pOptionDialog) ); | |
398 bool bSuccess = m_normalData.readFile( fileNameOut1 ); | |
399 if ( fileInSize >0 && ( !bSuccess || m_normalData.m_size==0 ) ) | |
400 { | |
401 KMessageBox::error(m_pOptionDialog, | |
402 i18n("Preprocessing possibly failed. Check this command:\n\n %1" | |
403 "\n\nThe preprocessing command will be disabled now." | |
404 ).arg(cmd) ); | |
405 m_pOptionDialog->m_PreProcessorCmd = ""; | |
406 m_normalData.readFile( fileNameIn1 ); | |
407 } | |
408 } | |
409 | |
410 // Internal Preprocessing: Uppercase-conversion | |
411 bool bInternalPreprocessing = false; | |
412 if ( m_pOptionDialog->m_bUpCase ) | |
413 { | |
414 int i; | |
415 char* pBuf = const_cast<char*>(m_normalData.m_pBuf); | |
416 for(i=0; i<m_normalData.m_size; ++i) | |
417 { | |
418 pBuf[i] = toupper(pBuf[i]); | |
419 } | |
420 | |
421 bInternalPreprocessing = true; | |
422 } | |
423 | |
424 // LineMatching Preprocessor | |
425 if ( ! m_pOptionDialog->m_LineMatchingPreProcessorCmd.isEmpty() ) | |
426 { | |
427 if ( bInternalPreprocessing ) | |
428 { | |
429 // write data to file after internal preprocessing before running the external LMPP-cmd. | |
430 if ( !fileNameOut1.isEmpty() ) | |
431 { | |
432 FileAccess::removeFile( fileNameOut1 ); | |
433 fileNameOut1=""; | |
434 } | |
435 | |
436 fileNameIn2 = FileAccess::tempFileName(); | |
437 bool bSuccess = m_normalData.writeFile( fileNameIn2 ); | |
438 if ( !bSuccess ) | |
439 { | |
440 KMessageBox::error(m_pOptionDialog, i18n("Error writing temporary file: %1").arg(fileNameIn2) ); | |
441 } | |
442 } | |
443 else | |
444 { | |
445 fileNameIn2 = fileNameOut1.isEmpty() ? fileNameIn1 : fileNameOut1; | |
446 } | |
447 | |
448 QString ppCmd = m_pOptionDialog->m_LineMatchingPreProcessorCmd; | |
449 fileNameOut2 = FileAccess::tempFileName(); | |
450 QString cmd = catCmd + " \"" + fileNameIn2 + "\" | " + ppCmd + " >\"" + fileNameOut2 + "\""; | |
451 ::system( encodeString(cmd, m_pOptionDialog) ); | |
452 bool bSuccess = m_lmppData.readFile( fileNameOut2 ); | |
453 if ( FileAccess(fileNameIn2).size()>0 && ( !bSuccess || m_lmppData.m_size==0 ) ) | |
454 { | |
455 KMessageBox::error(m_pOptionDialog, | |
456 i18n("The line-matching-preprocessing possibly failed. Check this command:\n\n %1" | |
457 "\n\nThe line-matching-preprocessing command will be disabled now." | |
458 ).arg(cmd) ); | |
459 m_pOptionDialog->m_LineMatchingPreProcessorCmd = ""; | |
460 m_lmppData.readFile( fileNameIn2 ); | |
461 } | |
462 FileAccess::removeFile( fileNameOut2 ); | |
463 | |
464 if ( bInternalPreprocessing && !fileNameIn2.isEmpty() ) | |
465 { | |
466 FileAccess::removeFile( fileNameIn2 ); | |
467 fileNameIn2=""; | |
468 } | |
469 } | |
470 else if ( m_pOptionDialog->m_bIgnoreComments ) | |
471 { | |
472 // We need a copy of the normal data. | |
473 m_lmppData.copyBufFrom( m_normalData ); | |
474 } | |
475 else | |
476 { // We don't need any lmpp data at all. | |
477 m_lmppData.reset(); | |
478 } | |
479 } | |
480 | |
481 m_normalData.preprocess( m_pOptionDialog->m_bPreserveCarriageReturn ); | |
482 m_lmppData.preprocess( false ); | |
483 | |
484 if ( m_lmppData.m_vSize < m_normalData.m_vSize ) | |
485 { | |
486 // This probably is the fault of the LMPP-Command, but not worth reporting. | |
487 m_lmppData.m_v.resize( m_normalData.m_vSize ); | |
488 for(int i=m_lmppData.m_vSize; i<m_normalData.m_vSize; ++i ) | |
489 { // Set all empty lines to point to the end of the buffer. | |
490 m_lmppData.m_v[i].pLine = m_lmppData.m_pBuf+m_lmppData.m_size; | |
491 } | |
492 | |
493 m_lmppData.m_vSize = m_normalData.m_vSize; | |
494 } | |
495 | |
496 // Ignore comments | |
497 if ( m_pOptionDialog->m_bIgnoreComments ) | |
498 { | |
499 m_lmppData.removeComments(); | |
500 int vSize = min2(m_normalData.m_vSize, m_lmppData.m_vSize); | |
501 for(int i=0; i<vSize; ++i ) | |
502 { | |
503 m_normalData.m_v[i].bContainsPureComment = m_lmppData.m_v[i].bContainsPureComment; | |
504 } | |
505 } | |
506 | |
507 // Remove unneeded temporary files. (A temp file from clipboard must not be deleted.) | |
508 if ( !bTempFileFromClipboard && !m_tempInputFileName.isEmpty() ) | |
509 { | |
510 FileAccess::removeFile( m_tempInputFileName ); | |
511 m_tempInputFileName = ""; | |
512 } | |
513 | |
514 if ( !fileNameOut1.isEmpty() ) | |
515 { | |
516 FileAccess::removeFile( fileNameOut1 ); | |
517 fileNameOut1=""; | |
518 } | |
519 } | |
520 | |
159 | 521 |
160 /** Prepare the linedata vector for every input line.*/ | 522 /** Prepare the linedata vector for every input line.*/ |
161 void SourceData::preprocess( bool bPreserveCR ) | 523 void SourceData::FileData::preprocess( bool bPreserveCR ) |
162 { | 524 { |
163 const char* p = m_pBuf; | 525 const char* p = m_pBuf; |
164 m_bIsText = true; | 526 m_bIsText = true; |
165 int lines = 1; | 527 int lines = 1; |
166 | |
167 int i; | 528 int i; |
168 for( i=0; i<m_size; ++i ) | 529 for( i=0; i<m_size; ++i ) |
169 { | 530 { |
170 if (p[i]=='\n') | 531 if ( isLineOrBufEnd(p,i,m_size) ) |
171 { | 532 { |
172 ++lines; | 533 ++lines; |
173 } | 534 } |
174 if ( p[i]=='\0' ) | 535 if ( p[i]=='\0' ) |
175 { | 536 { |
182 int lineLength=0; | 543 int lineLength=0; |
183 bool bNonWhiteFound = false; | 544 bool bNonWhiteFound = false; |
184 int whiteLength = 0; | 545 int whiteLength = 0; |
185 for( i=0; i<=m_size; ++i ) | 546 for( i=0; i<=m_size; ++i ) |
186 { | 547 { |
187 if ( i==m_size || p[i]=='\n' ) // The last line does not end with a linefeed. | 548 if ( isLineOrBufEnd( p, i, m_size ) ) |
188 { | 549 { |
189 m_v[lineIdx].pLine = &p[ i-lineLength ]; | 550 m_v[lineIdx].pLine = &p[ i-lineLength ]; |
190 while ( !bPreserveCR && lineLength>0 && m_v[lineIdx].pLine[lineLength-1]=='\r' ) | 551 while ( !bPreserveCR && lineLength>0 && m_v[lineIdx].pLine[lineLength-1]=='\r' ) |
191 { | 552 { |
192 --lineLength; | 553 --lineLength; |
235 // (if not in a string) | 596 // (if not in a string) |
236 if ( p[i]=='\'' ) | 597 if ( p[i]=='\'' ) |
237 { | 598 { |
238 bWhite = false; | 599 bWhite = false; |
239 ++i; | 600 ++i; |
240 for( ; i<size && p[i]!='\'' && p[i]!='\n'; ++i) | 601 for( ; !isLineOrBufEnd(p,i,size) && p[i]!='\''; ++i) |
241 ; | 602 ; |
242 if (p[i]=='\'') ++i; | 603 if (p[i]=='\'') ++i; |
243 } | 604 } |
244 | 605 |
245 // Strings have priority over comments: e.g. "/* Not a comment, but a string. */" | 606 // Strings have priority over comments: e.g. "/* Not a comment, but a string. */" |
246 else if ( p[i]=='"' ) | 607 else if ( p[i]=='"' ) |
247 { | 608 { |
248 bWhite = false; | 609 bWhite = false; |
249 ++i; | 610 ++i; |
250 for( ; i<size && !(p[i]=='"' && p[i-1]!='\\') && p[i]!='\n'; ++i) | 611 for( ; !isLineOrBufEnd(p,i,size) && !(p[i]=='"' && p[i-1]!='\\'); ++i) |
251 ; | 612 ; |
252 if (p[i]=='"') ++i; | 613 if (p[i]=='"') ++i; |
253 } | 614 } |
254 | 615 |
255 // C++-comment | 616 // C++-comment |
256 else if ( p[i]=='/' && i+1<size && p[i+1] =='/' ) | 617 else if ( p[i]=='/' && i+1<size && p[i+1] =='/' ) |
257 { | 618 { |
258 int commentStart = i; | 619 int commentStart = i; |
259 bCommentInLine = true; | 620 bCommentInLine = true; |
260 i+=2; | 621 i+=2; |
261 for( ; i<size && p[i]!='\n'; ++i) | 622 for( ; !isLineOrBufEnd(p,i,size); ++i) |
262 ; | 623 ; |
263 if ( !bWhite ) | 624 if ( !bWhite ) |
264 { | 625 { |
265 memset( &p[commentStart], ' ', i-commentStart ); | 626 memset( &p[commentStart], ' ', i-commentStart ); |
266 } | 627 } |
271 else if ( p[i]=='/' && i+1<size && p[i+1] =='*' ) | 632 else if ( p[i]=='/' && i+1<size && p[i+1] =='*' ) |
272 { | 633 { |
273 int commentStart = i; | 634 int commentStart = i; |
274 bCommentInLine = true; | 635 bCommentInLine = true; |
275 i+=2; | 636 i+=2; |
276 for( ; i<size && p[i]!='\n'; ++i) | 637 for( ; !isLineOrBufEnd(p,i,size); ++i) |
277 { | 638 { |
278 if ( i+1<size && p[i]=='*' && p[i+1]=='/') // end of the comment | 639 if ( i+1<size && p[i]=='*' && p[i+1]=='/') // end of the comment |
279 { | 640 { |
280 i+=2; | 641 i+=2; |
281 | 642 |
291 bStartsOpenComment = true; | 652 bStartsOpenComment = true; |
292 return; | 653 return; |
293 } | 654 } |
294 | 655 |
295 | 656 |
296 if (p[i]=='\n' || i>=size ) | 657 if ( isLineOrBufEnd(p,i,size) ) |
297 { | 658 { |
298 return; | 659 return; |
299 } | 660 } |
300 else if ( !isspace(p[i]) ) | 661 else if ( !isspace(p[i]) ) |
301 { | 662 { |
305 } | 666 } |
306 | 667 |
307 // Modifies the input data, and replaces C/C++ comments with whitespace | 668 // Modifies the input data, and replaces C/C++ comments with whitespace |
308 // when the line contains other data too. If the line contains only | 669 // when the line contains other data too. If the line contains only |
309 // a comment or white data, remember this in the flag bContainsPureComment. | 670 // a comment or white data, remember this in the flag bContainsPureComment. |
310 void SourceData::removeComments( LineData* pLD ) | 671 void SourceData::FileData::removeComments() |
311 { | 672 { |
312 int line=0; | 673 int line=0; |
313 char* p = (char*)m_pBuf; | 674 char* p = (char*)m_pBuf; |
314 bool bWithinComment=false; | 675 bool bWithinComment=false; |
315 int size = m_size; | 676 int size = m_size; |
322 if ( bWithinComment ) | 683 if ( bWithinComment ) |
323 { | 684 { |
324 int commentStart = i; | 685 int commentStart = i; |
325 bCommentInLine = true; | 686 bCommentInLine = true; |
326 | 687 |
327 for( ; i<size && p[i]!='\n'; ++i) | 688 for( ; !isLineOrBufEnd(p,i,size); ++i) |
328 { | 689 { |
329 if ( i+1<size && p[i]=='*' && p[i+1]=='/') // end of the comment | 690 if ( i+1<size && p[i]=='*' && p[i+1]=='/') // end of the comment |
330 { | 691 { |
331 i+=2; | 692 i+=2; |
332 | 693 |
344 { | 705 { |
345 checkLineForComments( p, i, size, bWhite, bCommentInLine, bWithinComment ); | 706 checkLineForComments( p, i, size, bWhite, bCommentInLine, bWithinComment ); |
346 } | 707 } |
347 | 708 |
348 // end of line | 709 // end of line |
349 assert( i>=size || p[i]=='\n'); | 710 assert( isLineOrBufEnd(p,i,size)); |
350 pLD[line].bContainsPureComment = bCommentInLine && bWhite; | 711 m_v[line].bContainsPureComment = bCommentInLine && bWhite; |
351 /* std::cout << line << " : " << | 712 /* std::cout << line << " : " << |
352 ( bCommentInLine ? "c" : " " ) << | 713 ( bCommentInLine ? "c" : " " ) << |
353 ( bWhite ? "w " : " ") << | 714 ( bWhite ? "w " : " ") << |
354 std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/ | 715 std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/ |
355 | 716 |
356 ++line; | 717 ++line; |
357 } | 718 } |
358 } | 719 } |
359 | 720 |
360 // read and preprocess file for line matching input data | 721 |
361 void SourceData::readLMPPFile( SourceData* pOrigSource, const QString& ppCmd, bool bUpCase, bool bRemoveComments ) | |
362 { | |
363 if ( ( ppCmd.isEmpty() && !bRemoveComments ) || pOrigSource->m_bPreserve ) | |
364 { | |
365 reset(); | |
366 } | |
367 else | |
368 { | |
369 setFilename( pOrigSource->m_fileAccess.absFilePath() ); | |
370 readPPFile( false, ppCmd, bUpCase ); | |
371 if ( m_vSize < pOrigSource->m_vSize ) | |
372 { | |
373 m_v.resize( pOrigSource->m_vSize ); | |
374 m_vSize = pOrigSource->m_vSize; | |
375 } | |
376 } | |
377 if ( bRemoveComments && m_vSize==pOrigSource->m_vSize ) | |
378 removeComments( &pOrigSource->m_v[0] ); | |
379 } | |
380 | |
381 | |
382 void SourceData::readPPFile( bool bPreserveCR, const QString& ppCmd, bool bUpCase ) | |
383 { | |
384 if ( !m_bPreserve ) | |
385 { | |
386 if ( ! ppCmd.isEmpty() && !m_fileName.isEmpty() && FileAccess::exists( m_fileName ) ) | |
387 { | |
388 QString fileNameOut = FileAccess::tempFileName(); | |
389 #ifdef _WIN32 | |
390 QString cmd = QString("type ") + m_fileName + " | " + ppCmd + " >" + fileNameOut; | |
391 #else | |
392 QString cmd = QString("cat ") + m_fileName + " | " + ppCmd + " >" + fileNameOut; | |
393 #endif | |
394 ::system( cmd.ascii() ); | |
395 readFile( fileNameOut, true, bUpCase ); | |
396 FileAccess::removeFile( fileNameOut ); | |
397 } | |
398 else | |
399 { | |
400 readFile( m_fileAccess.absFilePath(), true, bUpCase ); | |
401 } | |
402 } | |
403 preprocess( bPreserveCR ); | |
404 } | |
405 | |
406 void SourceData::readFile( const QString& filename, bool bFollowLinks, bool bUpCase ) | |
407 { | |
408 delete (char*)m_pBuf; | |
409 m_size = 0; | |
410 m_pBuf = 0; | |
411 char* pBuf = 0; | |
412 if ( filename.isEmpty() ) { return; } | |
413 | |
414 if ( !bFollowLinks ) | |
415 { | |
416 FileAccess fi( filename ); | |
417 if ( fi.isSymLink() ) | |
418 { | |
419 QString s = fi.readLink(); | |
420 m_size = s.length(); | |
421 m_pBuf = pBuf = new char[m_size+100]; | |
422 memcpy( pBuf, s.ascii(), m_size ); | |
423 return; | |
424 } | |
425 } | |
426 | |
427 | |
428 FileAccess fa( filename ); | |
429 m_size = fa.sizeForReading(); | |
430 m_pBuf = pBuf = new char[m_size+100]; | |
431 bool bSuccess = fa.readFile( pBuf, m_size ); | |
432 if ( !bSuccess ) | |
433 { | |
434 delete pBuf; | |
435 m_pBuf = 0; | |
436 m_size = 0; | |
437 return; | |
438 } | |
439 | |
440 | |
441 if ( bUpCase ) | |
442 { | |
443 int i; | |
444 for(i=0; i<m_size; ++i) | |
445 { | |
446 pBuf[i] = toupper(pBuf[i]); | |
447 } | |
448 } | |
449 | |
450 } | |
451 | |
452 void SourceData::setData( const QString& data, bool bUpCase ) | |
453 { | |
454 delete (char*)m_pBuf; | |
455 m_size = data.length(); | |
456 m_pBuf = 0; | |
457 | |
458 char* pBuf = 0; | |
459 m_pBuf = pBuf = new char[m_size+100]; | |
460 | |
461 memcpy( pBuf, data.ascii(), m_size ); | |
462 if ( bUpCase ) | |
463 { | |
464 int i; | |
465 for(i=0; i<m_size; ++i) | |
466 { | |
467 pBuf[i] = toupper(pBuf[i]); | |
468 } | |
469 } | |
470 m_bPreserve = true; | |
471 m_fileName=""; | |
472 m_aliasName = i18n("From Clipboard"); | |
473 m_fileAccess = FileAccess(""); | |
474 } | |
475 | |
476 void SourceData::setFilename( const QString& filename ) | |
477 { | |
478 FileAccess fa( filename ); | |
479 setFileAccess( fa ); | |
480 } | |
481 | |
482 QString SourceData::getFilename() | |
483 { | |
484 return m_fileName; | |
485 } | |
486 | |
487 QString SourceData::getAliasName() | |
488 { | |
489 return m_aliasName.isEmpty() ? m_fileAccess.prettyAbsPath() : m_aliasName; | |
490 } | |
491 | |
492 void SourceData::setAliasName( const QString& name ) | |
493 { | |
494 m_aliasName = name; | |
495 } | |
496 | |
497 void SourceData::setFileAccess( const FileAccess& fileAccess ) | |
498 { | |
499 m_fileAccess = fileAccess; | |
500 m_aliasName = QString(); | |
501 m_bPreserve = false; | |
502 m_fileName = m_fileAccess.absFilePath(); | |
503 } | |
504 | |
505 void prepareOccurances( LineData* p, int size ) | |
506 { | |
507 // Special analysis: Find out how often this line occurs | |
508 // Only problem: A simple search will cost O(N^2). | |
509 // To avoid this we will use a map. Then the cost will only be | |
510 // O(N*log N). (A hash table would be even better.) | |
511 | |
512 std::map<LineDataRef,int> occurancesMap; | |
513 int i; | |
514 for( i=0; i<size; ++i ) | |
515 { | |
516 ++occurancesMap[ LineDataRef( &p[i] ) ]; | |
517 } | |
518 | |
519 for( i=0; i<size; ++i ) | |
520 { | |
521 p[i].occurances = occurancesMap[ LineDataRef( &p[i] ) ]; | |
522 } | |
523 } | |
524 | 722 |
525 // First step | 723 // First step |
526 void calcDiff3LineListUsingAB( | 724 void calcDiff3LineListUsingAB( |
527 const DiffList* pDiffListAB, | 725 const DiffList* pDiffListAB, |
528 Diff3LineList& d3ll | 726 Diff3LineList& d3ll |
578 d3l.lineB = lineB; | 776 d3l.lineB = lineB; |
579 --d.diff2; | 777 --d.diff2; |
580 ++lineB; | 778 ++lineB; |
581 } | 779 } |
582 | 780 |
583 d3ll.push_back( d3l ); | 781 d3ll.push_back( d3l ); |
584 } | 782 } |
585 } | 783 } |
586 | 784 |
587 | 785 |
588 // Second step | 786 // Second step |
589 void calcDiff3LineListUsingAC( | 787 void calcDiff3LineListUsingAC( |
590 const DiffList* pDiffListAC, | 788 const DiffList* pDiffListAC, |
591 Diff3LineList& d3ll | 789 Diff3LineList& d3ll |
592 ) | 790 ) |
593 { | 791 { |
594 //////////////// | 792 //////////////// |
615 | 813 |
616 Diff3Line d3l; | 814 Diff3Line d3l; |
617 if( d.nofEquals>0 ) | 815 if( d.nofEquals>0 ) |
618 { | 816 { |
619 // Find the corresponding lineA | 817 // Find the corresponding lineA |
620 while( (*i3).lineA!=lineA ) | 818 while( (*i3).lineA!=lineA ) |
621 | 819 |
622 ++i3; | 820 ++i3; |
623 (*i3).lineC = lineC; | 821 (*i3).lineC = lineC; |
624 (*i3).bAEqC = true; | 822 (*i3).bAEqC = true; |
625 (*i3).bBEqC = (*i3).bAEqB; | 823 (*i3).bBEqC = (*i3).bAEqB; |
626 | 824 |
627 --d.nofEquals; | 825 --d.nofEquals; |
628 ++lineA; | 826 ++lineA; |
629 ++lineC; | 827 ++lineC; |
630 ++i3; | 828 ++i3; |
631 } | 829 } |
653 } | 851 } |
654 } | 852 } |
655 } | 853 } |
656 | 854 |
657 // Third step | 855 // Third step |
658 void calcDiff3LineListUsingBC( | 856 void calcDiff3LineListUsingBC( |
659 const DiffList* pDiffListBC, | 857 const DiffList* pDiffListBC, |
660 Diff3LineList& d3ll | 858 Diff3LineList& d3ll |
661 ) | 859 ) |
662 { | 860 { |
663 //////////////// | 861 //////////////// |
726 Diff3LineList::iterator i3 = i3c; | 924 Diff3LineList::iterator i3 = i3c; |
727 int nofDisturbingLines = 0; | 925 int nofDisturbingLines = 0; |
728 while( i3 != i3b && i3!=d3ll.end() ) | 926 while( i3 != i3b && i3!=d3ll.end() ) |
729 | 927 |
730 { | 928 { |
731 if ( (*i3).lineB != -1 ) | 929 if ( (*i3).lineB != -1 ) |
732 ++nofDisturbingLines; | 930 ++nofDisturbingLines; |
733 ++i3; | 931 ++i3; |
734 } | 932 } |
735 | 933 |
736 if ( nofDisturbingLines>0 && nofDisturbingLines < d.nofEquals ) | 934 if ( nofDisturbingLines>0 && nofDisturbingLines < d.nofEquals ) |
738 // Move the disturbing lines up, out of sight. | 936 // Move the disturbing lines up, out of sight. |
739 i3 = i3c; | 937 i3 = i3c; |
740 | 938 |
741 while( i3 != i3b ) | 939 while( i3 != i3b ) |
742 { | 940 { |
743 if ( (*i3).lineB != -1 ) | 941 if ( (*i3).lineB != -1 ) |
744 { | 942 { |
745 Diff3Line d3l; | 943 Diff3Line d3l; |
746 d3l.lineB = (*i3).lineB; | 944 d3l.lineB = (*i3).lineB; |
747 (*i3).lineB = -1; | 945 (*i3).lineB = -1; |
748 | 946 |
776 { | 974 { |
777 Diff3LineList::iterator i3 = i3b; | 975 Diff3LineList::iterator i3 = i3b; |
778 int nofDisturbingLines = 0; | 976 int nofDisturbingLines = 0; |
779 while( i3 != i3c && i3!=d3ll.end() ) | 977 while( i3 != i3c && i3!=d3ll.end() ) |
780 { | 978 { |
781 if ( (*i3).lineC != -1 ) | 979 if ( (*i3).lineC != -1 ) |
782 ++nofDisturbingLines; | 980 ++nofDisturbingLines; |
783 ++i3; | 981 ++i3; |
784 } | 982 } |
785 | 983 |
786 if ( nofDisturbingLines>0 && nofDisturbingLines < d.nofEquals ) | 984 if ( nofDisturbingLines>0 && nofDisturbingLines < d.nofEquals ) |
787 { | 985 { |
788 // Move the disturbing lines up, out of sight. | 986 // Move the disturbing lines up, out of sight. |
789 i3 = i3b; | 987 i3 = i3b; |
790 while( i3 != i3c ) | 988 while( i3 != i3c ) |
791 { | 989 { |
792 if ( (*i3).lineC != -1 ) | 990 if ( (*i3).lineC != -1 ) |
793 { | 991 { |
794 Diff3Line d3l; | 992 Diff3Line d3l; |
795 d3l.lineC = (*i3).lineC; | 993 d3l.lineC = (*i3).lineC; |
796 (*i3).lineC = -1; | 994 (*i3).lineC = -1; |
797 (*i3).bAEqC = false; | 995 (*i3).bAEqC = false; |
814 (*i3b).lineC = lineC; | 1012 (*i3b).lineC = lineC; |
815 (*i3b).bBEqC = true; | 1013 (*i3b).bBEqC = true; |
816 } | 1014 } |
817 } | 1015 } |
818 } | 1016 } |
819 | 1017 |
820 --d.nofEquals; | 1018 --d.nofEquals; |
821 ++lineB; | 1019 ++lineB; |
822 ++lineC; | 1020 ++lineC; |
823 ++i3b; | 1021 ++i3b; |
824 ++i3c; | 1022 ++i3c; |
859 /* | 1057 /* |
860 Diff3LineList::iterator it = d3ll.begin(); | 1058 Diff3LineList::iterator it = d3ll.begin(); |
861 int li=0; | 1059 int li=0; |
862 for( ; it!=d3ll.end(); ++it, ++li ) | 1060 for( ; it!=d3ll.end(); ++it, ++li ) |
863 { | 1061 { |
864 printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", | 1062 printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", |
865 li, (*it).lineA, (*it).lineB, (*it).lineC, | 1063 li, (*it).lineA, (*it).lineB, (*it).lineC, |
866 (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); | 1064 (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); |
867 } | 1065 } |
868 printf("\n");*/ | 1066 printf("\n");*/ |
869 } | 1067 } |
870 | 1068 |
871 #ifdef _WIN32 | 1069 #ifdef _WIN32 |
872 using ::equal; | 1070 using ::equal; |
873 #endif | 1071 #endif |
874 | 1072 |
875 // Fourth step | 1073 // Fourth step |
876 void calcDiff3LineListTrim( | 1074 void calcDiff3LineListTrim( |
877 Diff3LineList& d3ll, LineData* pldA, LineData* pldB, LineData* pldC | 1075 Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC |
878 ) | 1076 ) |
879 { | 1077 { |
880 const Diff3Line d3l_empty; | 1078 const Diff3Line d3l_empty; |
881 d3ll.remove( d3l_empty ); | 1079 d3ll.remove( d3l_empty ); |
882 | 1080 |
972 int l = lineA > lineB ? lineA : lineB; | 1170 int l = lineA > lineB ? lineA : lineB; |
973 | 1171 |
974 (*i).lineA = (*i3).lineA; | 1172 (*i).lineA = (*i3).lineA; |
975 (*i).lineB = (*i3).lineB; | 1173 (*i).lineB = (*i3).lineB; |
976 (*i).bAEqB = true; | 1174 (*i).bAEqB = true; |
977 | 1175 |
978 (*i3).lineA = -1; | 1176 (*i3).lineA = -1; |
979 (*i3).lineB = -1; | 1177 (*i3).lineB = -1; |
980 (*i3).bAEqB = false; | 1178 (*i3).bAEqB = false; |
981 i3A = i; | 1179 i3A = i; |
982 i3B = i; | 1180 i3B = i; |
991 Diff3LineList::iterator i = lineA > lineC ? i3A : i3C; | 1189 Diff3LineList::iterator i = lineA > lineC ? i3A : i3C; |
992 int l = lineA > lineC ? lineA : lineC; | 1190 int l = lineA > lineC ? lineA : lineC; |
993 (*i).lineA = (*i3).lineA; | 1191 (*i).lineA = (*i3).lineA; |
994 (*i).lineC = (*i3).lineC; | 1192 (*i).lineC = (*i3).lineC; |
995 (*i).bAEqC = true; | 1193 (*i).bAEqC = true; |
996 | 1194 |
997 (*i3).lineA = -1; | 1195 (*i3).lineA = -1; |
998 (*i3).lineC = -1; | 1196 (*i3).lineC = -1; |
999 (*i3).bAEqC = false; | 1197 (*i3).bAEqC = false; |
1000 i3A = i; | 1198 i3A = i; |
1001 i3C = i; | 1199 i3C = i; |
1010 Diff3LineList::iterator i = lineB > lineC ? i3B : i3C; | 1208 Diff3LineList::iterator i = lineB > lineC ? i3B : i3C; |
1011 int l = lineB > lineC ? lineB : lineC; | 1209 int l = lineB > lineC ? lineB : lineC; |
1012 (*i).lineB = (*i3).lineB; | 1210 (*i).lineB = (*i3).lineB; |
1013 (*i).lineC = (*i3).lineC; | 1211 (*i).lineC = (*i3).lineC; |
1014 (*i).bBEqC = true; | 1212 (*i).bBEqC = true; |
1015 | 1213 |
1016 (*i3).lineB = -1; | 1214 (*i3).lineB = -1; |
1017 (*i3).lineC = -1; | 1215 (*i3).lineC = -1; |
1018 (*i3).bBEqC = false; | 1216 (*i3).bBEqC = false; |
1019 i3B = i; | 1217 i3B = i; |
1020 i3C = i; | 1218 i3C = i; |
1050 | 1248 |
1051 Diff3LineList::iterator it = d3ll.begin(); | 1249 Diff3LineList::iterator it = d3ll.begin(); |
1052 int li=0; | 1250 int li=0; |
1053 for( ; it!=d3ll.end(); ++it, ++li ) | 1251 for( ; it!=d3ll.end(); ++it, ++li ) |
1054 { | 1252 { |
1055 printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", | 1253 printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", |
1056 li, (*it).lineA, (*it).lineB, (*it).lineC, | 1254 li, (*it).lineA, (*it).lineB, (*it).lineC, |
1057 (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); | 1255 (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); |
1058 | 1256 |
1059 } | 1257 } |
1060 */ | 1258 */ |
1061 } | 1259 } |
1062 | 1260 |
1063 void calcWhiteDiff3Lines( | 1261 void calcWhiteDiff3Lines( |
1064 Diff3LineList& d3ll, LineData* pldA, LineData* pldB, LineData* pldC | 1262 Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC |
1065 ) | 1263 ) |
1066 { | 1264 { |
1067 Diff3LineList::iterator i3 = d3ll.begin(); | 1265 Diff3LineList::iterator i3 = d3ll.begin(); |
1068 | 1266 |
1069 for( ; i3!=d3ll.end(); ++i3 ) | 1267 for( ; i3!=d3ll.end(); ++i3 ) |
1292 } | 1490 } |
1293 | 1491 |
1294 void fineDiff( | 1492 void fineDiff( |
1295 Diff3LineList& diff3LineList, | 1493 Diff3LineList& diff3LineList, |
1296 int selector, | 1494 int selector, |
1297 LineData* v1, | 1495 const LineData* v1, |
1298 LineData* v2, | 1496 const LineData* v2, |
1299 bool& bTextsTotalEqual | 1497 bool& bTextsTotalEqual |
1300 ) | 1498 ) |
1301 { | 1499 { |
1302 // Finetuning: Diff each line with deltas | 1500 // Finetuning: Diff each line with deltas |
1303 int maxSearchLength=500; | 1501 int maxSearchLength=500; |
1323 // std::cout << std::string( v1[k1].pLine, v1[k1].size ) << "\n"; | 1521 // std::cout << std::string( v1[k1].pLine, v1[k1].size ) << "\n"; |
1324 calcDiff( v1[k1].pLine, v1[k1].size, v2[k2].pLine, v2[k2].size, *pDiffList, 2, maxSearchLength ); | 1522 calcDiff( v1[k1].pLine, v1[k1].size, v2[k2].pLine, v2[k2].size, *pDiffList, 2, maxSearchLength ); |
1325 | 1523 |
1326 // Optimize the diff list. | 1524 // Optimize the diff list. |
1327 DiffList::iterator dli; | 1525 DiffList::iterator dli; |
1526 bool bUsefulFineDiff = false; | |
1328 for( dli = pDiffList->begin(); dli!=pDiffList->end(); ++dli) | 1527 for( dli = pDiffList->begin(); dli!=pDiffList->end(); ++dli) |
1329 { | 1528 { |
1330 if( dli->nofEquals < 4 && (dli->diff1>0 || dli->diff2>0) ) | 1529 if( dli->nofEquals >= 4 ) |
1530 { | |
1531 bUsefulFineDiff = true; | |
1532 break; | |
1533 } | |
1534 } | |
1535 | |
1536 for( dli = pDiffList->begin(); dli!=pDiffList->end(); ++dli) | |
1537 { | |
1538 if( dli->nofEquals < 4 && (dli->diff1>0 || dli->diff2>0) | |
1539 && !( bUsefulFineDiff && dli==pDiffList->begin() ) | |
1540 ) | |
1331 { | 1541 { |
1332 dli->diff1 += dli->nofEquals; | 1542 dli->diff1 += dli->nofEquals; |
1333 dli->diff2 += dli->nofEquals; | 1543 dli->diff2 += dli->nofEquals; |
1334 dli->nofEquals = 0; | 1544 dli->nofEquals = 0; |
1335 } | 1545 } |
1363 int j=0; | 1573 int j=0; |
1364 for( i= d3ll.begin(); i!= d3ll.end(); ++i, ++j) | 1574 for( i= d3ll.begin(); i!= d3ll.end(); ++i, ++j) |
1365 { | 1575 { |
1366 d3lv[j] = &(*i); | 1576 d3lv[j] = &(*i); |
1367 } | 1577 } |
1368 assert( j==(int)d3lv.size() ); | 1578 assert( j==(int)d3lv.size() ); |
1369 } | 1579 } |
1370 | 1580 |
1371 | 1581 |
1372 #include "diff.moc" | 1582 #include "diff.moc" |