/** * Convert a Difference list into a pretty HTML report. * * @param diffs List of Difference objects * @return HTML representation */ public String prettyHtml(List<Difference> diffs) { StringBuilder buf = new StringBuilder(); for (int x = 0; x < diffs.size(); x++) { Difference diff = diffs.get(x); EditType editType = diff.getEditType(); // Mode (delete, equal, // insert) String text = diff.getText(); // Text of change. // TODO(DMS): Do replacements // text = text.replace(/&/g, "&"); // text = text.replace(/</g, "<"); // text = text.replace(/>/g, ">"); // text = text.replace(/\n/g, "¶<BR>"); if (EditType.DELETE.equals(editType)) { buf.append("<del style=\"background:#FFE6E6;\">"); buf.append(text); buf.append("</del>"); } else if (EditType.INSERT.equals(editType)) { buf.append("<ins style=\"background:#E6FFE6;\">"); buf.append(text); buf.append("</ins>"); } else { buf.append("<span>"); buf.append(text); buf.append("</span>"); } } return buf.toString(); }
/** * loc is a location in source, compute and return the equivalent location in target. e.g. "The * cat" vs "The big cat", 1->1, 5->8 * * @param diffs List of Difference objects * @param loc Location within source * @return Location within target */ public int xIndex(final List<Difference> diffs, final int loc) { int chars1 = 0; int chars2 = 0; int lastChars1 = 0; int lastChars2 = 0; Difference lastDiff = null; for (Difference diff : diffs) { EditType editType = diff.getEditType(); // Equality or deletion? if (!EditType.INSERT.equals(editType)) { chars1 += diff.getText().length(); } // Equality or insertion? if (!EditType.DELETE.equals(editType)) { chars2 += diff.getText().length(); } // Overshot the location? if (chars1 > loc) { lastDiff = diff; break; } lastChars1 = chars1; lastChars2 = chars2; } // Was the location was deleted? if (lastDiff != null && EditType.DELETE.equals(lastDiff.getEditType())) { return lastChars2; } // Add the remaining character length. return lastChars2 + (loc - lastChars1); }
/** * Find the differences between two texts. * * @return List of Difference objects */ private List<Difference> compute() { List<Difference> diffs = new ArrayList<Difference>(); if ("".equals(source)) { // Just add some text (speedup) diffs.add(new Difference(EditType.INSERT, target)); return diffs; } if ("".equals(target)) { // Just delete some text (speedup) diffs.add(new Difference(EditType.DELETE, source)); return diffs; } String longText = source.length() > target.length() ? source : target; String shortText = source.length() > target.length() ? target : source; int i = longText.indexOf(shortText); if (i != -1) { // Shorter text is inside the longer text (speedup) EditType editType = (source.length() > target.length()) ? EditType.DELETE : EditType.INSERT; diffs.add(new Difference(editType, longText.substring(0, i))); diffs.add(new Difference(EditType.EQUAL, shortText)); diffs.add(new Difference(editType, longText.substring(i + shortText.length()))); return diffs; } // Check to see if the problem can be split in two. CommonMiddle middleMatch = Commonality.halfMatch(source, target); if (middleMatch != null) { // A half-match was found, sort out the return data. // Send both pairs off for separate processing. Diff startDiff = new Diff(middleMatch.getSourcePrefix(), middleMatch.getTargetPrefix(), checkLines); Diff endDiff = new Diff(middleMatch.getSourceSuffix(), middleMatch.getTargetSuffix(), checkLines); // Merge the results. diffs = startDiff.compare(); diffs.add(new Difference(EditType.EQUAL, middleMatch.getCommonality())); diffs.addAll(endDiff.compare()); return diffs; } // Perform a real diff. if (checkLines && source.length() + target.length() < 250) { checkLines = false; // Too trivial for the overhead. } LineMap lineMap = null; if (checkLines) { // Scan the text on a line-by-line basis first. lineMap = new LineMap(source, target); source = lineMap.getSourceMap(); target = lineMap.getTargetMap(); } diffs = new DifferenceEngine(source, target).generate(); if (diffs == null) { // No acceptable result. diffs = new ArrayList<Difference>(); diffs.add(new Difference(EditType.DELETE, source)); diffs.add(new Difference(EditType.INSERT, target)); } if (checkLines && lineMap != null) { // Convert the diff back to original text. lineMap.restore(diffs); // Eliminate freak matches (e.g. blank lines) DiffCleanup.cleanupSemantic(diffs); // Rediff any replacement blocks, this time character-by-character. // Add a dummy entry at the end. diffs.add(new Difference(EditType.EQUAL, "")); int countDeletes = 0; int countInserts = 0; StringBuilder textDelete = new StringBuilder(); StringBuilder textInsert = new StringBuilder(); ListIterator<Difference> pointer = diffs.listIterator(); Difference curDiff = pointer.next(); while (curDiff != null) { EditType editType = curDiff.getEditType(); if (EditType.INSERT.equals(editType)) { countInserts++; textInsert.append(curDiff.getText()); } else if (EditType.DELETE.equals(editType)) { countDeletes++; textDelete.append(curDiff.getText()); } else { // Upon reaching an equality, check for prior redundancies. if (countDeletes >= 1 && countInserts >= 1) { // Delete the offending records and add the merged ones. pointer.previous(); for (int j = 0; j < countDeletes + countInserts; j++) { pointer.previous(); pointer.remove(); } Diff newDiff = new Diff(textDelete.toString(), textInsert.toString(), false); for (Difference diff : newDiff.compare()) { pointer.add(diff); } } countInserts = 0; countDeletes = 0; textDelete.delete(0, textDelete.length()); textInsert.delete(0, textInsert.length()); } curDiff = pointer.hasNext() ? pointer.next() : null; } diffs.remove(diffs.size() - 1); // Remove the dummy entry at the // end. } return diffs; }