package nl.hsac.fitnesse.fixture.slim; import com.sksamuel.diffpatch.DiffMatchPatch; import nl.hsac.fitnesse.fixture.util.Formatter; import org.apache.commons.lang3.StringEscapeUtils; import java.util.LinkedList; /** * Fixture to determine and visualize differences between strings. */ public class CompareFixture extends SlimFixture { private final DiffMatchPatch diffMatchPatch = new DiffMatchPatch(); /** * Determines difference between two strings. * @param first first string to compare. * @param second second string to compare. * @return HTML of difference between the two. */ public String differenceBetweenAnd(String first, String second) { Formatter whitespaceFormatter = new Formatter() { @Override public String format(String value) { return ensureWhitespaceVisible(value); } }; return getDifferencesHtml(first, second, whitespaceFormatter); } /** * Determines difference between two strings, visualizing various forms of whitespace. * @param first first string to compare. * @param second second string to compare. * @return HTML of difference between the two. */ public String differenceBetweenExplicitWhitespaceAnd(String first, String second) { Formatter whitespaceFormatter = new Formatter() { @Override public String format(String value) { return explicitWhitespace(value); } }; return getDifferencesHtml(first, second, whitespaceFormatter); } protected String getDifferencesHtml(String first, String second, Formatter whitespaceFormatter) { if (first == null) { if (second == null) { return null; } else { first = ""; } } else if (second == null) { second = ""; } LinkedList<DiffMatchPatch.Diff> diffs = getDiffs(first, second); String rootTag = first.startsWith("<pre>") && first.endsWith("</pre>") ? "pre" : "div"; String diffPrettyHtml = diffToHtml(rootTag, diffs, whitespaceFormatter); if (first.startsWith("<pre>") && first.endsWith("</pre>")) { diffPrettyHtml = diffPrettyHtml.replaceFirst("^<div>", "<pre>").replaceFirst("</div>$", "</pre>"); } return diffPrettyHtml; } /** * Determines number of differences (substrings that are not equal) between two strings. * @param first first string to compare. * @param second second string to compare. * @return number of different substrings. */ public int countDifferencesBetweenAnd(String first, String second) { if (first == null) { if (second == null) { return 0; } else { first = ""; } } else if (second == null) { second = ""; } LinkedList<DiffMatchPatch.Diff> diffs = getDiffs(first, second); int diffCount = 0; for (DiffMatchPatch.Diff diff : diffs) { if (diff.operation != DiffMatchPatch.Operation.EQUAL) { diffCount++; } } return diffCount; } protected LinkedList<DiffMatchPatch.Diff> getDiffs(String first, String second) { LinkedList<DiffMatchPatch.Diff> diffs = diffMatchPatch.diff_main(cleanupValue(first), cleanupValue(second)); diffMatchPatch.diff_cleanupSemantic(diffs); return diffs; } /** * Determines difference between two strings, ignoring whitespace changes. * @param first first string to compare. * @param second second string to compare. * @return HTML of difference between the two. */ public String differenceBetweenIgnoreWhitespaceAnd(String first, String second) { String cleanFirst = allWhitespaceToSingleSpace(first); String cleanSecond = allWhitespaceToSingleSpace(second); String cleanDiff = differenceBetweenAnd(cleanFirst, cleanSecond); if (cleanDiff != null) { if (("<div>"+ cleanFirst + "</div>").equals(cleanDiff)) { cleanDiff = "<div>" + first + "</div>"; } else if (cleanFirst != null && cleanFirst.equals(cleanDiff)) { cleanDiff = first; } } return cleanDiff; } /** * Determines number of differences (substrings that are not equal) between two strings, * ignoring differences in whitespace. * @param first first string to compare. * @param second second string to compare. * @return number of different substrings. */ public int countDifferencesBetweenIgnoreWhitespaceAnd(String first, String second) { String cleanFirst = allWhitespaceToSingleSpace(first); String cleanSecond = allWhitespaceToSingleSpace(second); return countDifferencesBetweenAnd(cleanFirst, cleanSecond); } protected String allWhitespaceToSingleSpace(String value) { return value != null ? value // unicode non breaking space to normal space .replace("\u00A0", " ") // all sequences of whitespace replaced by single space .replaceAll("\\s+", " ") : null; } protected String diffToHtml(String rootTag, LinkedList<DiffMatchPatch.Diff> diffs, Formatter whitespaceFormatter) { StringBuilder html = new StringBuilder("<"); html.append(rootTag); html.append(">"); if (diffs.size() == 1 && diffs.get(0).operation == DiffMatchPatch.Operation.EQUAL) { html.append(StringEscapeUtils.escapeHtml4(diffs.get(0).text)); } else { for (DiffMatchPatch.Diff aDiff : diffs) { String text = StringEscapeUtils.escapeHtml4(aDiff.text); switch (aDiff.operation) { case INSERT: text = whitespaceFormatter.format(text); html.append("<ins class=\"collapse_rim\">").append(text) .append("</ins>"); break; case DELETE: text = whitespaceFormatter.format(text); html.append("<del class=\"collapse_rim\">").append(text) .append("</del>"); break; case EQUAL: html.append("<span>").append(text).append("</span>"); break; } } } html.append("</"); html.append(rootTag); html.append(">"); return html.toString(); } protected String ensureWhitespaceVisible(String text) { return text.replace(" ", " ") .replaceAll("\r?\n", " <br/>"); } protected String explicitWhitespace(String text) { return text.replace(" ", "·") .replace("\r", "↵") .replace("\n", "¶<br/>") .replace("\t", "→") .replace(" ", "•"); } }