/*$Id$*/
package at.jku.sii.sqlitereader;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Queue;
import java.util.Stack;
import at.jku.sii.sqlitereader.annotator.SimpleAnnotation;
import at.jku.sii.sqlitereader.annotator.SimpleAnnotator;
public class HTMLAnnotationRenderer {
private static final int LINE_LENGTH = 32; // bytes
public static void render(SqliteDataBase db, File rawData) {
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(new File(rawData.getAbsolutePath() + ".html")));
printHeader(out, rawData);
renderRawData(db.getAnnotator(), rawData, out);
renderDB(db, out);
printFooter(out);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (out != null)
try {
out.close();
} catch (IOException e) {
}
}
}
private static void renderDB(SqliteDataBase db, BufferedWriter out) throws IOException {
out.append("<h1>Database</h1>");
out.newLine();
{
StringBuilder b = new StringBuilder();
db.dump(b);
out.append(b);
}
}
private static void renderRawData(SimpleAnnotator annotations, File rawData, BufferedWriter out) {
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(rawData));
Queue<SimpleAnnotation> unseen = annotations.asDeque();
Stack<SimpleAnnotation> opened = new Stack<SimpleAnnotation>();
byte[] line = new byte[LINE_LENGTH];
out.append("<h1>Raw Data View</h1>");
out.append("<div id=\"rawdata\">");
out.newLine();
long pos = 0;
int read = 0;
while ((read = in.read(line)) > 0) {
for (int i = 0; i < read; ++i) {
while (!unseen.isEmpty() && unseen.peek().getPos() <= pos) {
SimpleAnnotation a = unseen.poll();
printAnnotationStart(out, a);
opened.push(a);
}
pos++;
out.append(String.format("%02X", line[i]));
out.append(' ');
while (!opened.isEmpty() && shouldClose(opened.peek(), pos)) {
SimpleAnnotation a = opened.pop();
printAnnotationEnd(out, a);
}
}
newLine(out);
}
out.newLine();
out.append("</div>");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) { // ignore
}
}
}
private static boolean shouldClose(SimpleAnnotation peek, long pos) {
return peek.getPos() + peek.getLength() <= pos;
}
private static void printAnnotationStart(BufferedWriter out, SimpleAnnotation annotation) throws IOException {
out.append(String.format("<div id=\"%d\" ", annotation.getPos()));
String text = annotation.getText();
Object[] args = annotation.getArgs();
StringBuilder clazzes = new StringBuilder();
StringBuilder title = new StringBuilder();
if (args.length < 1) {
clazzes.append(uncaptilize(text)).append(' ');
} else {
Object arg = args[0];
if (arg instanceof Enum<?>) {
final CharSequence enumClazz = uncaptilize(arg.getClass().getSimpleName());
clazzes.append(enumClazz).append(' ');
clazzes.append(enumClazz).append('_').append(arg.toString().toLowerCase()).append(' ');
} else if (Character.isUpperCase(text.charAt(0))) {
clazzes.append(uncaptilize(text)).append(' ');
}
title.append(text).append(": ").append(arg);
for (int i = 1; i < args.length; ++i)
title.append(" ").append(args[i]);
// data attribute
out.append("data-value=\"");
if (args.length >= 2)
out.append(args[1].toString());
else
out.append(arg.toString());
out.append("\" ");
}
if(clazzes.length() > 0)
out.append("class=\"").append(clazzes).append("\" ");
if(title.length() > 0)
out.append("title=\"").append(title).append("\" ");
out.append(">");
}
private static CharSequence uncaptilize(String s) {
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
}
private static void printAnnotationEnd(BufferedWriter out, SimpleAnnotation annotation) throws IOException {
out.append("</div>");
}
private static void newLine(BufferedWriter out) throws IOException {
out.newLine();
}
private static void printFooter(BufferedWriter out) throws IOException {
out.newLine();
out.append("</div>");
out.newLine();
out.append("</body></html>");
out.newLine();
}
private static void printHeader(BufferedWriter out, File rawFile) throws IOException {
out.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
out.newLine();
out.append("<html><head><title>").append(rawFile.getName()).append("</title><style type=\"text/css\">");
out.newLine();
printStyleRules(out);
out.newLine();
out.append("</style></head><body>");
out.newLine();
}
private static void printStyleRules(BufferedWriter out) throws IOException {
out.append(rule("div").add("font-family", "\"Courier New\",courier").add("display", "inline").toString());
out.append(rule("table, tr, th, td").add("border-spacing", "0").add("border", "1px solid black").toString());
out.append(rule(".header").add("background-color", "rgba(255,255,0,0.75)").toString());
out.append(rule(".unused").add("background-color", "rgba(255,255,255,1)").add("color", "rgb(192,192,192)").toString());
out.append(rule(".reserved").add("background-color", "rgba(64,64,64,1)").add("color", "rgb(192,192,192)").toString());
// cell formatting
out.append(rule(".cell").add("border", "1px solid rgba(192,255,192,0.75)").add("background-color", "rgba(192,255,192,0.75)").add("padding", "0 5px")
.toString());
// record format
out.append(rule(".record").add("background-color", "rgba(255,192,192,0.75)").add("padding", "0 5px").toString());
out.append(rule(".recordHeader").add("background-color", "rgba(255,192,192,0.75)").toString());
out.append(rule(".page").add("display", "block").add("border", "3px solid black").add("margin", "10px").toString());
// highlight the area of the div we are currently hovered
out.append(rule("div[title]:hover").add("background-color", "rgba(0,0,220,0.25)").toString());
}
public static Rule rule(String selection) {
return new Rule(selection);
}
private static class Rule {
private final StringBuilder b = new StringBuilder();
public Rule(String selection) {
this.b.append(selection).append(" {\n");
}
public Rule add(CharSequence property, CharSequence value) {
this.b.append(' ').append(property).append(": ").append(value).append(";\n");
return this;
}
@Override
public String toString() {
return this.b + "}\n";
}
}
}