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
17package com.android.dumprendertree2;
18
19import name.fraser.neil.plaintext.diff_match_patch;
20
21import java.util.LinkedList;
22
23/**
24 * Helper methods fo TextResult.getDiffAsHtml()
25 */
26public 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 = "&nbsp;";
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("  ", "&nbsp;&nbsp;")
168                    + endSpan + br;
169            lines.add(line);
170            line = "";
171        } else {
172            line += begSpan + diff.text.replace("  ", "&nbsp;&nbsp;") + 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