Line data Source code
1 : /**
2 : * Diff formatter, based on code by Steinar H. Gunderson, converted to work with the
3 : * Dairiki diff engine by Tim Starling
4 : *
5 : * GPL.
6 : */
7 :
8 : #include <stdio.h>
9 : #include "Wikidiff2.h"
10 :
11 : namespace wikidiff2 {
12 :
13 47 : Wikidiff2::Wikidiff2(const Config & config_)
14 : : config(config_),
15 : lineDiffConfig{0},
16 47 : wordDiffConfig{config.maxWordLevelDiffComplexity},
17 47 : wordDiffCache(wordDiffConfig),
18 : ldpConfig{
19 47 : config.changeThreshold,
20 47 : config.initialSplitThreshold,
21 47 : config.finalSplitThreshold,
22 47 : config.maxSplitSize
23 : },
24 47 : lineDiffProcessor(ldpConfig, wordDiffCache)
25 : {
26 47 : }
27 :
28 47 : void Wikidiff2::printDiff(const StringDiff & linediff)
29 : {
30 47 : int from_index = 1, to_index = 1;
31 :
32 : // Should a line number be printed before the next context line?
33 : // Set to true initially so we get a line number on line 1
34 47 : bool showLineNumber = true;
35 :
36 47 : printFileHeader();
37 :
38 47 : int currentOffsetFrom = 0;
39 47 : int currentOffsetTo = 0;
40 47 : int newLineLength = 1;
41 457 : for (int i = 0; i < linediff.size(); ++i) {
42 : int j;
43 : // Line 1 changed, show heading with no leading context
44 410 : if (linediff[i].op != DiffOp<String>::copy && i == 0) {
45 28 : printBlockHeader(1, 1);
46 : }
47 :
48 410 : int n1 = linediff[i].from.size();
49 410 : int n2 = linediff[i].to.size();
50 :
51 410 : switch (linediff[i].op) {
52 84 : case DiffOp<String>::add:
53 : // inserted lines
54 217 : for (j=0; j<n2; j++) {
55 :
56 133 : String toLine = *linediff[i].to[j];
57 :
58 133 : if (!printMovedLineDiff(linediff, i, j, from_index, to_index+j,
59 : -1, currentOffsetTo)) {
60 :
61 80 : printAdd(toLine, from_index, to_index+j, -1, currentOffsetTo);
62 : }
63 :
64 133 : currentOffsetTo += toLine.length() + newLineLength;
65 : }
66 84 : to_index += n2;
67 84 : break;
68 118 : case DiffOp<String>::del:
69 : // deleted lines
70 273 : for (j=0; j<n1; j++) {
71 :
72 155 : const String & fromLine = *linediff[i].from[j];
73 :
74 155 : if (!printMovedLineDiff(linediff, i, j, from_index+j, to_index,
75 : currentOffsetFrom, -1)) {
76 :
77 102 : printDelete(fromLine, from_index+j, to_index, currentOffsetFrom, -1);
78 : }
79 :
80 155 : currentOffsetFrom += fromLine.length() + newLineLength;
81 : }
82 118 : from_index += n1;
83 118 : break;
84 148 : case DiffOp<String>::copy:
85 : // copy/context
86 570 : for (j=0; j<n1; j++) {
87 :
88 422 : String line = *linediff[i].from[j];
89 :
90 386 : if ((i != 0 && j < config.numContextLines) /*trailing*/
91 808 : || (i != linediff.size() - 1 && j >= n1 - config.numContextLines)) /*leading*/ {
92 297 : if (showLineNumber) {
93 43 : printBlockHeader(from_index, to_index);
94 43 : showLineNumber = false;
95 : }
96 :
97 297 : printContext(line, from_index, to_index, currentOffsetFrom, currentOffsetTo);
98 : } else {
99 125 : showLineNumber = true;
100 : }
101 :
102 422 : currentOffsetTo += line.length() + newLineLength;
103 422 : currentOffsetFrom += line.length() + newLineLength;
104 :
105 422 : from_index++;
106 422 : to_index++;
107 : }
108 148 : break;
109 60 : case DiffOp<String>::change:
110 60 : if (n1 != n2) {
111 : // Line split
112 0 : printConcatDiff(
113 0 : linediff[i].from[0], n1,
114 0 : linediff[i].to[0], n2,
115 : from_index, to_index,
116 : currentOffsetFrom, currentOffsetTo);
117 0 : for (j = 0; j < n1; j++) {
118 0 : currentOffsetFrom += linediff[i].from[j]->length() + newLineLength;
119 : }
120 0 : for (j = 0; j < n2; j++) {
121 0 : currentOffsetTo += linediff[i].to[j]->length() + newLineLength;
122 : }
123 0 : from_index += n1;
124 0 : to_index += n2;
125 : } else {
126 : // Replace, i.e. we do a word diff between the two sets of lines
127 124 : for (j=0; j<n1; j++) {
128 64 : const String * toLine = linediff[i].to[j];
129 64 : const String * fromLine = linediff[i].from[j];
130 :
131 64 : printWordDiffFromStrings(fromLine, toLine, from_index+j, to_index+j,
132 : currentOffsetFrom, currentOffsetTo);
133 :
134 64 : currentOffsetTo += toLine->length() + newLineLength;
135 64 : currentOffsetFrom += fromLine->length() + newLineLength;
136 : }
137 60 : from_index += n1;
138 60 : to_index += n1;
139 : }
140 60 : break;
141 : }
142 :
143 : // Not first line anymore, don't show line number by default
144 410 : showLineNumber = false;
145 : }
146 :
147 47 : printFileFooter();
148 47 : }
149 :
150 : /**
151 : * Tell registered formatters to print an added line
152 : *
153 : * @see Formatter::printAdd
154 : */
155 80 : void Wikidiff2::printAdd(const String & line, int leftLine, int rightLine, int offsetFrom, int offsetTo)
156 : {
157 160 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
158 80 : (*f)->printAdd(line, leftLine, rightLine, offsetFrom, offsetTo);
159 : }
160 80 : }
161 :
162 : /**
163 : * Tell registered formatters to print a deleted line
164 : *
165 : * @see Formatter::printDelete
166 : */
167 102 : void Wikidiff2::printDelete(const String & line, int leftLine, int rightLine, int offsetFrom, int offsetTo)
168 : {
169 204 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
170 102 : (*f)->printDelete(line, leftLine, rightLine, offsetFrom, offsetTo);
171 : }
172 102 : }
173 :
174 : /**
175 : * Tell registered formatters to print a word diff
176 : *
177 : * @see Formatter::printWordDiff
178 : */
179 170 : void Wikidiff2::printWordDiff(
180 : const WordDiff & wordDiff,
181 : int leftLine, int rightLine,
182 : int offsetFrom, int offsetTo,
183 : bool printLeft, bool printRight,
184 : const String & srcAnchor, const String & dstAnchor,
185 : bool moveDirectionDownwards)
186 : {
187 340 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
188 170 : (*f)->printWordDiff(wordDiff,
189 : leftLine, rightLine,
190 : offsetFrom, offsetTo,
191 : printLeft, printRight,
192 : srcAnchor, dstAnchor,
193 : moveDirectionDownwards
194 170 : );
195 : }
196 170 : }
197 :
198 : /**
199 : * Do a word diff and then tell formatters to print it
200 : */
201 170 : void Wikidiff2::printWordDiffFromStrings(
202 : const String * text1, const String * text2,
203 : int leftLine, int rightLine,
204 : int offsetFrom, int offsetTo,
205 : bool printLeft, bool printRight,
206 : const String & srcAnchor, const String & dstAnchor,
207 : bool moveDirectionDownwards)
208 : {
209 170 : printWordDiff(
210 340 : *wordDiffCache.getDiff(text1, text2),
211 : leftLine, rightLine,
212 : offsetFrom, offsetTo,
213 : printLeft, printRight,
214 : srcAnchor, dstAnchor,
215 : moveDirectionDownwards
216 : );
217 170 : }
218 :
219 0 : void Wikidiff2::printConcatDiff(
220 : const String * lines1, int numLines1,
221 : const String * lines2, int numLines2,
222 : int leftLine, int rightLine,
223 : int offsetFrom, int offsetTo)
224 : {
225 0 : const WordDiff & wordDiff = *wordDiffCache.getConcatDiff(lines1, numLines1, lines2, numLines2);
226 0 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
227 0 : (*f)->printConcatDiff(wordDiff, leftLine, rightLine, offsetFrom, offsetTo);
228 : }
229 0 : }
230 :
231 : /**
232 : * Tell all formatters that we are starting
233 : *
234 : * @see Formatter::printFileHeader
235 : */
236 47 : void Wikidiff2::printFileHeader()
237 : {
238 94 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
239 47 : (*f)->printFileHeader();
240 : }
241 47 : }
242 :
243 : /**
244 : * Tell all formatters that we are ending
245 : *
246 : * @see Formatter::printFileFooter
247 : */
248 47 : void Wikidiff2::printFileFooter()
249 : {
250 94 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
251 47 : (*f)->printFileFooter();
252 : }
253 47 : }
254 :
255 : /**
256 : * Tell all formatters to print a block header
257 : *
258 : * @see Formatter::printBlockHeader
259 : */
260 71 : void Wikidiff2::printBlockHeader(int leftLine, int rightLine)
261 : {
262 142 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
263 71 : (*f)->printBlockHeader(leftLine, rightLine);
264 : }
265 71 : }
266 :
267 : /**
268 : * Tell all formatters to print a context line
269 : *
270 : * @see Formatter::printContext
271 : */
272 297 : void Wikidiff2::printContext(const String & input, int leftLine, int rightLine, int offsetFrom, int offsetTo)
273 : {
274 594 : for (auto f = formatters.begin(); f != formatters.end(); f++) {
275 297 : (*f)->printContext(input, leftLine, rightLine, offsetFrom, offsetTo);
276 : }
277 297 : }
278 :
279 801 : std::shared_ptr<Wikidiff2::DiffMapEntry> Wikidiff2::getDiffMapEntry(
280 : const String * text1, const String * text2,
281 : int opIndexFrom, int opLineFrom,
282 : int opIndexTo, int opLineTo)
283 : {
284 : return std::make_shared<DiffMapEntry>(
285 : wordDiffCache.getDiffStats(text1, text2),
286 : opIndexFrom, opLineFrom,
287 801 : opIndexTo, opLineTo);
288 : }
289 :
290 : /**
291 : * Detect a moved line at the current position. If there was a moved line, print it
292 : * and return true. If there was no moved line, do nothing and return false.
293 : *
294 : * @param linediff The line-level diff
295 : * @param opIndex The current index into linediff
296 : * @param opLine The current index into linediff[opIndex].from or
297 : * linediff[opIndex].to, specifying a particular added or deleted line.
298 : * @param leftLine The 1-based line number on the LHS
299 : * @param rightLine The 1-based line number on the RHS
300 : * @param offsetFrom The 0-based byte offset in the LHS input string
301 : * @param offsetTo The 0-based byte offset in the RHS input string
302 : */
303 288 : bool Wikidiff2::printMovedLineDiff(const StringDiff & linediff, int opIndex, int opLine,
304 : int leftLine, int rightLine, int offsetFrom, int offsetTo)
305 : {
306 : // helper fn creates 64-bit lookup key from opIndex and opLine
307 2243 : auto makeKey = [](int index, int line) {
308 2243 : return uint64_t(index) << 32 | line;
309 : };
310 :
311 212 : auto makeAnchorName = [](int index, int line, bool lhs) {
312 : char ch[2048];
313 212 : snprintf(ch, sizeof(ch), "movedpara_%d_%d_%s", index, line, lhs? "lhs": "rhs");
314 212 : return String(ch);
315 : };
316 :
317 : // check whether this paragraph immediately follows the other.
318 : // if so, they will be matched up next to each other and displayed as a change, not a move.
319 106 : auto isNext = [] (int opIndex, int opLine, int otherIndex, int otherLine) {
320 106 : if(otherIndex==opIndex && otherLine==opLine+1)
321 0 : return true;
322 106 : if(otherIndex==opIndex+1 && otherLine==0)
323 0 : return true;
324 106 : return false;
325 : };
326 :
327 : // compare positions of moved lines, return true if moved downwards
328 106 : auto movedir = [] (int opIndex, int opLine, int otherIndex, int otherLine) {
329 106 : return (otherIndex > opIndex) || (otherIndex == opIndex && otherLine > opLine);
330 : };
331 :
332 : #ifdef DEBUG_MOVED_LINES
333 : auto debugPrintf = [this](const char *fmt, ...) {
334 : char ch[2048];
335 : va_list ap;
336 : va_start(ap, fmt);
337 : vsnprintf(ch, sizeof(ch), fmt, ap);
338 : va_end(ap);
339 :
340 : std::cerr << ch << std::endl;
341 : };
342 : #else
343 2129 : auto debugPrintf = [](...) { };
344 : #endif
345 :
346 288 : if(!allowPrintMovedLineDiff(linediff, config.maxMovedLines)) {
347 0 : debugPrintf("printMovedLineDiff: diff too large (maxMovedLines=%ld), not detecting moved lines",
348 : config.maxMovedLines);
349 0 : return false;
350 : }
351 :
352 288 : debugPrintf("printMovedLineDiff (...), %d, %d", opIndex, opLine);
353 :
354 288 : bool printLeft = linediff[opIndex].op == DiffOp<String>::del ? true : false;
355 288 : bool printRight = !printLeft;
356 :
357 : // check whether this op actually refers to the diff map entry
358 913 : auto cmpDiffMapEntries = [&](int otherIndex, int otherLine) -> bool {
359 : // check whether the other paragraph already exists in the diff map.
360 913 : uint64_t otherKey = makeKey(otherIndex, otherLine);
361 913 : auto it = diffMap.find(otherKey);
362 913 : if (it != diffMap.end()) {
363 : // if found, check whether it refers to the current paragraph.
364 286 : auto other = it->second;
365 286 : bool cmp = (printLeft ?
366 72 : other->opIndexFrom == opIndex && other->opLineFrom == opLine :
367 71 : other->opIndexTo == opIndex && other->opLineTo == opLine);
368 143 : if(!cmp && (printLeft ? other->lhsDisplayed : other->rhsDisplayed)) {
369 : // the paragraph was already moved to a different place. a move operation can only have one source and one destination.
370 0 : debugPrintf("printMovedLineDiff(..., %d, %d): excluding this candidate (multiple potential matches). op=%s, printLeft %s, otheridx/line %d/%d, found %d/%d, other->lhsDisplayed %s, other->rhsDisplayed %s",
371 : opIndex, opLine,
372 0 : linediff[opIndex].op == DiffOp<String>::add ? "add": linediff[opIndex].op == DiffOp<String>::del ? "del": "???",
373 0 : printLeft ? "true" : "false",
374 0 : otherIndex, otherLine, (printLeft ? other->opIndexFrom : other->opIndexTo), (printLeft? other->opLineFrom: other->opLineTo),
375 0 : other->lhsDisplayed ? "true" : "false",
376 0 : other->rhsDisplayed ? "true" : "false");
377 0 : return false;
378 : }
379 : // the entry in the diff map refers to this paragraph.
380 572 : debugPrintf("printMovedLineDiff(..., %d, %d): diffMap entry refers to this paragraph (or other side not displayed). op=%s, printLeft %s, otheridx/line %d/%d, found %d/%d",
381 : opIndex, opLine,
382 143 : linediff[opIndex].op == DiffOp<String>::add ? "add": linediff[opIndex].op == DiffOp<String>::del ? "del": "???",
383 143 : printLeft ? "true" : "false",
384 286 : otherIndex, otherLine, (printLeft ? other->opIndexFrom : other->opIndexTo), (printLeft? other->opLineFrom: other->opLineTo));
385 143 : return true;
386 : }
387 : // no entry in the diffMap.
388 1540 : debugPrintf("printMovedLineDiff(..., %d, %d): no diffMap entry found. op=%s, printLeft %s, otheridx/line %d/%d",
389 : opIndex, opLine,
390 770 : linediff[opIndex].op == DiffOp<String>::add ? "add": linediff[opIndex].op == DiffOp<String>::del ? "del": "???",
391 770 : printLeft ? "true" : "false",
392 : otherIndex, otherLine);
393 770 : return true;
394 288 : };
395 :
396 : // look for corresponding moved line for the opposite case in moved-line-map
397 : // if moved line exists:
398 : // print diff to the moved line, omitting the left/right side for added/deleted line
399 288 : uint64_t key = makeKey(opIndex, opLine);
400 288 : auto it = diffMap.find(key);
401 288 : if (it != diffMap.end()) {
402 106 : auto best = it->second;
403 53 : int otherIndex = linediff[opIndex].op == DiffOp<String>::add ? best->opIndexFrom : best->opIndexTo;
404 53 : int otherLine = linediff[opIndex].op == DiffOp<String>::add ? best->opLineFrom : best->opLineTo;
405 :
406 53 : if(!cmpDiffMapEntries(otherIndex, otherLine))
407 0 : return false;
408 :
409 53 : if(isNext(otherIndex, otherLine, opIndex, opLine)) {
410 0 : debugPrintf("this one was already shown as a change, not displaying again...");
411 0 : return true;
412 : } else {
413 : // XXXX todo: we already have the diff, don't have to do it again, just have to print it
414 53 : printWordDiffFromStrings(
415 53 : linediff[best->opIndexFrom].from[best->opLineFrom],
416 53 : linediff[best->opIndexTo].to[best->opLineTo],
417 : leftLine, rightLine, offsetFrom, offsetTo, printLeft, printRight,
418 106 : makeAnchorName(opIndex, opLine, printLeft),
419 106 : makeAnchorName(otherIndex, otherLine, !printLeft),
420 53 : movedir(opIndex,opLine, otherIndex,otherLine));
421 : }
422 :
423 53 : if(printLeft)
424 26 : best->lhsDisplayed = true;
425 : else
426 27 : best->rhsDisplayed = true;
427 :
428 53 : debugPrintf("found in diffmap. copy: %d, del: %d, add: %d, change: %d, similarity: %.4f\n"
429 : "from: (%d,%d) to: (%d,%d)",
430 53 : best->ds.opCharCount[DiffOp<Word>::copy], best->ds.opCharCount[DiffOp<Word>::del], best->ds.opCharCount[DiffOp<Word>::add], best->ds.opCharCount[DiffOp<Word>::change], best->ds.charSimilarity,
431 53 : best->opIndexFrom, best->opLineFrom, best->opIndexTo, best->opLineTo);
432 :
433 53 : return true;
434 : }
435 :
436 235 : debugPrintf("nothing found in moved-line-map");
437 :
438 : // else:
439 : // try to find a corresponding moved line in deleted/added lines
440 235 : int otherOp = (linediff[opIndex].op == DiffOp<String>::add ? DiffOp<String>::del : DiffOp<String>::add);
441 470 : std::shared_ptr<DiffMapEntry> found = nullptr;
442 5644 : for (int i = 0; i < linediff.size(); ++i) {
443 5409 : if (linediff[i].op == otherOp) {
444 681 : auto& lines = (linediff[opIndex].op == DiffOp<String>::add ? linediff[i].from : linediff[i].to);
445 1611 : for (int k = 0; k < lines.size(); ++k) {
446 930 : auto it= diffMap.find(makeKey(i, k));
447 930 : if(it!=diffMap.end())
448 : {
449 213 : auto found = it->second;
450 213 : debugPrintf("found: lhsDisplayed=%s, rhsDisplayed=%s\n", found->lhsDisplayed? "true": "false", found->rhsDisplayed? "true": "false");
451 213 : if( (printLeft && found->lhsDisplayed) || (printRight && found->rhsDisplayed) )
452 : {
453 129 : debugPrintf("%chs already displayed, not considering this one", printLeft? 'l': 'r');
454 129 : continue;
455 : }
456 : }
457 801 : std::shared_ptr<DiffMapEntry> tmp;
458 : bool potentialMatch;
459 801 : if (otherOp == DiffOp<String>::del) {
460 395 : tmp = getDiffMapEntry(linediff[opIndex].to[opLine], lines[k], i, k, opIndex, opLine);
461 395 : potentialMatch = cmpDiffMapEntries(tmp->opIndexFrom, tmp->opLineFrom);
462 : } else {
463 406 : tmp = getDiffMapEntry(lines[k], linediff[opIndex].from[opLine], opIndex, opLine, i, k);
464 406 : potentialMatch = cmpDiffMapEntries(tmp->opIndexTo, tmp->opLineTo);
465 : }
466 801 : if (!found || (tmp->ds.charSimilarity > found->ds.charSimilarity) && potentialMatch) {
467 349 : found= tmp;
468 : }
469 : }
470 : }
471 : }
472 :
473 235 : if(found)
474 186 : debugPrintf("candidate found with similarity %.2f (from %d:%d to %d:%d)", found->ds.charSimilarity, found->opIndexFrom, found->opLineFrom, found->opIndexTo, found->opLineTo);
475 :
476 : // if candidate exists:
477 : // add candidate to moved-line-map twice, for add/del case
478 : // print diff to the moved line, omitting the left/right side for added/deleted line
479 235 : if (found && found->ds.charSimilarity > config.movedLineThreshold) {
480 : // if we displayed a diff to the found block before, don't display this one as moved.
481 59 : int otherIndex = linediff[opIndex].op == DiffOp<String>::add ? found->opIndexFrom : found->opIndexTo;
482 59 : int otherLine = linediff[opIndex].op == DiffOp<String>::add ? found->opLineFrom : found->opLineTo;
483 :
484 59 : if(!cmpDiffMapEntries(otherIndex, otherLine))
485 0 : return false;
486 :
487 59 : if(diffMap.find(makeKey(otherIndex, otherLine)) != diffMap.end()) {
488 6 : debugPrintf("found existing diffMap entry -- not overwriting.");
489 6 : return false;
490 : }
491 :
492 53 : if(printLeft)
493 27 : found->lhsDisplayed = true;
494 : else
495 26 : found->rhsDisplayed = true;
496 :
497 53 : diffMap[key] = found;
498 53 : diffMap[makeKey(otherIndex, otherLine)] = found;
499 53 : debugPrintf("inserting (%d,%d) + (%d,%d)", opIndex, opLine, otherIndex, otherLine);
500 :
501 53 : if(isNext(opIndex, opLine, otherIndex, otherLine)) {
502 0 : debugPrintf("This one immediately follows, displaying as change...");
503 0 : printWordDiffFromStrings(
504 0 : linediff[found->opIndexFrom].from[found->opLineFrom],
505 0 : linediff[found->opIndexTo].to[found->opLineTo],
506 : leftLine, rightLine, offsetFrom, offsetTo);
507 0 : found->lhsDisplayed = true;
508 0 : found->rhsDisplayed = true;
509 : }
510 : else {
511 : // XXXX todo: we already have the diff, don't have to do it again, just have to print it
512 53 : printWordDiffFromStrings(
513 53 : linediff[found->opIndexFrom].from[found->opLineFrom],
514 53 : linediff[found->opIndexTo].to[found->opLineTo],
515 : leftLine, rightLine, offsetFrom, offsetTo, printLeft, printRight,
516 106 : makeAnchorName(opIndex, opLine, printLeft),
517 106 : makeAnchorName(otherIndex, otherLine, !printLeft),
518 53 : movedir(opIndex,opLine, otherIndex,otherLine));
519 : }
520 :
521 53 : debugPrintf("copy: %d, del: %d, add: %d, change: %d, similarity: %.4f\n"
522 : "from: (%d,%d) to: (%d,%d)",
523 53 : found->ds.opCharCount[DiffOp<Word>::copy], found->ds.opCharCount[DiffOp<Word>::del], found->ds.opCharCount[DiffOp<Word>::add], found->ds.opCharCount[DiffOp<Word>::change], found->ds.charSimilarity,
524 53 : found->opIndexFrom, found->opLineFrom, found->opIndexTo, found->opLineTo);
525 :
526 53 : return true;
527 : }
528 :
529 176 : return false;
530 : }
531 :
532 94 : void Wikidiff2::explodeLines(const String & text, StringVector &lines)
533 : {
534 94 : String::const_iterator ptr = text.begin();
535 1354 : while (ptr != text.end()) {
536 1260 : String::const_iterator ptr2 = std::find(ptr, text.end(), '\n');
537 1260 : lines.push_back(String(ptr, ptr2));
538 :
539 1260 : ptr = ptr2;
540 1260 : if (ptr != text.end()) {
541 1198 : ++ptr;
542 : }
543 : }
544 94 : }
545 :
546 47 : void Wikidiff2::execute(const String & text1, const String & text2)
547 : {
548 : // Split input strings into lines
549 94 : StringVector lines1;
550 94 : StringVector lines2;
551 47 : explodeLines(text1, lines1);
552 47 : explodeLines(text2, lines2);
553 :
554 47 : wordDiffCache.setLines(&lines1, &lines2);
555 :
556 : // Do the diff
557 94 : StringDiff lineDiff(lineDiffConfig, lines1, lines2);
558 47 : lineDiffProcessor.process(lineDiff);
559 47 : printDiff(lineDiff);
560 :
561 47 : wordDiffCache.setLines(nullptr, nullptr);
562 47 : }
563 :
564 47 : void Wikidiff2::addFormatter(Formatter & formatter)
565 : {
566 47 : formatters.push_back(&formatter);
567 47 : }
568 :
569 : } // namespace wikidiff2
|