/**
* Copyright Copyright 2010-14 Simon Andrews
*
* This file is part of BamQC.
*
* BamQC is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* BamQC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with BamQC; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Changelog:
* - Simon Andrews: Class creation.
*/
package uk.ac.babraham.BamQC.Report;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import uk.ac.babraham.BamQC.BamQCApplication;
import uk.ac.babraham.BamQC.BamQCConfig;
import uk.ac.babraham.BamQC.BamQCException;
import uk.ac.babraham.BamQC.Modules.QCModule;
import uk.ac.babraham.BamQC.Sequence.SequenceFile;
import uk.ac.babraham.BamQC.Utilities.ImageToBase64;
/**
*
* @author Simon Andrews
*
*/
public class HTMLReportArchive {
private static Logger log = Logger.getLogger(HTMLReportArchive.class);
private XMLStreamWriter xhtml=null;
private StringBuffer data = new StringBuffer();
private QCModule [] modules;
private ZipOutputStream zip;
private SequenceFile sequenceFile;
private byte [] buffer = new byte[1024];
private File htmlFile;
private File zipFile;
public HTMLReportArchive (SequenceFile sequenceFile, QCModule [] modules, File htmlFile) throws BamQCException, IOException, XMLStreamException {
if(modules == null) {
throw new BamQCException("File was not processed.");
}
this.sequenceFile = sequenceFile;
this.modules = modules;
this.htmlFile = htmlFile;
this.zipFile = new File(htmlFile.getAbsoluteFile().toString().replaceAll("\\.html$", "")+".zip");
StringWriter htmlStr = new StringWriter();
XMLOutputFactory xmlfactory = XMLOutputFactory.newInstance();
this.xhtml= xmlfactory.createXMLStreamWriter(htmlStr);
zip = new ZipOutputStream(new FileOutputStream(zipFile));
zip.putNextEntry(new ZipEntry(folderName()+"/"));
zip.putNextEntry(new ZipEntry(folderName()+"/Icons/"));
zip.putNextEntry(new ZipEntry(folderName()+"/Images/"));
startDocument();
for (int m=0;m<modules.length;m++) {
if (modules[m].ignoreInReport()) continue;
xhtml.writeStartElement("div");
xhtml.writeAttribute("class", "module");
xhtml.writeStartElement("h2");
xhtml.writeAttribute("id", "M"+m);
// Add an icon before the module name
if (modules[m].raisesError())
{
xhtml.writeEmptyElement("img");
xhtml.writeAttribute("src",base64ForIcon("Icons/error.png"));
xhtml.writeAttribute("alt","[FAIL]");
}
else if (modules[m].raisesWarning())
{
xhtml.writeEmptyElement("img");
xhtml.writeAttribute("src",base64ForIcon("Icons/warning.png"));
xhtml.writeAttribute("alt","[WARN]");
}
else {
xhtml.writeEmptyElement("img");
xhtml.writeAttribute("src",base64ForIcon("Icons/tick.png"));
xhtml.writeAttribute("alt","[OK]");
}
xhtml.writeCharacters(modules[m].name());
data.append(">>");
data.append(modules[m].name());
data.append("\t");
if (modules[m].raisesError()) {
data.append("fail");
}
else if (modules[m].raisesWarning()) {
data.append("warn");
}
else {
data.append("pass");
}
data.append("\n");
xhtml.writeEndElement();
modules[m].makeReport(this);
data.append(">>END_MODULE\n");
xhtml.writeEndElement();
}
closeDocument();
zip.putNextEntry(new ZipEntry(folderName()+"/bamqc_report.html"));
xhtml.flush();
xhtml.close();
zip.write(htmlStr.toString().getBytes());
zip.closeEntry();
zip.putNextEntry(new ZipEntry(folderName()+"/bamqc_data.txt"));
zip.write(data.toString().getBytes());
zip.closeEntry();
//XSL-FO
try {
DocumentBuilderFactory domFactory=DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(false);
DocumentBuilder builder=domFactory.newDocumentBuilder();
Document src=builder.parse(new InputSource( new StringReader(htmlStr.toString())));
InputStream rsrc=getClass().getResourceAsStream("/Templates/bamqc2fo.xsl");
if(rsrc!=null)
{
domFactory.setNamespaceAware(true);
builder=domFactory.newDocumentBuilder();
Document html2fo=builder.parse(rsrc);
rsrc.close();
TransformerFactory tf=TransformerFactory.newInstance();
Templates templates=tf.newTemplates(new DOMSource(html2fo));
zip.putNextEntry(new ZipEntry(folderName()+"/bamqc.fo"));
templates.newTransformer().transform(new DOMSource(src), new StreamResult(zip));
zip.closeEntry();
}
}
catch (Exception e) {
log.error(e, e);
}
zip.close();
// Save the HTML file at the same level as the zip file
PrintWriter pr = new PrintWriter(new FileWriter(htmlFile));
pr.print(htmlStr.toString());
pr.close();
if (BamQCConfig.getInstance().do_unzip) {
unzipZipFile(zipFile);
}
}
private void unzipZipFile (File file) throws IOException {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
int size;
byte [] buffer = new byte[1024];
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
log.debug("Going to extract '"+entry.getName()+"'");
if (entry.isDirectory()) {
File dir = new File(file.getParent()+"/"+entry.getName());
if (dir.exists() && dir.isDirectory()) continue; // Don't need to do anything
if (dir.exists() && ! dir.isDirectory()) throw new IOException ("File exists with dir name "+dir.getName());
if (!dir.mkdir()) throw new IOException("Failed to make dir for "+dir.getName());
continue;
}
BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getParent()+"/"+entry.getName()),buffer.length);
while ((size = bis.read(buffer,0,buffer.length)) != -1) {
bos.write(buffer,0,size);
}
bos.flush();
bos.close();
bis.close();
}
} catch(IOException e) {
throw e;
} finally {
if(zipFile != null)
zipFile.close();
}
}
public XMLStreamWriter xhtmlStream ()
{
return this.xhtml;
}
public StringBuffer dataDocument() {
return data;
}
public String folderName () {
return htmlFile.getName().replaceAll("\\.html$", "");
}
public ZipOutputStream zipFile () {
return zip;
}
private void startDocument () throws IOException,XMLStreamException
{
// Just put the fastQC version at the start of the text report
data.append("##BamQC\t");
data.append(BamQCApplication.VERSION);
data.append("\n");
// Add in the icon files for pass/fail/warn
for(String icnName:new String[]{
"bamqc_icon.png",
"warning.png",
"error.png",
"tick.png"})
{
InputStream in =getClass().getResourceAsStream("/Templates/Icons/"+icnName);
if(in==null) continue;
zip.putNextEntry(new ZipEntry(folderName()+"/Icons/"+icnName));
int len;
while ((len = in.read(buffer)) > 0) {
zip.write(buffer, 0, len);
}
in.close();
zip.closeEntry();
}
SimpleDateFormat df = new SimpleDateFormat("EEE d MMM yyyy");
xhtml.writeStartElement("html");
xhtml.writeStartElement("head");
xhtml.writeStartElement("title");
xhtml.writeCharacters(sequenceFile.name());
xhtml.writeCharacters(" BamQC Report");
xhtml.writeEndElement();//title
InputStream rsrc=getClass().getResourceAsStream("/Templates/header_template.html");
if(rsrc!=null)
{
xhtml.writeStartElement("style");
xhtml.writeAttribute("type", "text/css");
byte array[]=new byte[128];
int nRead;
while((nRead=rsrc.read(array))!=-1) { xhtml.writeCharacters(new String(array,0,nRead));}
rsrc.close();
xhtml.writeEndElement();//style
}
xhtml.writeEndElement();//head
xhtml.writeStartElement("body");
xhtml.writeStartElement("div");
xhtml.writeAttribute("class", "header");
xhtml.writeStartElement("div");
xhtml.writeAttribute("id", "header_title");
xhtml.writeEmptyElement("img");
xhtml.writeAttribute("src", base64ForIcon("Icons/bamqc_icon.png"));
xhtml.writeAttribute("alt", "BamQC");
xhtml.writeCharacters("BamQC Report");
xhtml.writeEndElement();//div
xhtml.writeStartElement("div");
xhtml.writeAttribute("id", "header_filename");
xhtml.writeCharacters(df.format(new Date()));
xhtml.writeEmptyElement("br");
xhtml.writeCharacters(sequenceFile.name());
xhtml.writeEndElement();//div
xhtml.writeEndElement();//div
xhtml.writeStartElement("div");
xhtml.writeAttribute("class", "summary");
xhtml.writeStartElement("h2");
xhtml.writeCharacters("Summary");
xhtml.writeEndElement();//h2
xhtml.writeStartElement("ul");
StringBuffer summaryText = new StringBuffer();
for (int m=0;m<modules.length;m++) {
if (modules[m].ignoreInReport()) {
continue;
}
xhtml.writeStartElement("li");
xhtml.writeEmptyElement("img");
if (modules[m].raisesError()) {
xhtml.writeAttribute("src", base64ForIcon("Icons/error.png"));
xhtml.writeAttribute("alt", "[FAIL]");
summaryText.append("FAIL");
}
else if (modules[m].raisesWarning()) {
xhtml.writeAttribute("src", base64ForIcon("Icons/warning.png"));
xhtml.writeAttribute("alt", "[WARNING]");
summaryText.append("WARN");
}
else {
xhtml.writeAttribute("src", base64ForIcon("Icons/tick.png"));
xhtml.writeAttribute("alt", "[PASS]");
summaryText.append("PASS");
}
summaryText.append("\t");
summaryText.append(modules[m].name());
summaryText.append("\t");
summaryText.append(sequenceFile.name());
summaryText.append(BamQCConfig.getInstance().lineSeparator);
xhtml.writeStartElement("a");
xhtml.writeAttribute("href", "#M"+m);
xhtml.writeCharacters(modules[m].name());
xhtml.writeEndElement();//a
xhtml.writeEndElement();//li
}
xhtml.writeEndElement();//ul
xhtml.writeEndElement();//div
xhtml.writeStartElement("div");
xhtml.writeAttribute("class", "main");
zip.putNextEntry(new ZipEntry(folderName()+"/summary.txt"));
zip.write(summaryText.toString().getBytes());
}
private String base64ForIcon (String path) {
try {
BufferedImage b = ImageIO.read(ClassLoader.getSystemResource("Templates/"+path));
return (ImageToBase64.imageToBase64(b));
}
catch (IOException ioe) {
log.error(ioe, ioe);
return "Failed";
}
}
private void closeDocument () throws XMLStreamException {
xhtml.writeEndElement();//div
xhtml.writeStartElement("div");
xhtml.writeAttribute("class", "footer");
xhtml.writeCharacters("Produced by ");
xhtml.writeStartElement("a");
xhtml.writeAttribute("href", "http://www.bioinformatics.babraham.ac.uk/projects/bamqc/");
xhtml.writeCharacters("BamQC");
xhtml.writeEndElement();//a
xhtml.writeCharacters(" (version "+BamQCApplication.VERSION+")");
xhtml.writeEndElement();//div
xhtml.writeEndElement();//body
xhtml.writeEndElement();//html
}
}