1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dumprendertree2; 18 19 import name.fraser.neil.plaintext.diff_match_patch; 20 21 import java.util.LinkedList; 22 23 /** 24 * Helper methods fo TextResult.getDiffAsHtml() 25 */ 26 public class VisualDiffUtils { 27 28 private static final int DONT_PRINT_LINE_NUMBER = -1; 29 30 /** 31 * Preprocesses the list of diffs so that new line characters appear only at the end of 32 * diff.text 33 * 34 * @param diffs 35 * @return 36 * LinkedList of diffs where new line character appears only on the end of 37 * diff.text 38 */ 39 public static LinkedList<diff_match_patch.Diff> splitDiffsOnNewline( 40 LinkedList<diff_match_patch.Diff> diffs) { 41 LinkedList<diff_match_patch.Diff> newDiffs = new LinkedList<diff_match_patch.Diff>(); 42 43 String[] parts; 44 int lengthMinusOne; 45 for (diff_match_patch.Diff diff : diffs) { 46 parts = diff.text.split("\n", -1); 47 if (parts.length == 1) { 48 newDiffs.add(diff); 49 continue; 50 } 51 52 lengthMinusOne = parts.length - 1; 53 for (int i = 0; i < lengthMinusOne; i++) { 54 newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[i] + "\n")); 55 } 56 if (!parts[lengthMinusOne].isEmpty()) { 57 newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[lengthMinusOne])); 58 } 59 } 60 61 return newDiffs; 62 } 63 64 public static void generateExpectedResultLines(LinkedList<diff_match_patch.Diff> diffs, 65 LinkedList<Integer> lineNums, LinkedList<String> lines) { 66 String delSpan = "<span class=\"del\">"; 67 String eqlSpan = "<span class=\"eql\">"; 68 69 String line = ""; 70 int i = 1; 71 diff_match_patch.Diff diff; 72 int size = diffs.size(); 73 boolean isLastDiff; 74 for (int j = 0; j < size; j++) { 75 diff = diffs.get(j); 76 isLastDiff = j == size - 1; 77 switch (diff.operation) { 78 case DELETE: 79 line = processDiff(diff, lineNums, lines, line, i, delSpan, isLastDiff); 80 if (line.equals("")) { 81 i++; 82 } 83 break; 84 85 case INSERT: 86 // If the line is currently empty and this insertion is the entire line, the 87 // expected line is absent, so it has no line number. 88 if (diff.text.endsWith("\n") || isLastDiff) { 89 lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++); 90 lines.add(line); 91 line = ""; 92 } 93 break; 94 95 case EQUAL: 96 line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff); 97 if (line.equals("")) { 98 i++; 99 } 100 break; 101 } 102 } 103 } 104 105 public static void generateActualResultLines(LinkedList<diff_match_patch.Diff> diffs, 106 LinkedList<Integer> lineNums, LinkedList<String> lines) { 107 String insSpan = "<span class=\"ins\">"; 108 String eqlSpan = "<span class=\"eql\">"; 109 110 String line = ""; 111 int i = 1; 112 diff_match_patch.Diff diff; 113 int size = diffs.size(); 114 boolean isLastDiff; 115 for (int j = 0; j < size; j++) { 116 diff = diffs.get(j); 117 isLastDiff = j == size - 1; 118 switch (diff.operation) { 119 case INSERT: 120 line = processDiff(diff, lineNums, lines, line, i, insSpan, isLastDiff); 121 if (line.equals("")) { 122 i++; 123 } 124 break; 125 126 case DELETE: 127 // If the line is currently empty and deletion is the entire line, the 128 // actual line is absent, so it has no line number. 129 if (diff.text.endsWith("\n") || isLastDiff) { 130 lineNums.add(line.equals("") ? DONT_PRINT_LINE_NUMBER : i++); 131 lines.add(line); 132 line = ""; 133 } 134 break; 135 136 case EQUAL: 137 line = processDiff(diff, lineNums, lines, line, i, eqlSpan, isLastDiff); 138 if (line.equals("")) { 139 i++; 140 } 141 break; 142 } 143 } 144 } 145 146 /** 147 * Generate or append a line for a given diff and add it to given collections if necessary. 148 * It puts diffs in HTML spans. 149 * 150 * @param diff 151 * @param lineNums 152 * @param lines 153 * @param line 154 * @param i 155 * @param begSpan 156 * @param forceOutputLine Force the current line to be output 157 * @return 158 */ 159 public static String processDiff(diff_match_patch.Diff diff, LinkedList<Integer> lineNums, 160 LinkedList<String> lines, String line, int i, String begSpan, boolean forceOutputLine) { 161 String endSpan = "</span>"; 162 String br = " "; 163 164 if (diff.text.endsWith("\n") || forceOutputLine) { 165 lineNums.add(i); 166 /** TODO: Think of better way to replace stuff */ 167 line += begSpan + diff.text.replace(" ", " ") 168 + endSpan + br; 169 lines.add(line); 170 line = ""; 171 } else { 172 line += begSpan + diff.text.replace(" ", " ") + endSpan; 173 } 174 175 return line; 176 } 177 178 public static String getHtml(LinkedList<Integer> lineNums1, LinkedList<String> lines1, 179 LinkedList<Integer> lineNums2, LinkedList<String> lines2) { 180 StringBuilder html = new StringBuilder(); 181 int lineNum; 182 int size = lines1.size(); 183 for (int i = 0; i < size; i++) { 184 html.append("<tr class=\"results\">"); 185 186 html.append(" <td class=\"line_count\">"); 187 lineNum = lineNums1.removeFirst(); 188 if (lineNum > 0) { 189 html.append(lineNum); 190 } 191 html.append(" </td>"); 192 193 html.append(" <td class=\"line\">"); 194 html.append(lines1.removeFirst()); 195 html.append(" </td>"); 196 197 html.append(" <td class=\"space\"></td>"); 198 199 html.append(" <td class=\"line_count\">"); 200 lineNum = lineNums2.removeFirst(); 201 if (lineNum > 0) { 202 html.append(lineNum); 203 } 204 html.append(" </td>"); 205 206 html.append(" <td class=\"line\">"); 207 html.append(lines2.removeFirst()); 208 html.append(" </td>"); 209 210 html.append("</tr>"); 211 } 212 return html.toString(); 213 } 214 } 215