From 3935f2f32c307e412698c997bab6b39f9a82a3d0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 20 May 2026 22:45:10 +0000 Subject: [PATCH 1/2] Extract HTML report layout into Templates fragments Move stylesheet to Templates/fastqc.css and split the static HTML scaffold into report_template.html, sidebar_item.html, and module_wrapper.html. HTMLReportArchive assembles these with placeholder replacement; module bodies are still generated via XMLStreamWriter. Output verified byte-identical against master for test/data/minimal.fastq. Co-authored-by: Phil Ewels --- INSTALL.md | 2 +- .../{header_template.html => fastqc.css} | 0 Templates/module_wrapper.html | 1 + Templates/report_template.html | 1 + Templates/sidebar_item.html | 1 + .../FastQC/Report/HTMLReportArchive.java | 350 +++++++++--------- 6 files changed, 174 insertions(+), 181 deletions(-) rename Templates/{header_template.html => fastqc.css} (100%) create mode 100644 Templates/module_wrapper.html create mode 100644 Templates/report_template.html create mode 100644 Templates/sidebar_item.html diff --git a/INSTALL.md b/INSTALL.md index 10bf4d2..e97c762 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -151,7 +151,7 @@ zcat *fastq.gz | fastqc stdin:my_results If you want to run FastQC as part of a sequencing pipeline you may wish to change the formatting of the report to add in your own branding or to include extra information. -In the Templates directory you will find a file called `header_template.html` which +In the Templates directory you will find a file called `fastqc.css` which you can edit to change the look of the report. This file contains all of the header for the report file, including the CSS section and you can alter this however you see fit. diff --git a/Templates/header_template.html b/Templates/fastqc.css similarity index 100% rename from Templates/header_template.html rename to Templates/fastqc.css diff --git a/Templates/module_wrapper.html b/Templates/module_wrapper.html new file mode 100644 index 0000000..a145737 --- /dev/null +++ b/Templates/module_wrapper.html @@ -0,0 +1 @@ +

{{ALT}}{{MODULE_NAME}}

{{MODULE_CONTENT}}
diff --git a/Templates/report_template.html b/Templates/report_template.html new file mode 100644 index 0000000..eccf351 --- /dev/null +++ b/Templates/report_template.html @@ -0,0 +1 @@ +{{TITLE}} FastQC Report
FastQCFastQC Report
{{DATE}}
{{FILENAME}}

