package net.rubygrapefruit.docs.html;
import net.rubygrapefruit.docs.model.*;
import net.rubygrapefruit.docs.model.Component;
import net.rubygrapefruit.docs.model.Error;
import net.rubygrapefruit.docs.model.List;
import net.rubygrapefruit.docs.renderer.Chunk;
import net.rubygrapefruit.docs.renderer.MultiPageRenderer;
import net.rubygrapefruit.docs.renderer.Page;
import net.rubygrapefruit.docs.renderer.TitleBlock;
import net.rubygrapefruit.docs.theme.TextTheme;
import net.rubygrapefruit.docs.theme.Theme;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.awt.*;
import java.io.OutputStream;
public class HtmlRenderer extends MultiPageRenderer {
private final String EOL = String.format("%n");
@Override
protected void doRender(Page page, Theme theme, OutputStream stream) throws Exception {
XMLStreamWriter writer = XMLOutputFactory.newFactory().createXMLStreamWriter(stream, "utf-8");
try {
writer.writeDTD(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
writer.writeCharacters(EOL);
writer.writeStartElement("html");
writer.writeCharacters(EOL);
writer.writeStartElement("head");
writer.writeCharacters(EOL);
writer.writeStartElement("meta");
writer.writeAttribute("http-equiv", "Content-Type");
writer.writeAttribute("content", "text/html; charset=UTF-8");
writer.writeEndElement();
writer.writeCharacters(EOL);
writer.writeStartElement("style");
writer.writeCharacters(EOL);
TextTheme textTheme = theme.getAspect(TextTheme.class);
if (textTheme != null) {
writer.writeCharacters("html { font-family: ");
writer.writeCharacters(textTheme.getFontName());
writer.writeCharacters("; font-size: 12pt; color: ");
writer.writeCharacters(toRGB(textTheme.getColour()));
writer.writeCharacters("; line-height: normal;");
writer.writeCharacters("}\n");
writer.writeCharacters("body { margin: 3em 5em; background-color: white; }\n");
writer.writeCharacters("h1, h2, h3, h4, h5, h6 { font-family: ");
writer.writeCharacters(textTheme.getHeaderFontName());
writer.writeCharacters("; color: ");
writer.writeCharacters(toRGB(textTheme.getHeaderColour()));
writer.writeCharacters("; }\n");
writer.writeCharacters("p { line-height: ");
writer.writeCharacters(textTheme.getLineSpacing().toString());
writer.writeCharacters("; }\n");
writer.writeCharacters("p.title { font-weight: bold; }");
writer.writeCharacters("a { color: ");
writer.writeCharacters(toRGB(textTheme.getColour()));
writer.writeCharacters("; }\n");
writer.writeCharacters("h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { color: ");
writer.writeCharacters(toRGB(textTheme.getHeaderColour()));
writer.writeCharacters("; }\n");
writer.writeCharacters("div.header { overflow: auto; height: 2em; margin-bottom: 1.5em; }\n");
writer.writeCharacters("div.footer { overflow: auto; margin-top: 6.5em; }\n");
writer.writeCharacters("div.navbar { text-align: right; }\n");
writer.writeCharacters("div.navbar a { color: #909090; margin-left: 2em; }\n");
writer.writeCharacters("div.navbar a:visited { color: #909090; }\n");
}
writer.writeCharacters(".unknown { color: red; }\n");
HtmlTheme htmlTheme = theme.getAspect(HtmlTheme.class);
if (htmlTheme != null) {
StringBuilder rules = new StringBuilder();
htmlTheme.writeStyleRules(rules);
writer.writeCharacters(rules.toString());
}
writer.writeEndElement();
writer.writeCharacters(EOL);
writer.writeEndElement();
writer.writeCharacters(EOL);
writer.writeStartElement("body");
writer.writeCharacters(EOL);
writeHeader(page, writer);
writeChunk(page, page.getChunk(), writer);
writeFooter(page, writer);
writer.writeEndElement();
writer.writeCharacters(EOL);
writer.writeEndElement();
writer.writeCharacters(EOL);
} finally {
writer.close();
}
}
private String toRGB(Color colour) {
return String.format("#%02x%02x%02x", colour.getRed(), colour.getGreen(),
colour.getBlue());
}
private void writeHeader(Page page, XMLStreamWriter writer) throws XMLStreamException {
writeNavLinks(page, "header", writer);
}
private void writeFooter(Page page, XMLStreamWriter writer) throws XMLStreamException {
writeNavLinks(page, "footer", writer);
}
private void writeNavLinks(Page page, String htmlClass, XMLStreamWriter writer) throws XMLStreamException {
if (page.getPreviousUrl() == null && page.getHomeUrl() == null && page.getNextUrl() == null) {
return;
}
writer.writeStartElement("div");
writer.writeAttribute("class", "navbar " + htmlClass);
if (page.getPreviousUrl() != null) {
writer.writeStartElement("a");
writer.writeAttribute("href", page.getPreviousUrl());
writer.writeAttribute("class", "previouslink");
writer.writeCharacters("Previous");
writer.writeEndElement();
}
if (page.getHomeUrl() != null) {
writer.writeStartElement("a");
writer.writeAttribute("href", page.getHomeUrl());
writer.writeAttribute("class", "homelink");
writer.writeCharacters("Home");
writer.writeEndElement();
}
if (page.getNextUrl() != null) {
writer.writeStartElement("a");
writer.writeAttribute("href", page.getNextUrl());
writer.writeAttribute("class", "nextlink");
writer.writeCharacters("Next");
writer.writeEndElement();
}
writer.writeEndElement();
}
private void writeChunk(Page page, Chunk chunk, XMLStreamWriter writer) throws XMLStreamException {
for (Block block : chunk.getContents()) {
if (block instanceof TitleBlock) {
TitleBlock titleBlock = (TitleBlock) block;
writeTitle(page, titleBlock.getComponent(), 1, writer);
} else if (block instanceof Component) {
writeComponent(page, (Component) block, 1, writer);
} else if (block instanceof Error) {
writeErrorBlock((Error) block, writer);
} else {
writeComponentChildBlock(page, block, writer);
}
}
}
private void writeComponent(Page page, Component component, int depth, XMLStreamWriter writer) throws XMLStreamException {
writeTitle(page, component, depth, writer);
for (Block block : component.getContents()) {
if (block instanceof Section) {
Section child = (Section) block;
writeSection(page, child, depth + 1, writer);
} else if (block instanceof Component) {
Component child = (Component) block;
writeComponent(page, child, depth + 1, writer);
} else {
writeComponentChildBlock(page, block, writer);
}
}
}
private void writeSection(Page page, Section section, int depth, XMLStreamWriter writer) throws XMLStreamException {
writeTitle(page, section, depth, writer);
for (Block block : section.getContents()) {
if (block instanceof Section) {
Section child = (Section) block;
writeSection(page, child, depth + 1, writer);
} else {
writeComponentChildBlock(page, block, writer);
}
}
}
private void writeTitle(Page page, Component component, int depth, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("a");
writer.writeAttribute("name", component.getId());
writer.writeEndElement();
if (component.getTitle().isEmpty()) {
return;
}
writer.writeStartElement("h" + Math.min(depth, 6));
writeInline(page, component.getTitle(), writer);
writer.writeEndElement();
writer.writeCharacters(EOL);
}
private void writeComponentChildBlock(Page page, Block block, XMLStreamWriter writer) throws XMLStreamException {
if (block instanceof Paragraph) {
Paragraph paragraph = (Paragraph) block;
writer.writeStartElement("p");
writeInline(page, paragraph, writer);
writer.writeEndElement();
writer.writeCharacters(EOL);
} else if (block instanceof ItemisedList) {
ItemisedList list = (ItemisedList) block;
writer.writeStartElement("ul");
writer.writeCharacters(EOL);
writeItems(page, list, writer);
writer.writeEndElement();
writer.writeCharacters(EOL);
} else if (block instanceof OrderedList) {
OrderedList list = (OrderedList) block;
writer.writeStartElement("ol");
writer.writeCharacters(EOL);
writeItems(page, list, writer);
writer.writeEndElement();
writer.writeCharacters(EOL);
} else if (block instanceof ProgramListing) {
ProgramListing programListing = (ProgramListing) block;
writer.writeStartElement("pre");
writer.writeAttribute("class", "programlisting");
writer.writeCharacters(programListing.getText());
writer.writeEndElement();
writer.writeCharacters(EOL);
} else if (block instanceof Example) {
Example example = (Example) block;
for (Block childBlock : example.getContents()) {
writeComponentChildBlock(page, childBlock, writer);
}
if (!example.getTitle().isEmpty()) {
writer.writeStartElement("p");
writer.writeAttribute("class", "title");
writeInline(page, example.getTitle(), writer);
writer.writeEndElement();
writer.writeCharacters(EOL);
}
} else if (block instanceof Error) {
writeErrorBlock((Error) block, writer);
} else {
throw new IllegalStateException(String.format("Don't know how to render block of type '%s'.",
block.getClass().getSimpleName()));
}
}
private void writeErrorBlock(Error error, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("div");
writer.writeAttribute("class", "unknown");
writer.writeCharacters(error.getMessage());
writer.writeEndElement();
writer.writeCharacters(EOL);
}
private void writeInline(Page page, InlineContainer inline, XMLStreamWriter writer) throws XMLStreamException {
for (Inline element : inline.getContents()) {
if (element instanceof Text) {
Text text = (Text) element;
writer.writeCharacters(text.getText());
} else if (element instanceof Code) {
writeCodeInline(page, (Code) element, "code", writer);
} else if (element instanceof Literal) {
writeCodeInline(page, (Literal) element, "literal", writer);
} else if (element instanceof ClassName) {
writeCodeInline(page, (ClassName) element, "classname", writer);
} else if (element instanceof Emphasis) {
writeEmphasisInline(page, (Emphasis) element, writer);
} else if (element instanceof CrossReference) {
writeCrossReference(page, (CrossReference) element, writer);
} else if (element instanceof Link) {
writeLink(page, (Link) element, writer);
} else if (element instanceof Error) {
writeErrorInline((Error) element, writer);
} else {
throw new IllegalStateException(String.format("Don't know how to render inline of type '%s'.",
element.getClass().getSimpleName()));
}
}
}
private void writeCrossReference(Page page, CrossReference crossReference, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("a");
Referenceable target = crossReference.getTarget();
Page targetPage = page.getPageFor(target);
if (targetPage != page) {
writer.writeAttribute("href", page.getUrlTo(targetPage) + "#" + target.getId());
} else {
writer.writeAttribute("href", "#" + target.getId());
}
writeInline(page, crossReference, writer);
writer.writeEndElement();
}
private void writeLink(Page page, Link link, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("a");
writer.writeAttribute("href", link.getTarget().toString());
writeInline(page, link, writer);
writer.writeEndElement();
}
private void writeErrorInline(Error error, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("span");
writer.writeAttribute("class", "unknown");
writer.writeCharacters(error.getMessage());
writer.writeEndElement();
writer.writeCharacters(EOL);
}
private void writeCodeInline(Page page, InlineContainer code, String htmlClass, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("code");
writer.writeAttribute("class", htmlClass);
writeInline(page, code, writer);
writer.writeEndElement();
}
private void writeEmphasisInline(Page page, InlineContainer emphasis, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("em");
writeInline(page, emphasis, writer);
writer.writeEndElement();
}
private void writeItems(Page page, List list, XMLStreamWriter writer) throws XMLStreamException {
for (ListItem item : list.getItems()) {
writer.writeStartElement("li");
writer.writeCharacters(EOL);
for (Block childBlock : item.getContents()) {
writeComponentChildBlock(page, childBlock, writer);
}
writer.writeEndElement();
writer.writeCharacters(EOL);
}
}
}