package net.java.textilej.parser.builder;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import net.java.textilej.parser.Attributes;
import net.java.textilej.util.FormattingXMLStreamWriter;
import net.java.textilej.util.XmlStreamWriter;
public class HtmlDocumentBuilder extends AbstractXmlDocumentBuilder {
private static final Map<SpanType,String> spanTypeToElementName = new HashMap<SpanType,String>();
static {
spanTypeToElementName.put(SpanType.BOLD,"b");
spanTypeToElementName.put(SpanType.CITATION,"cite");
spanTypeToElementName.put(SpanType.ITALIC,"i");
spanTypeToElementName.put(SpanType.EMPHASIS,"em");
spanTypeToElementName.put(SpanType.STRONG,"strong");
spanTypeToElementName.put(SpanType.DELETED,"del");
spanTypeToElementName.put(SpanType.INSERTED,"ins");
spanTypeToElementName.put(SpanType.SUPERSCRIPT,"sup");
spanTypeToElementName.put(SpanType.SUBSCRIPT,"sub");
spanTypeToElementName.put(SpanType.SPAN,"span");
spanTypeToElementName.put(SpanType.CODE,"code");
spanTypeToElementName.put(SpanType.MONOSPACE,"tt");
}
private static final Map<BlockType,ElementInfo> blockTypeToElementInfo = new HashMap<BlockType,ElementInfo>();
static {
blockTypeToElementInfo.put(BlockType.BULLETED_LIST,new ElementInfo("ul"));
blockTypeToElementInfo.put(BlockType.CODE,new ElementInfo("code" ));
blockTypeToElementInfo.put(BlockType.FOOTNOTE,new ElementInfo("footnote" ));
blockTypeToElementInfo.put(BlockType.LIST_ITEM,new ElementInfo("li"));
blockTypeToElementInfo.put(BlockType.NUMERIC_LIST,new ElementInfo("ol"));
blockTypeToElementInfo.put(BlockType.PARAGRAPH,new ElementInfo("p" ));
blockTypeToElementInfo.put(BlockType.PREFORMATTED,new ElementInfo("pre" ));
blockTypeToElementInfo.put(BlockType.QUOTE,new ElementInfo("blockquote" ));
blockTypeToElementInfo.put(BlockType.TABLE,new ElementInfo("table" ));
blockTypeToElementInfo.put(BlockType.TABLE_CELL_HEADER,new ElementInfo("th" ));
blockTypeToElementInfo.put(BlockType.TABLE_CELL_NORMAL,new ElementInfo("td" ));
blockTypeToElementInfo.put(BlockType.TABLE_ROW,new ElementInfo("tr"));
blockTypeToElementInfo.put(BlockType.TIP,new ElementInfo("div","tip","border: 1px solid #090;background-color: #dfd;margin: 20px;padding: 0px 6px 0px 6px;"));
blockTypeToElementInfo.put(BlockType.WARNING,new ElementInfo("div","warning","border: 1px solid #c00;background-color: #fcc;margin: 20px;padding: 0px 6px 0px 6px;"));
blockTypeToElementInfo.put(BlockType.INFORMATION,new ElementInfo("div","info","border: 1px solid #3c78b5;background-color: #D8E4F1;margin: 20px;padding: 0px 6px 0px 6px;"));
blockTypeToElementInfo.put(BlockType.NOTE,new ElementInfo("div","note","border: 1px solid #F0C000;background-color: #FFFFCE;margin: 20px;padding: 0px 6px 0px 6px;"));
}
private String htmlNsUri = "http://www.w3.org/1999/xhtml";
private String htmlDtd = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
private boolean emitAsDocument = true;
private boolean emitDtd = false;
private String title;
public HtmlDocumentBuilder(Writer out) {
super(out);
}
public HtmlDocumentBuilder(XmlStreamWriter writer) {
super(writer);
}
protected XmlStreamWriter createFormattingXmlStreamWriter(Writer out) {
XmlStreamWriter writer = super.createXmlStreamWriter(out);
return new FormattingXMLStreamWriter(writer) {
@Override
protected boolean preserveWhitespace(String elementName) {
return elementName.equals("pre") || elementName.equals("code");
}
};
}
/**
* The XML Namespace URI of the HTML elements, only used if {@link #isEmitAsDocument()}.
* The default value is "<code>http://www.w3.org/1999/xhtml</code>".
*/
public String getHtmlNsUri() {
return htmlNsUri;
}
/**
* The XML Namespace URI of the HTML elements, only used if {@link #isEmitAsDocument()}.
* The default value is "<code>http://www.w3.org/1999/xhtml</code>".
*/
public void setHtmlNsUri(String htmlNsUri) {
this.htmlNsUri = htmlNsUri;
}
/**
* The DTD to emit, if {@link #isEmitDtd()} and {@link #isEmitAsDocument()}.
* The default value is <code><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></code>
*/
public String getHtmlDtd() {
return htmlDtd;
}
/**
* The DTD to emit, if {@link #isEmitDtd()} and {@link #isEmitAsDocument()}.
* @see #getHtmlDtd()
*/
public void setHtmlDtd(String htmlDtd) {
this.htmlDtd = htmlDtd;
}
/**
* Indicate if the resulting HTML should be emitted as a document. If false, the html and body tags are not included
* in the output.
* Default value is true.
*/
public boolean isEmitAsDocument() {
return emitAsDocument;
}
/**
* Indicate if the resulting HTML should be emitted as a document. If false, the html and body tags are not included
* in the output.
* Default value is true.
*/
public void setEmitAsDocument(boolean emitAsDocument) {
this.emitAsDocument = emitAsDocument;
}
/**
* Indicate if the resulting HTML should include a DTD. Ignored unless {@link #isEmitAsDocument()}.
* Default value is false.
*/
public boolean isEmitDtd() {
return emitDtd;
}
/**
* Indicate if the resulting HTML should include a DTD. Ignored unless {@link #isEmitAsDocument()}.
* Default value is false.
*/
public void setEmitDtd(boolean emitDtd) {
this.emitDtd = emitDtd;
}
/**
* Set the document title, which will be emitted into the <title> element.
* Ignored unless {@link #isEmitAsDocument()}
*
* @return the title or null if there is none
*/
public String getTitle() {
return title;
}
/**
* Set the document title, which will be emitted into the <title> element.
* Ignored unless {@link #isEmitAsDocument()}
*
* @param title the title or null if there is none
*/
public void setTitle(String title) {
this.title = title;
}
@Override
public void beginDocument() {
writer.setDefaultNamespace(htmlNsUri);
if (emitAsDocument) {
writer.writeStartDocument();
if (emitDtd && htmlDtd != null) {
writer.writeDTD(htmlDtd);
}
writer.writeStartElement(htmlNsUri,"html");
writer.writeDefaultNamespace(htmlNsUri);
writer.writeStartElement(htmlNsUri,"head");
if (base != null && baseInHead) {
writer.writeEmptyElement(htmlNsUri,"base");
writer.writeAttribute("href", base.toString());
}
if (title != null) {
writer.writeStartElement(htmlNsUri,"title");
writer.writeCharacters(title);
writer.writeEndElement(); // title
}
writer.writeEndElement(); // head
writer.writeStartElement(htmlNsUri,"body");
}
}
@Override
public void endDocument() {
if (emitAsDocument) {
writer.writeEndElement(); // body
writer.writeEndElement(); // html
writer.writeEndDocument();
}
writer.close();
}
@Override
public void entityReference(String entity) {
writer.writeEntityRef(entity);
}
@Override
public void acronym(String text, String definition) {
writer.writeStartElement(htmlNsUri,"acronym");
writer.writeAttribute("title", definition);
writer.writeCharacters(text);
writer.writeEndElement();
}
@Override
public void link(String hrefOrHashName, String text) {
writer.writeStartElement(htmlNsUri,"a");
writer.writeAttribute("href", makeUrlAbsolute(hrefOrHashName));
characters(text);
writer.writeEndElement(); // a
}
@Override
public void beginBlock(BlockType type, Attributes attributes) {
ElementInfo elementInfo = blockTypeToElementInfo.get(type);
if (elementInfo == null) {
throw new IllegalStateException(type.name());
}
writer.writeStartElement(htmlNsUri,elementInfo.name);
if (elementInfo.cssClass != null) {
if (attributes.getCssClass() == null) {
attributes.setCssClass(elementInfo.cssClass);
} else {
attributes.setCssClass(elementInfo.cssClass+' '+attributes.getCssClass());
}
}
if (elementInfo.cssStyles != null) {
if (attributes.getCssStyle() == null) {
attributes.setCssStyle(elementInfo.cssStyles);
} else {
attributes.setCssStyle(elementInfo.cssStyles+attributes.getCssStyle());
}
}
applyAttributes(attributes);
if (attributes.getTitle() != null) {
beginBlock(BlockType.PARAGRAPH, new Attributes());
beginSpan(SpanType.BOLD, new Attributes());
characters(attributes.getTitle());
endSpan();
endBlock();
}
}
@Override
public void beginHeading(int level, Attributes attributes) {
if (level > 6) {
level = 6;
}
writer.writeStartElement(htmlNsUri,"h"+level);
applyAttributes(attributes);
}
@Override
public void beginSpan(SpanType type, Attributes attributes) {
String elementName = spanTypeToElementName.get(type);
if (elementName == null) {
throw new IllegalStateException(type.name());
}
writer.writeStartElement(htmlNsUri,elementName);
applyAttributes(attributes);
}
@Override
public void endBlock() {
writer.writeEndElement();
}
@Override
public void endHeading() {
writer.writeEndElement();
}
@Override
public void endSpan() {
writer.writeEndElement();
}
@Override
public void image(Attributes attributes, String url) {
writer.writeEmptyElement(htmlNsUri,"img");
applyAttributes(attributes);
writer.writeAttribute("src", makeUrlAbsolute(url));
}
private void applyAttributes(Attributes attributes) {
if (attributes.getId() != null) {
writer.writeAttribute("id", attributes.getId());
}
if (attributes.getCssClass() != null) {
writer.writeAttribute("class", attributes.getCssClass());
}
if (attributes.getCssStyle() != null) {
writer.writeAttribute("style", attributes.getCssStyle());
}
if (attributes.getLanguage() != null) {
writer.writeAttribute("lang", attributes.getLanguage());
}
}
@Override
public void imageLink(String href, String imageUrl) {
writer.writeStartElement(htmlNsUri,"a");
writer.writeAttribute("href", makeUrlAbsolute(href));
writer.writeEmptyElement(htmlNsUri,"img");
writer.writeAttribute("src", makeUrlAbsolute(imageUrl));
writer.writeAttribute("border", "0");
writer.writeEndElement(); // a
}
@Override
public void lineBreak() {
writer.writeEmptyElement(htmlNsUri,"br");
writer.writeCharacters("\n");
}
public void charactersUnescaped(String literal) {
writer.writeLiteral(literal);
}
private static final class ElementInfo {
final String name;
final String cssClass;
final String cssStyles;
public ElementInfo(String name, String cssClass, String cssStyles) {
this.name = name;
this.cssClass = cssClass;
this.cssStyles = cssStyles != null && !cssStyles.endsWith(";")?cssStyles+';':cssStyles;
}
public ElementInfo(String name) {
this(name,null,null);
}
}
}