Summary

    {{SUMMARY_ITEMS}}
{{MODULE_CONTENT}}
diff --git a/Templates/sidebar_item.html b/Templates/sidebar_item.html new file mode 100644 index 0000000..21a8df0 --- /dev/null +++ b/Templates/sidebar_item.html @@ -0,0 +1 @@ +
  • {{ALT}}{{MODULE_NAME}}
  • diff --git a/uk/ac/babraham/FastQC/Report/HTMLReportArchive.java b/uk/ac/babraham/FastQC/Report/HTMLReportArchive.java index 334eda5..bc1f399 100644 --- a/uk/ac/babraham/FastQC/Report/HTMLReportArchive.java +++ b/uk/ac/babraham/FastQC/Report/HTMLReportArchive.java @@ -66,90 +66,58 @@ public class HTMLReportArchive { private byte [] buffer = new byte[1024]; private File htmlFile; private File zipFile; + private final String reportTemplate; + private final String sidebarItemTemplate; + private final String moduleWrapperTemplate; + private final String cssContent; public HTMLReportArchive (SequenceFile sequenceFile, QCModule [] modules, File htmlFile) throws IOException, XMLStreamException { this.sequenceFile = sequenceFile; this.modules = modules; this.htmlFile = htmlFile; this.zipFile = new File(htmlFile.getAbsoluteFile().toString().replaceAll("\\.html$", "")+".zip"); - StringWriter htmlStr = new StringWriter(); + this.reportTemplate = loadTemplate("/Templates/report_template.html"); + this.sidebarItemTemplate = loadTemplate("/Templates/sidebar_item.html"); + this.moduleWrapperTemplate = loadTemplate("/Templates/module_wrapper.html"); + this.cssContent = loadResource("/Templates/fastqc.css"); + XMLOutputFactory xmlfactory = XMLOutputFactory.newInstance(); - this.xhtml= xmlfactory.createXMLStreamWriter(htmlStr); + this.xhtml= xmlfactory.createXMLStreamWriter(new StringWriter()); 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>"); - 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()+"/fastqc_report.html")); + data.append("##FastQC\t"); + data.append(FastQCApplication.VERSION); + data.append("\n"); + addIconsToZip(); + + String moduleContent = generateModuleContent(); xhtml.flush(); xhtml.close(); - zip.write(htmlStr.toString().getBytes()); + + String finalHtml = generateHtmlFromTemplate(moduleContent); + + zip.putNextEntry(new ZipEntry(folderName()+"/fastqc_report.html")); + zip.write(finalHtml.getBytes()); zip.closeEntry(); zip.putNextEntry(new ZipEntry(folderName()+"/fastqc_data.txt")); zip.write(data.toString().getBytes()); zip.closeEntry(); - + + zip.putNextEntry(new ZipEntry(folderName()+"/summary.txt")); + zip.write(generateSummaryText().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()))); + Document src=builder.parse(new InputSource( new StringReader(finalHtml))); InputStream rsrc=getClass().getResourceAsStream("/Templates/fastqc2fo.xsl"); if(rsrc!=null) { @@ -176,7 +144,7 @@ else if (modules[m].raisesWarning()) { PrintWriter pr = new PrintWriter(new FileWriter(htmlFile)); - pr.print(htmlStr.toString()); + pr.print(finalHtml); pr.close(); @@ -238,15 +206,7 @@ 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("##FastQC\t"); - data.append(FastQCApplication.VERSION); - data.append("\n"); - - // Add in the icon files for pass/fail/warn + private void addIconsToZip() throws IOException { for(String icnName:new String[]{ "fastqc_icon.png", "warning.png", @@ -263,121 +223,172 @@ private void startDocument () throws IOException,XMLStreamException in.close(); zip.closeEntry(); } - - + } - SimpleDateFormat df = new SimpleDateFormat("EEE d MMM yyyy"); - - xhtml.writeDTD(""); - xhtml.writeStartElement("html"); - xhtml.writeStartElement("head"); - - xhtml.writeStartElement("title"); - xhtml.writeCharacters(sequenceFile.name()); - xhtml.writeCharacters(" FastQC Report"); - xhtml.writeEndElement();//title - - InputStream rsrc=getClass().getResourceAsStream("/Templates/header_template.html"); - if(rsrc!=null) - { - xhtml.writeStartElement("style"); - xhtml.writeAttribute("type", "text/css"); + private String loadResource(String resourcePath) throws IOException { + InputStream in = getClass().getResourceAsStream(resourcePath); + if (in == null) { + throw new IOException("Resource not found: " + resourcePath); + } - 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 - } + StringWriter writer = new StringWriter(); + byte[] readBuffer = new byte[1024]; + int nRead; + while ((nRead = in.read(readBuffer)) != -1) { + writer.write(new String(readBuffer, 0, nRead)); + } + in.close(); + return writer.toString(); + } - - - - 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/fastqc_icon.png")); - xhtml.writeAttribute("alt", "FastQC"); - xhtml.writeCharacters("FastQC 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"); - + private String loadTemplate(String templatePath) throws IOException { + return loadResource(templatePath).stripTrailing(); + } + + private String escapeXml(String s) { + return s.replace("&", "&").replace("<", "<").replace(">", ">"); + } + + private String generateSummaryItems() throws IOException { + StringBuffer summaryItems = new StringBuffer(); + + for (int m=0;m>"); + 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"); + + modules[m].makeReport(this); + data.append(">>END_MODULE\n"); + + moduleXhtml.flush(); + moduleXhtml.close(); + + this.xhtml = originalXhtml; + + String icon; + String alt; + if (modules[m].raisesError()) { + icon = base64ForIcon("Icons/error.png"); + alt = "[FAIL]"; + } + else if (modules[m].raisesWarning()) { + icon = base64ForIcon("Icons/warning.png"); + alt = "[WARN]"; + } + else { + icon = base64ForIcon("Icons/tick.png"); + alt = "[OK]"; + } + + String moduleWrapper = moduleWrapperTemplate + .replace("{{MODULE_INDEX}}", String.valueOf(m)) + .replace("{{ICON}}", icon) + .replace("{{ALT}}", alt) + .replace("{{MODULE_NAME}}", escapeXml(modules[m].name())) + .replace("{{MODULE_CONTENT}}", moduleBodyWriter.toString()); + + allModulesContent.append(moduleWrapper); + } + + return allModulesContent.toString(); } - + + private String generateHtmlFromTemplate(String moduleContent) throws IOException { + SimpleDateFormat df = new SimpleDateFormat("EEE d MMM yyyy"); + + return reportTemplate + .replace("{{TITLE}}", escapeXml(sequenceFile.name())) + .replace("{{CSS_CONTENT}}", escapeXml(cssContent)) + .replace("{{FASTQC_ICON}}", base64ForIcon("Icons/fastqc_icon.png")) + .replace("{{DATE}}", escapeXml(df.format(new Date()))) + .replace("{{FILENAME}}", escapeXml(sequenceFile.name())) + .replace("{{SUMMARY_ITEMS}}", generateSummaryItems()) + .replace("{{MODULE_CONTENT}}", moduleContent) + .replace("{{VERSION}}", FastQCApplication.VERSION); + } + private String base64ForIcon (String path) { try { BufferedImage b = ImageIO.read(ClassLoader.getSystemResource("Templates/"+path)); @@ -388,25 +399,4 @@ private String base64ForIcon (String path) { 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/fastqc/"); - xhtml.writeCharacters("FastQC"); - xhtml.writeEndElement();//a - xhtml.writeCharacters(" (version "+FastQCApplication.VERSION+")"); - xhtml.writeEndElement();//div - - xhtml.writeEndElement();//body - xhtml.writeEndElement();//html - } - - - - } From 518614dbd8733dcee72d61f2075b781c73ceb79c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 20 May 2026 22:56:30 +0000 Subject: [PATCH 2/2] Format HTML report template fragments with 2-space indent Makes report_template.html, sidebar_item.html, and module_wrapper.html easier to read and edit. Generated HTML reports are no longer minified. Co-authored-by: Phil Ewels --- Templates/module_wrapper.html | 7 ++++++- Templates/report_template.html | 32 +++++++++++++++++++++++++++++++- Templates/sidebar_item.html | 5 ++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Templates/module_wrapper.html b/Templates/module_wrapper.html index a145737..4d071bb 100644 --- a/Templates/module_wrapper.html +++ b/Templates/module_wrapper.html @@ -1 +1,6 @@ -

    {{ALT}}{{MODULE_NAME}}

    {{MODULE_CONTENT}}
    +
    +

    + {{ALT}}{{MODULE_NAME}} +

    +{{MODULE_CONTENT}} +
    diff --git a/Templates/report_template.html b/Templates/report_template.html index eccf351..ebcd91d 100644 --- a/Templates/report_template.html +++ b/Templates/report_template.html @@ -1 +1,31 @@ -{{TITLE}} FastQC Report
    FastQCFastQC Report
    {{DATE}}
    {{FILENAME}}

    Summary

      {{SUMMARY_ITEMS}}
    {{MODULE_CONTENT}}
    + + + + {{TITLE}} FastQC Report + + + +
    +
    + FastQCFastQC Report +
    +
    + {{DATE}}
    {{FILENAME}} +
    +
    +
    +

    Summary

    +
      +{{SUMMARY_ITEMS}} +
    +
    +
    +{{MODULE_CONTENT}} +
    + + + diff --git a/Templates/sidebar_item.html b/Templates/sidebar_item.html index 21a8df0..1eb5bca 100644 --- a/Templates/sidebar_item.html +++ b/Templates/sidebar_item.html @@ -1 +1,4 @@ -
  • {{ALT}}{{MODULE_NAME}}
  • +
  • + {{ALT}} + {{MODULE_NAME}} +