OSDN Git Service

- Use Poco::RegularExpression instead of PCRE library. (Poco::RegularExpression inter...
[winmerge-jp/winmerge-jp.git] / Src / CompareEngines / DiffUtils.cpp
1 /**
2  * @file  DiffUtils.cpp
3  *
4  * @brief Implementation file for DiffUtils class.
5  */
6 // ID line follows -- this is updated by SVN
7 // $Id: DiffUtils.cpp 6932 2009-07-26 14:04:31Z kimmov $
8
9
10 #include "StdAfx.h"
11 #include <map>
12 #include "CompareOptions.h"
13 #include "FilterList.h"
14 #include "DiffContext.h"
15 #include "DIFF.H"
16 #include "DiffUtils.h"
17 #include "coretools.h"
18 #include "DiffList.h"
19 #include "DiffWrapper.h"
20 #include "FilterCommentsManager.h"
21
22 namespace CompareEngines
23 {
24 static void CopyTextStats(const file_data * inf, FileTextStats * myTextStats);
25
26 /**
27  * @brief Default constructor.
28  */
29 DiffUtils::DiffUtils()
30                 : m_pOptions(NULL)
31                 , m_pFilterList(NULL)
32                 , m_inf(NULL)
33                 , m_FilterCommentsManager(new ::FilterCommentsManager)
34                 , m_pDiffWrapper(new ::CDiffWrapper)
35 {
36 }
37
38 /**
39  * @brief Default destructor.
40  */
41 DiffUtils::~DiffUtils()
42 {
43         ClearFilterList();
44 }
45
46 /**
47  * @brief Set compare options from general compare options.
48  * @param [in ]options General compare options.
49  * @return true if succeeded, false otherwise.
50  */
51 bool DiffUtils::SetCompareOptions(const CompareOptions & options)
52 {
53         m_pOptions.reset(new DiffutilsOptions((DiffutilsOptions&)options));
54         if (m_pOptions.get() == NULL)
55                 return false;
56
57         m_pOptions->SetToDiffUtils();
58         return true;
59 }
60
61 /**
62  * @brief Clear current filters list.
63  * Don't delete the list as it points to external list.
64  */
65 void DiffUtils::ClearFilterList()
66 {
67         m_pFilterList = NULL;
68 }
69
70 /**
71  * @brief Set line filters list to use.
72  * @param [in] list List of line filters.
73  */
74 void DiffUtils::SetFilterList(FilterList * list)
75 {
76         m_pFilterList = list;
77 }
78
79 /**
80  * @brief Set filedata.
81  * @param [in] items Count of filedata items to set.
82  * @param [in] data File data.
83  */
84 void DiffUtils::SetFileData(int items, file_data *data)
85 {
86         // We support only two files currently!
87         ASSERT(items == 2);
88         m_inf = data;
89 }
90
91 /**
92  * @brief Compare two files (as earlier specified).
93  * @return DIFFCODE as a result of compare.
94  */
95 int DiffUtils::diffutils_compare_files()
96 {
97         int bin_flag = 0;
98         int bin_file = 0; // bitmap for binary files
99
100         // Do the actual comparison (generating a change script)
101         struct change *script = NULL;
102         bool success = Diff2Files(&script, 0, &bin_flag, false, &bin_file);
103         if (!success)
104         {
105                 return DIFFCODE::FILE | DIFFCODE::TEXT | DIFFCODE::CMPERR;
106         }
107         UINT code = DIFFCODE::FILE | DIFFCODE::TEXT | DIFFCODE::SAME;
108
109         // make sure to start counting diffs at 0
110         // (usually it is -1 at this point, for unknown)
111         m_ndiffs = 0;
112         m_ntrivialdiffs = 0;
113
114         if (script)
115         {
116                 struct change *next = script;
117                 struct change *thisob = 0, *end = 0;
118
119                 String asLwrCaseExt;
120                 String LowerCaseExt = CA2T(m_inf[0].name);
121                 int PosOfDot = LowerCaseExt.rfind('.');
122                 if (PosOfDot != -1)
123                 {
124                         LowerCaseExt.erase(0, PosOfDot + 1);
125                         CharLower(&*LowerCaseExt.begin());
126                         asLwrCaseExt = LowerCaseExt;
127                 }
128
129                 while (next)
130                 {
131                         /* Find a set of changes that belong together.  */
132                         thisob = next;
133                         end = find_change(next);
134
135                         /* Disconnect them from the rest of the changes,
136                         making them a hunk, and remember the rest for next iteration.  */
137                         next = end->link;
138                         end->link = 0;
139 #ifdef _DEBUG
140                         debug_script(thisob);
141 #endif
142
143                         {
144                                 /* Determine range of line numbers involved in each file.  */
145                                 int first0 = 0, last0 = 0, first1 = 0, last1 = 0, deletes = 0, inserts = 0;
146                                 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, m_inf);
147                                 if (deletes || inserts || thisob->trivial)
148                                 {
149                                         /* Print the lines that the first file has.  */
150                                         int trans_a0 = 0, trans_b0 = 0, trans_a1 = 0, trans_b1 = 0;
151                                         translate_range(&m_inf[0], first0, last0, &trans_a0, &trans_b0);
152                                         translate_range(&m_inf[1], first1, last1, &trans_a1, &trans_b1);
153
154                                         //Determine quantity of lines in this block for both sides
155                                         int QtyLinesLeft = (trans_b0 - trans_a0);
156                                         int QtyLinesRight = (trans_b1 - trans_a1);
157
158                                         if(m_pOptions->m_filterCommentsLines)
159                                         {
160                                                 OP_TYPE op = OP_NONE;
161                                                 if (!deletes && !inserts)
162                                                         op = OP_TRIVIAL;
163                                                 else
164                                                         op = OP_DIFF;
165
166                                                 DIFFOPTIONS options = {0};
167                                                 options.nIgnoreWhitespace = m_pOptions->m_ignoreWhitespace;
168                                                 options.bIgnoreBlankLines = m_pOptions->m_bIgnoreBlankLines;
169                                                 options.bFilterCommentsLines = m_pOptions->m_filterCommentsLines;
170                                                 options.bIgnoreCase = m_pOptions->m_bIgnoreCase;
171                                                 options.bIgnoreEol = m_pOptions->m_bIgnoreEOLDifference;
172                                                 m_pDiffWrapper->SetOptions(&options);
173                                                 m_pDiffWrapper->PostFilter(thisob->line0, QtyLinesLeft+1, thisob->line1, QtyLinesRight+1, op, *m_FilterCommentsManager, asLwrCaseExt.c_str());
174                                                 if(op == OP_TRIVIAL)
175                                                 {
176                                                         thisob->trivial = 1;
177                                                 }
178                                         }
179
180                                         // Match lines against regular expression filters
181                                         // Our strategy is that every line in both sides must
182                                         // match regexp before we mark difference as ignored.
183                                         if(m_pFilterList && m_pFilterList->HasRegExps())
184                                         {
185                                                 bool match2 = false;
186                                                 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft, 0);
187                                                 if (match1)
188                                                         match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight, 1);
189                                                 if (match1 && match2)
190                                                         thisob->trivial = 1;
191                                         }
192
193                                 }
194                                 /* Reconnect the script so it will all be freed properly.  */
195                                 end->link = next;
196                         }
197                 }
198         }
199
200
201         // Free change script (which we don't want)
202         if (script != NULL)
203         {
204                 struct change *p, *e;
205                 for (e = script; e; e = p)
206                 {
207                         if (!e->trivial)
208                                 ++m_ndiffs;
209                         else
210                                 ++m_ntrivialdiffs;
211                         p = e->link;
212                         free(e);
213                 }
214                 if (m_ndiffs > 0)
215                         code = code & ~DIFFCODE::SAME | DIFFCODE::DIFF;
216         }
217
218         // diff_2_files set bin_flag to -1 if different binary
219         // diff_2_files set bin_flag to +1 if same binary
220
221         if (bin_flag != 0)
222         {
223                 // Clear text-flag, set binary flag
224                 // We don't know diff counts for binary files
225                 code = code & ~DIFFCODE::TEXT;
226                 switch (bin_file)
227                 {
228                 case BINFILE_SIDE1:
229                         code |= DIFFCODE::BINSIDE1;
230                         break;
231                 case BINFILE_SIDE2:
232                         code |= DIFFCODE::BINSIDE2;
233                         break;
234                 case BINFILE_SIDE1 | BINFILE_SIDE2:
235                         code |= DIFFCODE::BIN;
236                         break;
237                 default:
238                         _RPTF1(_CRT_ERROR, "Invalid bin_file value: %d", bin_file);
239                         break;
240                 }
241                 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
242         }
243
244         if (bin_flag < 0)
245         {
246                 // Clear same-flag, set diff-flag
247                 code = code & ~DIFFCODE::SAME | DIFFCODE::DIFF;
248         }
249
250         return code;
251 }
252
253 /**
254  * @brief Match regular expression list against given difference.
255  * This function matches the regular expression list against the difference
256  * (given as start line and end line). Matching the diff requires that all
257  * lines in difference match.
258  * @param [in] StartPos First line of the difference.
259  * @param [in] endPos Last line of the difference.
260  * @param [in] FileNo File to match.
261  * return true if any of the expressions matches.
262  */
263 bool DiffUtils::RegExpFilter(int StartPos, int EndPos, int FileNo)
264 {
265         if (m_pFilterList == NULL)
266         {
267                 _RPTF0(_CRT_ERROR, "DiffUtils::RegExpFilter() called when "
268                                 "filterlist doesn't exist (=NULL)");
269                 return false;
270         }
271
272         bool linesMatch = true; // set to false when non-matching line is found.
273         int line = StartPos;
274
275         while (line <= EndPos && linesMatch == true)
276         {
277                 size_t len = files[FileNo].linbuf[line + 1] - files[FileNo].linbuf[line];
278                 const char *string = files[FileNo].linbuf[line];
279                 size_t stringlen = linelen(string, len);
280                 if (!m_pFilterList->Match(std::string(string, stringlen), m_codepage))
281                 {
282                         linesMatch = false;
283                 }
284                 ++line;
285         }
286         return linesMatch;
287 }
288
289 /**
290  * @brief Compare two files using diffutils.
291  *
292  * Compare two files (in DiffFileData param) using diffutils. Run diffutils
293  * inside SEH so we can trap possible error and exceptions. If error or
294  * execption is trapped, return compare failure.
295  * @param [out] diffs Pointer to list of change structs where diffdata is stored.
296  * @param [in] depth Depth in folder compare (we use 0).
297  * @param [out] bin_status used to return binary status from compare.
298  * @param [in] bMovedBlocks If TRUE moved blocks are analyzed.
299  * @param [out] bin_file Returns which file was binary file as bitmap.
300     So if first file is binary, first bit is set etc. Can be NULL if binary file
301     info is not needed (faster compare since diffutils don't bother checking
302     second file if first is binary).
303  * @return TRUE when compare succeeds, FALSE if error happened during compare.
304  */
305 bool DiffUtils::Diff2Files(struct change ** diffs, int depth,
306                 int * bin_status, bool bMovedBlocks, int * bin_file)
307 {
308         bool bRet = true;
309         __try
310         {
311                 *diffs = diff_2_files(m_inf, depth, bin_status, bMovedBlocks, bin_file);
312         }
313         __except(EXCEPTION_EXECUTE_HANDLER)
314         {
315                 *diffs = NULL;
316                 bRet = false;
317         }
318         return bRet;
319 }
320
321 /**
322  * @brief Copy text stat results from diffutils back into the FileTextStats structure
323  */
324 static void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
325 {
326         myTextStats->ncrlfs = inf->count_crlfs;
327         myTextStats->ncrs = inf->count_crs;
328         myTextStats->nlfs = inf->count_lfs;
329 }
330
331 /**
332  * @brief Return diff counts for last compare.
333  * @param [out] diffs Count of real differences.
334  * @param [out] trivialDiffs Count of ignored differences.
335  */
336 void DiffUtils::GetDiffCounts(int & diffs, int & trivialDiffs)
337 {
338         diffs = m_ndiffs;
339         trivialDiffs = m_ntrivialdiffs;
340 }
341
342 /**
343  * @brief Return text statistics for last compare.
344  * @param [in] side For which file to return statistics.
345  * @param [out] stats Stats as asked.
346  */
347 void DiffUtils::GetTextStats(int side, FileTextStats *stats)
348 {
349         CopyTextStats(&m_inf[side], stats);
350 }
351
352 } // namespace CompareEngines