Jkroll has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/328685 )
Change subject: [WIP] Extend WikiDiff2 to show changes inside moved paragraphes
......................................................................
[WIP] Extend WikiDiff2 to show changes inside moved paragraphes
Change-Id: If6855bcddbfea47614130daedf21a3de43510fc4
---
M InlineDiff.cpp
M InlineDiff.h
M TableDiff.cpp
M TableDiff.h
M Wikidiff2.cpp
M Wikidiff2.h
M config.m4
7 files changed, 197 insertions(+), 19 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/php/wikidiff2
refs/changes/85/328685/1
diff --git a/InlineDiff.cpp b/InlineDiff.cpp
index d60215c..f87f3f9 100644
--- a/InlineDiff.cpp
+++ b/InlineDiff.cpp
@@ -10,7 +10,7 @@
printWrappedLine("<div class=\"mw-diff-inline-deleted\"><del>", line,
"</del></div>\n");
}
-void InlineDiff::printWordDiff(const String& text1, const String& text2)
+void InlineDiff::printWordDiff(const String& text1, const String& text2, bool
printLeft, bool printRight)
{
WordVector words1, words2;
@@ -19,6 +19,8 @@
WordDiff worddiff(words1, words2, MAX_WORD_LEVEL_DIFF_COMPLEXITY);
String word;
+ // XXXX todo: omit left side & do strike-through according to
printLeft/printRight
+
result += "<div class=\"mw-diff-inline-changed\">";
for (unsigned i = 0; i < worddiff.size(); ++i) {
DiffOp<Word> & op = worddiff[i];
diff --git a/InlineDiff.h b/InlineDiff.h
index 01304ca..0dc7f66 100644
--- a/InlineDiff.h
+++ b/InlineDiff.h
@@ -8,7 +8,7 @@
protected:
void printAdd(const String& line);
void printDelete(const String& line);
- void printWordDiff(const String& text1, const String& text2);
+ void printWordDiff(const String& text1, const String& text2,
bool printLeft = true, bool printRight = true);
void printBlockHeader(int leftLine, int rightLine);
void printContext(const String& input);
diff --git a/TableDiff.cpp b/TableDiff.cpp
index 5a4969f..f2565eb 100644
--- a/TableDiff.cpp
+++ b/TableDiff.cpp
@@ -23,7 +23,7 @@
"</tr>\n";
}
-void TableDiff::printWordDiff(const String & text1, const String & text2)
+void TableDiff::printWordDiff(const String & text1, const String & text2, bool
printLeft, bool printRight)
{
WordVector words1, words2;
@@ -33,17 +33,29 @@
//debugPrintWordDiff(worddiff);
- // print twice; first for left side, then for right side
- result += "<tr>\n"
- " <td class=\"diff-marker\">−</td>\n"
- " <td class=\"diff-deletedline\"><div>";
- printWordDiffSide(worddiff, false);
- result += "</div></td>\n"
- " <td class=\"diff-marker\">+</td>\n"
- " <td class=\"diff-addedline\"><div>";
- printWordDiffSide(worddiff, true);
- result += "</div></td>\n"
- "</tr>\n";
+ result += "<tr>\n";
+
+ // print left side or blank placeholder.
+ if (printLeft) {
+ result += " <td class=\"diff-marker\">−</td>\n"
+ " <td class=\"diff-deletedline\"><div>";
+ printWordDiffSide(worddiff, false);
+ result += "</div></td>\n";
+ } else {
+ result += " <td colspan=\"2\"
class=\"diff-empty\"> </td>\n";
+ }
+
+ // print right side or blank placeholder.
+ if (printRight) {
+ result += " <td class=\"diff-marker\">+</td>\n"
+ " <td class=\"diff-addedline\"><div>";
+ printWordDiffSide(worddiff, true);
+ result += "</div></td>\n"
+ "</tr>\n";
+ } else {
+ result += " <td colspan=\"2\"
class=\"diff-empty\"> </td>\n"
+ "</tr>\n";
+ }
}
void TableDiff::printWordDiffSide(WordDiff &worddiff, bool added)
diff --git a/TableDiff.h b/TableDiff.h
index 0a3adc6..4a4115a 100644
--- a/TableDiff.h
+++ b/TableDiff.h
@@ -8,7 +8,7 @@
protected:
void printAdd(const String& line);
void printDelete(const String& line);
- void printWordDiff(const String& text1, const String & text2);
+ void printWordDiff(const String& text1, const String & text2,
bool printLeft = true, bool printRight = true);
void printTextWithDiv(const String& input);
void printBlockHeader(int leftLine, int rightLine);
void printContext(const String& input);
diff --git a/Wikidiff2.cpp b/Wikidiff2.cpp
index d579b61..e3ee143 100644
--- a/Wikidiff2.cpp
+++ b/Wikidiff2.cpp
@@ -11,6 +11,7 @@
#include <thai/thailib.h>
#include <thai/thwchar.h>
#include <thai/thbrk.h>
+#include <stdarg.h>
void Wikidiff2::diffLines(const StringVector & lines1, const StringVector &
lines2,
@@ -37,7 +38,9 @@
// inserted lines
n = linediff[i].to.size();
for (j=0; j<n; j++) {
- printAdd(*linediff[i].to[j]);
+ if (!printMovedLineDiff(linediff, i,
j)) {
+ printAdd(*linediff[i].to[j]);
+ }
}
to_index += n;
break;
@@ -45,7 +48,9 @@
// deleted lines
n = linediff[i].from.size();
for (j=0; j<n; j++) {
- printDelete(*linediff[i].from[j]);
+ if (!printMovedLineDiff(linediff, i,
j)) {
+
printDelete(*linediff[i].from[j]);
+ }
}
from_index += n;
break;
@@ -93,6 +98,115 @@
}
}
+//#define DEBUG_MOVED_LINES
+
+bool Wikidiff2::printMovedLineDiff(StringDiff & linediff, int opIndex, int
opLine)
+{
+ // helper fn creates 64-bit lookup key from opIndex and opLine
+ auto makeKey = [](int index, int line) {
+ return uint64_t(index) << 32 | line;
+ };
+
+#ifdef DEBUG_MOVED_LINES
+ auto debugPrintf = [this](const char *fmt, ...) {
+ char ch[2048];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(ch, sizeof(ch), fmt, ap);
+ va_end(ap);
+
+ result += "<tr><td /><td class=\"diff-context\" colspan=3>";
+ result += ch;
+ result += "</td></tr>";
+ };
+#else
+ auto debugPrintf = [](...) { };
+#endif
+
+ debugPrintf("printMovedLineDiff (...), %d, %d\n", opIndex, opLine);
+
+ // look for corresponding moved line for the opposite case in
moved-line-map
+ // if moved line exists:
+ // print diff to the moved line, omitting the left/right side for
added/deleted line
+ uint64_t key = makeKey(opIndex, opLine);
+ auto it = diffMap.find(key);
+ if (it != diffMap.end()) {
+ auto best = it->second;
+
+ bool printLeft = linediff[opIndex].op == DiffOp<String>::del ?
true : false;
+ bool printRight = !printLeft;
+ // XXXX todo: we already have the diff, don't have to do it
again, just have to print it
+
printWordDiff(*linediff[best->opIndexFrom].from[best->opLineFrom],
*linediff[best->opIndexTo].to[best->opLineTo],
+ printLeft, printRight);
+
+ debugPrintf("found in diffmap. copy: %d, del: %d, add: %d,
change: %d, similarity: %.4f\n"
+ "from: (%d,%d) to: (%d,%d)\n",
+ best->opCharCount[DiffOp<Word>::copy],
best->opCharCount[DiffOp<Word>::del], best->opCharCount[DiffOp<Word>::add],
best->opCharCount[DiffOp<Word>::change], best->similarity,
+ best->opIndexFrom, best->opLineFrom, best->opIndexTo,
best->opLineTo);
+
+ return true;
+ }
+
+ // else:
+ // try to find a corresponding moved line in deleted/added lines
+ int otherOp = (linediff[opIndex].op == DiffOp<String>::add ?
DiffOp<String>::del : DiffOp<String>::add);
+ std::shared_ptr<DiffMapEntry> found = nullptr;
+ for (int i = 0; i < linediff.size(); ++i) {
+ if (linediff[i].op == otherOp) {
+ auto& lines = (linediff[opIndex].op ==
DiffOp<String>::add ? linediff[i].from : linediff[i].to);
+ for (int k = 0; k < lines.size(); ++k) {
+ WordVector words1, words2;
+ std::shared_ptr<DiffMapEntry> tmp;
+ explodeWords(*lines[k], words1);
+ if (otherOp == DiffOp<String>::del) {
+
explodeWords(*linediff[opIndex].to[opLine], words2);
+ tmp =
std::make_shared<DiffMapEntry>(words1, words2, i, k, opIndex, opLine);
+ } else {
+
explodeWords(*linediff[opIndex].from[opLine], words2);
+ tmp =
std::make_shared<DiffMapEntry>(words1, words2, opIndex, opLine, i, k);
+ }
+ if (!found || tmp->similarity >
found->similarity) {
+ found= tmp;
+ }
+ }
+ }
+ }
+
+ // if candidate exists:
+ // add candidate to moved-line-map twice, for add/del case
+ // print diff to the moved line, omitting the left/right side for
added/deleted line
+ if (found && found->similarity > 0.25) {
+ // if we displayed a diff to the found block before, don't
display this one as moved.
+ // XXXX does not work for the case where the block is moved "upwards"?
+ uint64_t otherKey = linediff[opIndex].op == DiffOp<String>::add
?
+ makeKey(found->opIndexFrom, found->opLineFrom) :
+ makeKey(found->opIndexTo, found->opLineTo);
+ if (diffMap.find(otherKey) != diffMap.end()) {
+ debugPrintf("printMovedLineDiff(..., %d, %d): not printing diff
again...", opIndex, opLine);
+ return false;
+ }
+
+ diffMap[key] = found;
+ uint64_t oppositeKey = makeKey(found->opIndexTo,
found->opLineTo);
+ diffMap[oppositeKey] = found;
+
+ bool printLeft = linediff[opIndex].op == DiffOp<String>::del ?
true : false;
+ bool printRight = !printLeft;
+ // XXXX todo: we already have the diff, don't have to do it
again, just have to print it
+
printWordDiff(*linediff[found->opIndexFrom].from[found->opLineFrom],
*linediff[found->opIndexTo].to[found->opLineTo],
+ printLeft, printRight);
+
+ debugPrintf("copy: %d, del: %d, add: %d, change: %d,
similarity: %.4f\n"
+ "from: (%d,%d) to: (%d,%d)\n",
+ found->opCharCount[DiffOp<Word>::copy],
found->opCharCount[DiffOp<Word>::del], found->opCharCount[DiffOp<Word>::add],
found->opCharCount[DiffOp<Word>::change], found->similarity,
+ found->opIndexFrom, found->opLineFrom,
found->opIndexTo, found->opLineTo);
+
+ return true;
+ }
+
+ return false;
+}
+
void Wikidiff2::debugPrintWordDiff(WordDiff & worddiff)
{
for (unsigned i = 0; i < worddiff.size(); ++i) {
diff --git a/Wikidiff2.h b/Wikidiff2.h
index da75e7b..1079930 100644
--- a/Wikidiff2.h
+++ b/Wikidiff2.h
@@ -14,6 +14,7 @@
#include <string>
#include <vector>
#include <set>
+#include <memory>
class Wikidiff2 {
public:
@@ -34,11 +35,24 @@
enum { MAX_WORD_LEVEL_DIFF_COMPLEXITY = 40000000 };
String result;
+ struct DiffMapEntry
+ {
+ WordDiff diff;
+ int opCharCount[4] = { 0 };
+ double similarity;
+ int opIndexFrom, opLineFrom, opIndexTo, opLineTo;
+
+ DiffMapEntry(WordVector& words1, WordVector& words2,
int opIndexFrom_, int opLineFrom_, int opIndexTo_, int opLineTo_);
+ };
+ // PhpAllocator can't be specialized for std::pair, so we're
using the standard allocator.
+ typedef std::map<uint64_t, std::shared_ptr<struct
Wikidiff2::DiffMapEntry> > DiffMap;
+ DiffMap diffMap;
+
virtual void diffLines(const StringVector & lines1, const
StringVector & lines2,
int numContextLines);
virtual void printAdd(const String & line) = 0;
virtual void printDelete(const String & line) = 0;
- virtual void printWordDiff(const String & text1, const String &
text2) = 0;
+ virtual void printWordDiff(const String & text1, const String &
text2, bool printLeft = true, bool printRight = true) = 0;
virtual void printBlockHeader(int leftLine, int rightLine) = 0;
virtual void printContext(const String & input) = 0;
@@ -52,6 +66,8 @@
void explodeWords(const String & text, WordVector &tokens);
void explodeLines(const String & text, StringVector &lines);
+
+ bool printMovedLineDiff(StringDiff & linediff, int opIndex, int
opLine);
};
inline bool Wikidiff2::isLetter(int ch)
@@ -83,4 +99,38 @@
return result;
}
+inline Wikidiff2::DiffMapEntry::DiffMapEntry(Wikidiff2::WordVector& words1,
Wikidiff2::WordVector& words2, int opIndexFrom_, int opLineFrom_, int
opIndexTo_, int opLineTo_):
+ diff(words1, words2, MAX_WORD_LEVEL_DIFF_COMPLEXITY),
+ opIndexFrom(opIndexFrom_), opLineFrom(opLineFrom_),
opIndexTo(opIndexTo_), opLineTo(opLineTo_)
+{
+ int charsTotal = 0;
+ for (int i = 0; i < diff.size(); ++i) {
+ int op = diff[i].op;
+ int charCount;
+ switch (diff[i].op) {
+ case DiffOp<Word>::del:
+ case DiffOp<Word>::copy:
+ charCount = diff[i].from.size();
+ break;
+ case DiffOp<Word>::add:
+ charCount = diff[i].to.size();
+ break;
+ case DiffOp<Word>::change:
+ charCount = std::max(diff[i].from.size(),
diff[i].to.size());
+ break;
+ }
+ opCharCount[op] += charCount;
+ charsTotal += charCount;
+ }
+ if (opCharCount[DiffOp<Word>::copy] == 0) {
+ similarity = 0.0;
+ } else {
+ if (charsTotal) {
+ similarity = double(opCharCount[DiffOp<Word>::copy]) /
charsTotal;
+ } else {
+ similarity = 0.0;
+ }
+ }
+}
+
#endif
diff --git a/config.m4 b/config.m4
index b848398..c9e2197 100644
--- a/config.m4
+++ b/config.m4
@@ -36,6 +36,6 @@
PHP_SUBST(WIKIDIFF2_SHARED_LIBADD)
AC_DEFINE(HAVE_WIKIDIFF2, 1, [ ])
- export CXXFLAGS="-Wno-write-strings $CXXFLAGS"
+ export CXXFLAGS="-Wno-write-strings -std=c++11 $CXXFLAGS"
PHP_NEW_EXTENSION(wikidiff2, php_wikidiff2.cpp Wikidiff2.cpp TableDiff.cpp
InlineDiff.cpp, $ext_shared)
fi
--
To view, visit https://gerrit.wikimedia.org/r/328685
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If6855bcddbfea47614130daedf21a3de43510fc4
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/php/wikidiff2
Gerrit-Branch: master
Gerrit-Owner: Jkroll <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits