package fitnesse.testsystems.slim; import java.util.ArrayList; import java.util.Collections; import java.util.List; import difflib.Chunk; import difflib.Delta; import difflib.DiffUtils; import difflib.Patch; import fitnesse.html.HtmlUtil; public class HtmlDiffUtil { public abstract static class Builder { List<Character> text; final Patch<Character> patch; final StringBuilder stringBuilder = new StringBuilder(); boolean isInDiffArea = false; String openingTag = "<span class=\"diff\">"; String closingTag = "</span>"; protected Builder(final String actual, final String expected) { this.patch = DiffUtils.diff(stringToCharacterList(actual), stringToCharacterList(expected)); } public Builder setOpeningTag(final String openingTag) { this.openingTag = openingTag; return this; } public Builder setClosingTag(final String closingTag) { this.closingTag = closingTag; return this; } public String build() { for (int i = 0; i < getText().size(); i++) { addElementToStringBuilder(i); } return stringBuilder.toString(); } protected abstract Chunk<Character> getChunk(Delta<Character> d); private void addElementToStringBuilder(final int i) { Delta<Character> delta = getDeltaByIndex(i, patch); addOpeningTagIfDeltaAtFirstElement(i, delta); addTagIfDeltaSwitching(delta); addCharacterFromDeltaOrText(i, delta); addClosingTagIfDeltaAtLastElement(i, delta); } private void addCharacterFromDeltaOrText(final int i, final Delta<Character> delta) { if (delta == null) { stringBuilder.append(HtmlUtil.escapeHTML(getText().get(i).toString())); } else { stringBuilder.append(HtmlUtil .escapeHTML(getFromChunkByIndex(getChunk(delta), i).toString())); } } private void addTagIfDeltaSwitching(final Delta<Character> delta) { if (delta == null) { if (isInDiffArea) { addClosingTag(); } } else { if (!isInDiffArea) { addOpeningTag(); } } } private void addClosingTagIfDeltaAtLastElement(final int i, final Delta<Character> delta) { if (delta != null && isLastElement(i)) { addClosingTag(); } } private void addClosingTag() { isInDiffArea = false; stringBuilder.append(closingTag); } private void addOpeningTagIfDeltaAtFirstElement(final int i, final Delta<Character> delta) { if (delta != null && isFirstElement(i)) { addOpeningTag(); } } private void addOpeningTag() { isInDiffArea = true; stringBuilder.append(openingTag); } private Delta<Character> getDeltaByIndex(final int i, final Patch<Character> patch) { for (Delta<Character> delta : patch.getDeltas()) { if (isInChunk(getChunk(delta), i)) { return delta; } } return null; } private boolean isInChunk(final Chunk<Character> chunk, final int i) { return i >= chunk.getPosition() && i < chunk.getPosition() + chunk.size(); } private Character getFromChunkByIndex(final Chunk<Character> chunk, final int i) { int j = i - chunk.getPosition(); return chunk.getLines().get(j); } private List<Character> getText() { return text; } private boolean isFirstElement(final int i) { return i == 0; } private boolean isLastElement(final int i) { return i == getText().size() - 1; } protected List<Character> stringToCharacterList(final String s) { if (s == null || s.isEmpty()) { return Collections.<Character> emptyList(); } List<Character> characterList = new ArrayList<>(s.length()); for (char c : s.toCharArray()) { characterList.add(c); } return characterList; } } public static class ActualBuilder extends Builder { public ActualBuilder(final String actual, final String expected) { super(actual, expected); text = stringToCharacterList(actual); } @Override protected Chunk<Character> getChunk(final Delta<Character> d) { return d.getOriginal(); } } public static class ExpectedBuilder extends Builder { public ExpectedBuilder(final String actual, final String expected) { super(actual, expected); text = stringToCharacterList(expected); } @Override protected Chunk<Character> getChunk(final Delta<Character> d) { return d.getRevised(); } } public static String buildActual(final String actual, final String expected) { return new ActualBuilder(actual, expected).build(); } public static String buildExpected(final String actual, final String expected) { return new ExpectedBuilder(actual, expected).build(); } }